From 131e3dcd39d2ed031483c08f17d2240f1f40ff29 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 14:16:24 +0200 Subject: [PATCH 01/10] feat: support AbortSignal on more methods (#41547) --- docs/src/api/class-apirequestcontext.md | 14 +++ docs/src/api/class-locator.md | 4 + docs/src/api/class-route.md | 2 + packages/playwright-client/types/types.d.ts | 92 ++++++++++++++++++- packages/playwright-core/src/client/fetch.ts | 3 +- .../playwright-core/src/client/locator.ts | 4 +- .../playwright-core/src/client/network.ts | 2 +- packages/playwright-core/types/types.d.ts | 92 ++++++++++++++++++- utils/generate_types/overrides.d.ts | 4 +- 9 files changed, 207 insertions(+), 10 deletions(-) diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index 2ddc3815f6b18..28399a0b031b8 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -184,6 +184,8 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.delete.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.delete.signal = %%-input-signal-%% + ### option: APIRequestContext.delete.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -361,6 +363,8 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/ ### option: APIRequestContext.fetch.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.fetch.signal = %%-input-signal-%% + ### option: APIRequestContext.fetch.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -474,6 +478,8 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query ### option: APIRequestContext.get.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.get.signal = %%-input-signal-%% + ### option: APIRequestContext.get.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -539,6 +545,8 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.head.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.head.signal = %%-input-signal-%% + ### option: APIRequestContext.head.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -604,6 +612,8 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.patch.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.patch.signal = %%-input-signal-%% + ### option: APIRequestContext.patch.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -790,6 +800,8 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar ### option: APIRequestContext.post.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.post.signal = %%-input-signal-%% + ### option: APIRequestContext.post.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 @@ -855,6 +867,8 @@ context cookies from the response. The method will automatically follow redirect ### option: APIRequestContext.put.timeout = %%-js-python-csharp-fetch-option-timeout-%% * since: v1.16 +### option: APIRequestContext.put.signal = %%-input-signal-%% + ### option: APIRequestContext.put.failOnStatusCode = %%-js-python-csharp-fetch-option-failonstatuscode-%% * since: v1.16 diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 3cb294302d111..bba1f1f9a699a 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1137,6 +1137,8 @@ Maximum time in milliseconds to wait for the locator before evaluating. Note tha Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation itself is not limited by the timeout. Defaults to `0` - no timeout. +### option: Locator.evaluate.signal = %%-input-signal-%% + ## async method: Locator.evaluateAll * since: v1.14 - returns: <[Serializable]> @@ -1234,6 +1236,8 @@ Maximum time in milliseconds to wait for the locator before evaluating. Note tha Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation itself is not limited by the timeout. Defaults to `0` - no timeout. +### option: Locator.evaluateHandle.signal = %%-input-signal-%% + ## async method: Locator.fill * since: v1.14 diff --git a/docs/src/api/class-route.md b/docs/src/api/class-route.md index 5bbe4b3f8374b..ccccbe556c71a 100644 --- a/docs/src/api/class-route.md +++ b/docs/src/api/class-route.md @@ -528,6 +528,8 @@ Maximum number of times network errors should be retried. Currently only `ECONNR Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. +### option: Route.fetch.signal = %%-input-signal-%% + ### option: Route.fetch.method * since: v1.29 - `method` <[string]> diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 6da20cd4fa3a7..da1cec18709e9 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -13850,7 +13850,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression). * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; /** * Execute JavaScript code in the page, taking the matching element as an argument, and return a * [JSHandle](https://playwright.dev/docs/api/class-jshandle) with the result. @@ -13883,7 +13883,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression). * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; /** * Execute JavaScript code in the page, taking all matching elements as an argument. * @@ -19044,6 +19044,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19181,6 +19192,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19293,6 +19315,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19379,6 +19412,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19465,6 +19509,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19593,6 +19648,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19679,6 +19745,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -22394,6 +22471,17 @@ export interface Route { */ postData?: string|Buffer|Serializable; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 6439951aa475d..59a729ae636ee 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -44,6 +44,7 @@ export type FetchOptions = { form?: { [key: string]: string|number|boolean; } | FormData; multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; } | FormData; timeout?: number, + signal?: AbortSignal, failOnStatusCode?: boolean, ignoreHTTPSErrors?: boolean, maxRedirects?: number, @@ -260,7 +261,7 @@ export class APIRequestContext extends ChannelOwner(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise { - return await this._withElement(h => h.evaluate(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout }); + return await this._withElement(h => h.evaluate(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); } async evaluateAll(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { @@ -142,7 +142,7 @@ export class Locator implements api.Locator { } async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise> { - return await this._withElement(h => h.evaluateHandle(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout }); + return await this._withElement(h => h.evaluateHandle(pageFunction, arg), { title: 'Evaluate', timeout: options?.timeout, signal: options?.signal }); } async fill(value: string, options: channels.ElementHandleFillOptions & TimeoutOptions = {}): Promise { diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index e1efd766913c6..6986160ee7aa2 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -346,7 +346,7 @@ export class Route extends ChannelOwner implements api.Ro }); } - async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise { + async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number, signal?: AbortSignal } = {}): Promise { return await this._wrapApiCall(async () => { return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options }); }); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 6da20cd4fa3a7..da1cec18709e9 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13850,7 +13850,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression). * @param options */ - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; /** * Execute JavaScript code in the page, taking the matching element as an argument, and return a * [JSHandle](https://playwright.dev/docs/api/class-jshandle) with the result. @@ -13883,7 +13883,7 @@ export interface Locator { * [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-handle-option-expression). * @param options */ - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise>; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; /** * Execute JavaScript code in the page, taking all matching elements as an argument. * @@ -19044,6 +19044,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19181,6 +19192,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19293,6 +19315,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19379,6 +19412,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19465,6 +19509,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19593,6 +19648,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -19679,6 +19745,17 @@ export interface APIRequestContext { */ params?: { [key: string]: string|number|boolean; }|URLSearchParams|string; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ @@ -22394,6 +22471,17 @@ export interface Route { */ postData?: string|Buffer|Serializable; + /** + * Allows to cancel the operation using an + * [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the + * operation will be aborted and throw an error. Note that providing a signal does not disable the default timeout, + * which can be changed using + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout); pass + * `timeout: 0` to disable the timeout entirely. + */ + signal?: AbortSignal; + /** * Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. */ diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 54300f3c1e90b..302fd7e31294e 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -190,8 +190,8 @@ export interface ElementHandle extends JSHandle { } export interface Locator { - evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise; - evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number }): Promise>; + evaluate(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; + evaluateHandle(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise>; evaluateAll(pageFunction: PageFunctionOn, arg?: Arg): Promise; waitForFunction(pageFunction: PageFunctionOn, arg?: Arg, options?: { timeout?: number, signal?: AbortSignal }): Promise; elementHandle(options?: { timeout?: number }): Promise>; From ddf814cfbd40f88fc53637c5f747b7bdc9823d5d Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 14:42:13 +0200 Subject: [PATCH 02/10] chore(tsconfig): drop misleading extends fallback comment (#41593) --- .../src/transform/tsconfig-loader.ts | 1 - tests/playwright-test/resolver.spec.ts | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/transform/tsconfig-loader.ts b/packages/playwright/src/transform/tsconfig-loader.ts index 3dc9d7e89d9fb..690034cf6bb69 100644 --- a/packages/playwright/src/transform/tsconfig-loader.ts +++ b/packages/playwright/src/transform/tsconfig-loader.ts @@ -67,7 +67,6 @@ function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string, referencedConfigFile += '.json'; const currentDir = path.dirname(baseConfigFile); let resolvedConfigFile = path.resolve(currentDir, referencedConfigFile); - // TODO: I don't see how this makes sense, delete in the next minor release. if (referencedConfigFile.includes('/') && referencedConfigFile.includes('.') && !fs.existsSync(resolvedConfigFile)) resolvedConfigFile = path.join(currentDir, 'node_modules', referencedConfigFile); if (!fs.existsSync(resolvedConfigFile)) diff --git a/tests/playwright-test/resolver.spec.ts b/tests/playwright-test/resolver.spec.ts index 765897bbe67fd..c9909e0f871f4 100644 --- a/tests/playwright-test/resolver.spec.ts +++ b/tests/playwright-test/resolver.spec.ts @@ -587,6 +587,41 @@ test('should resolve paths relative to the originating config when extending and expect(result.exitCode).toBe(0); }); +test('should resolve extends from an explicit node_modules subpath', async ({ runInlineTest }) => { + // The @tsconfig/* base packages are commonly referenced by an explicit subpath, + // e.g. "extends": "@tsconfig/node18/tsconfig.json". Playwright resolves such a + // value through the config's node_modules when it is not found relative to the + // config directory. Removing that fallback would break these configs. + const result = await runInlineTest({ + 'node_modules/@my/tsconfig-base/tsconfig.json': `{ + "compilerOptions": { + "paths": { + "util/*": ["./mapped/*"], + }, + }, + }`, + 'tsconfig.json': `{ + "extends": "@my/tsconfig-base/tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + }, + }`, + 'a.test.ts': ` + import { foo } from 'util/file'; + import { test, expect } from '@playwright/test'; + test('test', () => { + expect(foo).toBe('foo'); + }); + `, + 'mapped/file.ts': ` + export const foo = 'foo'; + `, + }); + + expect(result.passed).toBe(1); + expect(result.exitCode).toBe(0); +}); + test('should fail loudly when extends path cannot be resolved', async ({ runInlineTest }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41543' }); From 682ae319e67957fc102e6a8f6da76bd41c386dd7 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 15:28:55 +0200 Subject: [PATCH 03/10] devops: fix missing test report comment on same-repo PRs (#41595) --- .github/workflows/create_test_report.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index 7850e4f1c07b1..2473a050962e6 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -45,16 +45,16 @@ jobs: retention-days: 30 # The triggering workflow ran on a pull_request event, but workflow_run.pull_requests is - # empty for PRs from forks, so resolve the number from the head repo + branch instead. + # empty for PRs from forks, so resolve the number from the head ref instead. gh's head + # filter wants "owner:branch" for forks but a bare branch for same-repo PRs. - name: Resolve PR number if: ${{ github.event.workflow_run.event == 'pull_request' }} id: pr env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch || format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) }} run: | - NUMBER=$(gh pr view --repo "${{ github.repository }}" \ - "${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }}" \ - --json number --jq '.number' 2>/dev/null || true) + NUMBER=$(gh pr view --repo "${{ github.repository }}" "$HEAD_REF" --json number --jq '.number' 2>/dev/null || true) echo "number=$NUMBER" >> "$GITHUB_OUTPUT" - name: Post report comment to PR From dd6342af46fe3a04039c9e999457487905295a10 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 2 Jul 2026 14:31:38 +0100 Subject: [PATCH 04/10] chore: move codegen and device descriptors into @isomorphic (#41594) --- .../injected/src/recorder/pollingRecorder.ts | 2 +- packages/injected/src/recorder/recorder.ts | 2 +- packages/isomorphic/codegen/DEPS.list | 1 + .../src => isomorphic/codegen}/actions.d.ts | 0 .../src/server => isomorphic}/codegen/csharp.ts | 8 ++++---- .../src/server => isomorphic}/codegen/java.ts | 10 +++++----- .../server => isomorphic}/codegen/javascript.ts | 8 ++++---- .../src/server => isomorphic}/codegen/jsonl.ts | 4 ++-- .../server => isomorphic}/codegen/language.ts | 6 +++--- .../server => isomorphic}/codegen/languages.ts | 0 .../src/server => isomorphic}/codegen/python.ts | 8 ++++---- .../src/server => isomorphic}/codegen/types.ts | 17 ++++++++++++++--- .../server => isomorphic}/deviceDescriptors.ts | 0 .../deviceDescriptorsSource.json | 0 packages/isomorphic/types.ts | 10 ++++++++++ .../src/client/browserContext.ts | 2 +- packages/playwright-core/src/server/DEPS.list | 1 - .../src/server/codegen/DEPS.list | 3 --- .../src/server/debugController.ts | 6 +++--- .../server/dispatchers/localUtilsDispatcher.ts | 2 +- packages/playwright-core/src/server/index.ts | 2 +- packages/playwright-core/src/server/recorder.ts | 4 ++-- .../src/server/recorder/DEPS.list | 2 -- .../src/server/recorder/recorderApp.ts | 8 ++++---- .../src/server/recorder/recorderRunner.ts | 4 ++-- .../server/recorder/recorderSignalProcessor.ts | 4 ++-- .../src/server/recorder/recorderUtils.ts | 2 +- packages/playwright-core/src/server/types.ts | 11 +---------- tests/library/inspector/recorder-api.spec.ts | 2 +- utils/build/build.js | 2 +- utils/doclint/cli.js | 2 +- utils/generate_types/index.js | 2 +- 32 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 packages/isomorphic/codegen/DEPS.list rename packages/{recorder/src => isomorphic/codegen}/actions.d.ts (100%) rename packages/{playwright-core/src/server => isomorphic}/codegen/csharp.ts (98%) rename packages/{playwright-core/src/server => isomorphic}/codegen/java.ts (97%) rename packages/{playwright-core/src/server => isomorphic}/codegen/javascript.ts (98%) rename packages/{playwright-core/src/server => isomorphic}/codegen/jsonl.ts (92%) rename packages/{playwright-core/src/server => isomorphic}/codegen/language.ts (96%) rename packages/{playwright-core/src/server => isomorphic}/codegen/languages.ts (100%) rename packages/{playwright-core/src/server => isomorphic}/codegen/python.ts (97%) rename packages/{playwright-core/src/server => isomorphic}/codegen/types.ts (70%) rename packages/{playwright-core/src/server => isomorphic}/deviceDescriptors.ts (100%) rename packages/{playwright-core/src/server => isomorphic}/deviceDescriptorsSource.json (100%) delete mode 100644 packages/playwright-core/src/server/codegen/DEPS.list diff --git a/packages/injected/src/recorder/pollingRecorder.ts b/packages/injected/src/recorder/pollingRecorder.ts index 55dd2f0ea42e4..5649dd852b1a7 100644 --- a/packages/injected/src/recorder/pollingRecorder.ts +++ b/packages/injected/src/recorder/pollingRecorder.ts @@ -18,7 +18,7 @@ import { Recorder } from './recorder'; import type { InjectedScript } from '../injectedScript'; import type { RecorderDelegate } from './recorder'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorderTypes'; interface Embedder { diff --git a/packages/injected/src/recorder/recorder.ts b/packages/injected/src/recorder/recorder.ts index fd51d5ca05be3..9005d70bc403d 100644 --- a/packages/injected/src/recorder/recorder.ts +++ b/packages/injected/src/recorder/recorder.ts @@ -23,7 +23,7 @@ import type { AriaSnapshot } from '../ariaSnapshot'; import type { Highlight, HighlightEntry } from '../highlight'; import type { InjectedScript } from '../injectedScript'; import type { ElementText } from '../selectorUtils'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorderTypes'; import type { Language } from '@isomorphic/locatorGenerators'; diff --git a/packages/isomorphic/codegen/DEPS.list b/packages/isomorphic/codegen/DEPS.list new file mode 100644 index 0000000000000..e43dcb5d9f30b --- /dev/null +++ b/packages/isomorphic/codegen/DEPS.list @@ -0,0 +1 @@ +[*] diff --git a/packages/recorder/src/actions.d.ts b/packages/isomorphic/codegen/actions.d.ts similarity index 100% rename from packages/recorder/src/actions.d.ts rename to packages/isomorphic/codegen/actions.d.ts diff --git a/packages/playwright-core/src/server/codegen/csharp.ts b/packages/isomorphic/codegen/csharp.ts similarity index 98% rename from packages/playwright-core/src/server/codegen/csharp.ts rename to packages/isomorphic/codegen/csharp.ts index 078cf2dd48b79..af9bb4ff2f08d 100644 --- a/packages/playwright-core/src/server/codegen/csharp.ts +++ b/packages/isomorphic/codegen/csharp.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { asLocator } from '@isomorphic/locatorGenerators'; -import { escapeWithQuotes } from '@isomorphic/stringUtils'; +import { asLocator } from '../locatorGenerators'; +import { escapeWithQuotes } from '../stringUtils'; import { sanitizeDeviceOptions, toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type { BrowserContextOptions } from '../../..'; -import type * as actions from '@recorder/actions'; +import type { BrowserContextOptions } from 'playwright-core'; +import type * as actions from './actions'; type CSharpLanguageMode = 'library' | 'mstest' | 'nunit' | 'xunit'; diff --git a/packages/playwright-core/src/server/codegen/java.ts b/packages/isomorphic/codegen/java.ts similarity index 97% rename from packages/playwright-core/src/server/codegen/java.ts rename to packages/isomorphic/codegen/java.ts index 7aa8c3ce483a3..cbaa0cfebcda7 100644 --- a/packages/playwright-core/src/server/codegen/java.ts +++ b/packages/isomorphic/codegen/java.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { asLocator } from '@isomorphic/locatorGenerators'; -import { escapeWithQuotes } from '@isomorphic/stringUtils'; +import { asLocator } from '../locatorGenerators'; +import { escapeWithQuotes } from '../stringUtils'; import { toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import { JavaScriptFormatter } from './javascript'; -import type { BrowserContextOptions } from '../../..'; -import type * as types from '../types'; +import type { BrowserContextOptions } from 'playwright-core'; +import type * as types from './types'; import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type * as actions from '@recorder/actions'; +import type * as actions from './actions'; type JavaLanguageMode = 'library' | 'junit'; diff --git a/packages/playwright-core/src/server/codegen/javascript.ts b/packages/isomorphic/codegen/javascript.ts similarity index 98% rename from packages/playwright-core/src/server/codegen/javascript.ts rename to packages/isomorphic/codegen/javascript.ts index 202dd6797a570..8972143a6729a 100644 --- a/packages/playwright-core/src/server/codegen/javascript.ts +++ b/packages/isomorphic/codegen/javascript.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { asLocator } from '@isomorphic/locatorGenerators'; -import { escapeWithQuotes, formatObject, formatObjectOrVoid } from '@isomorphic/stringUtils'; +import { asLocator } from '../locatorGenerators'; +import { escapeWithQuotes, formatObject, formatObjectOrVoid } from '../stringUtils'; import { sanitizeDeviceOptions, toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type { BrowserContextOptions } from '../../..'; -import type * as actions from '@recorder/actions'; +import type { BrowserContextOptions } from 'playwright-core'; +import type * as actions from './actions'; export class JavaScriptLanguageGenerator implements LanguageGenerator { id: string; diff --git a/packages/playwright-core/src/server/codegen/jsonl.ts b/packages/isomorphic/codegen/jsonl.ts similarity index 92% rename from packages/playwright-core/src/server/codegen/jsonl.ts rename to packages/isomorphic/codegen/jsonl.ts index 29591b6b3898b..c6c44ccad5e90 100644 --- a/packages/playwright-core/src/server/codegen/jsonl.ts +++ b/packages/isomorphic/codegen/jsonl.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { asLocator } from '@isomorphic/locatorGenerators'; +import { asLocator } from '../locatorGenerators'; import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type * as actions from '@recorder/actions'; +import type * as actions from './actions'; export class JsonlLanguageGenerator implements LanguageGenerator { id = 'jsonl'; diff --git a/packages/playwright-core/src/server/codegen/language.ts b/packages/isomorphic/codegen/language.ts similarity index 96% rename from packages/playwright-core/src/server/codegen/language.ts rename to packages/isomorphic/codegen/language.ts index 1da8a915617ec..413ee9f0864de 100644 --- a/packages/playwright-core/src/server/codegen/language.ts +++ b/packages/isomorphic/codegen/language.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import type { BrowserContextOptions } from '../../..'; -import type * as types from '../types'; +import type { BrowserContextOptions } from 'playwright-core'; +import type * as types from './types'; import type { LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type * as actions from '@recorder/actions'; +import type * as actions from './actions'; export function generateCode(actions: actions.ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) { const header = languageGenerator.generateHeader(options); diff --git a/packages/playwright-core/src/server/codegen/languages.ts b/packages/isomorphic/codegen/languages.ts similarity index 100% rename from packages/playwright-core/src/server/codegen/languages.ts rename to packages/isomorphic/codegen/languages.ts diff --git a/packages/playwright-core/src/server/codegen/python.ts b/packages/isomorphic/codegen/python.ts similarity index 97% rename from packages/playwright-core/src/server/codegen/python.ts rename to packages/isomorphic/codegen/python.ts index e9f9caf40a7f7..833bdbda08f6a 100644 --- a/packages/playwright-core/src/server/codegen/python.ts +++ b/packages/isomorphic/codegen/python.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { asLocator } from '@isomorphic/locatorGenerators'; -import { escapeWithQuotes, toSnakeCase } from '@isomorphic/stringUtils'; +import { asLocator } from '../locatorGenerators'; +import { escapeWithQuotes, toSnakeCase } from '../stringUtils'; import { sanitizeDeviceOptions, toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; -import type { BrowserContextOptions } from '../../..'; -import type * as actions from '@recorder/actions'; +import type { BrowserContextOptions } from 'playwright-core'; +import type * as actions from './actions'; export class PythonLanguageGenerator implements LanguageGenerator { id: string; diff --git a/packages/playwright-core/src/server/codegen/types.ts b/packages/isomorphic/codegen/types.ts similarity index 70% rename from packages/playwright-core/src/server/codegen/types.ts rename to packages/isomorphic/codegen/types.ts index ef905649f515e..105590eac779e 100644 --- a/packages/playwright-core/src/server/codegen/types.ts +++ b/packages/isomorphic/codegen/types.ts @@ -14,11 +14,22 @@ * limitations under the License. */ -import type { BrowserContextOptions, LaunchOptions } from '../../..'; -import type * as actions from '@recorder/actions'; -import type { Language } from '@isomorphic/locatorGenerators'; +import type { BrowserContextOptions, LaunchOptions } from 'playwright-core'; +import type * as actions from './actions'; +import type { Language } from '../locatorGenerators'; +import type { Point } from '../types'; export type { Language }; +export type SmartKeyboardModifier = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'ControlOrMeta'; + +export type MouseClickOptions = { + modifiers?: SmartKeyboardModifier[]; + position?: Point; + delay?: number; + button?: 'left' | 'right' | 'middle'; + clickCount?: number; +}; + export type LanguageGeneratorOptions = { browserName: string; launchOptions: LaunchOptions; diff --git a/packages/playwright-core/src/server/deviceDescriptors.ts b/packages/isomorphic/deviceDescriptors.ts similarity index 100% rename from packages/playwright-core/src/server/deviceDescriptors.ts rename to packages/isomorphic/deviceDescriptors.ts diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/isomorphic/deviceDescriptorsSource.json similarity index 100% rename from packages/playwright-core/src/server/deviceDescriptorsSource.json rename to packages/isomorphic/deviceDescriptorsSource.json diff --git a/packages/isomorphic/types.ts b/packages/isomorphic/types.ts index 8bf7df0614d1c..a3370e0b22881 100644 --- a/packages/isomorphic/types.ts +++ b/packages/isomorphic/types.ts @@ -20,3 +20,13 @@ export type Rect = Size & Point; export type Quad = [ Point, Point, Point, Point ]; export type NameValue = { name: string, value: string }; export type HeadersArray = NameValue[]; + +export type DeviceDescriptor = { + userAgent: string, + viewport: Size, + deviceScaleFactor: number, + isMobile: boolean, + hasTouch: boolean, + defaultBrowserType: 'chromium' | 'firefox' | 'webkit' +}; +export type Devices = { [name: string]: DeviceDescriptor }; diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 2052cbfedaf04..a914b800dbc89 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -51,7 +51,7 @@ import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; import type { URLMatch } from '@isomorphic/urlMatch'; import type * as channels from './channels'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; interface RecorderEventSink { actionAdded?(page: Page, actionInContext: actions.ActionInContext, code: string): void; diff --git a/packages/playwright-core/src/server/DEPS.list b/packages/playwright-core/src/server/DEPS.list index f9cf05a0b315c..d0741477416db 100644 --- a/packages/playwright-core/src/server/DEPS.list +++ b/packages/playwright-core/src/server/DEPS.list @@ -5,7 +5,6 @@ ../package.ts ../protocol/ ./ -./codegen/ ./har/ ./recorder/ ./registry/ diff --git a/packages/playwright-core/src/server/codegen/DEPS.list b/packages/playwright-core/src/server/codegen/DEPS.list deleted file mode 100644 index 2258dc1a0431e..0000000000000 --- a/packages/playwright-core/src/server/codegen/DEPS.list +++ /dev/null @@ -1,3 +0,0 @@ -[*] -@isomorphic/** -../deviceDescriptors.ts diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index a733dd2379cbd..ff755bfdd2347 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -19,11 +19,11 @@ import { parseAriaSnapshotUnsafe } from '@isomorphic/ariaSnapshot'; import { unsafeLocatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher'; import { asLocator } from '@isomorphic/locatorGenerators'; +import { generateCode } from '@isomorphic/codegen/language'; +import { JavaScriptLanguageGenerator } from '@isomorphic/codegen/javascript'; import { SdkObject, createInstrumentation } from './instrumentation'; import { Recorder, RecorderEvent } from './recorder'; -import { generateCode } from './codegen/language'; import { collapseActions } from './recorder/recorderUtils'; -import { JavaScriptLanguageGenerator } from './codegen/javascript'; import type { Language } from '@isomorphic/locatorGenerators'; import type { BrowserContext } from './browserContext'; @@ -31,7 +31,7 @@ import type { InstrumentationListener } from './instrumentation'; import type { Playwright } from './playwright'; import type { ElementInfo, Mode } from '@recorder/recorderTypes'; import type { Progress } from './progress'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; export class DebugController extends SdkObject { static Events = { diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index eb44eed4540d9..536d92e6fdff2 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -16,12 +16,12 @@ import net from 'net'; import { resolveGlobToRegexPattern } from '@isomorphic/urlMatch'; +import { deviceDescriptors as descriptors } from '@isomorphic/deviceDescriptors'; import { fetchData } from '../utils'; import { getUserAgent } from '../userAgent'; import { Dispatcher } from './dispatcher'; import { SdkObject } from '../instrumentation'; import * as localUtils from '../localUtils'; -import { deviceDescriptors as descriptors } from '../deviceDescriptors'; import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher'; import { PipeTransport } from '../pipeTransport'; import { Progress } from '../progress'; diff --git a/packages/playwright-core/src/server/index.ts b/packages/playwright-core/src/server/index.ts index 0dad0c0e6feec..f98b5cfc2e2dc 100644 --- a/packages/playwright-core/src/server/index.ts +++ b/packages/playwright-core/src/server/index.ts @@ -17,7 +17,7 @@ export { Browser } from './browser'; export { BrowserContext } from './browserContext'; export { findRepeatedSubsequencesForTest } from './callLog'; -export { deviceDescriptors } from './deviceDescriptors'; +export { deviceDescriptors } from '@isomorphic/deviceDescriptors'; export { DispatcherConnection, RootDispatcher, setMaxDispatchersForTest } from './dispatchers/dispatcher'; export { RequestDispatcher, ResponseDispatcher } from './dispatchers/networkDispatchers'; export { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 360431911b222..ede46cd5b94ae 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -34,13 +34,13 @@ import { Frame } from './frames'; import { Page } from './page'; import { performAction } from './recorder/recorderRunner'; -import type { Language } from './codegen/types'; +import type { Language } from '@isomorphic/codegen/types'; import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; import type { Point } from '@isomorphic/types'; import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot'; import type { Progress } from './progress'; import type * as channels from './channels'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { CallLog, CallLogStatus, ElementInfo, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes'; import type { RegisteredListener } from '@utils/eventsHelper'; diff --git a/packages/playwright-core/src/server/recorder/DEPS.list b/packages/playwright-core/src/server/recorder/DEPS.list index a2d3fe744f93d..fa2415126a0c0 100644 --- a/packages/playwright-core/src/server/recorder/DEPS.list +++ b/packages/playwright-core/src/server/recorder/DEPS.list @@ -2,7 +2,5 @@ @isomorphic/** @utils/** ../ -../codegen/language.ts -../codegen/languages.ts ../../package.ts node_modules/mime diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 9e6b37e7c5d8d..c692d1cc361fc 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -19,21 +19,21 @@ import path from 'path'; import mime from 'mime'; import { isUnderTest } from '@utils/debug'; +import { languageSet } from '@isomorphic/codegen/languages'; +import { generateCode } from '@isomorphic/codegen/language'; import { libPath } from '../../package'; import { syncLocalStorageWithSettings } from '../launchApp'; import { launchApp } from '../launchApp'; import { nullProgress, ProgressController } from '../progress'; import { ThrottledFile } from './throttledFile'; -import { languageSet } from '../codegen/languages'; import { collapseActions, shouldMergeAction } from './recorderUtils'; -import { generateCode } from '../codegen/language'; import { Recorder, RecorderEvent } from '../recorder'; import { BrowserContext } from '../browserContext'; import type { Page } from '../page'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { CallLog, ElementInfo, Mode, RecorderBackend, RecorderFrontend, Source } from '@recorder/recorderTypes'; -import type { Language, LanguageGeneratorOptions } from '../codegen/types'; +import type { Language, LanguageGeneratorOptions } from '@isomorphic/codegen/types'; import type * as channels from '../channels'; import type { Progress } from '../progress'; import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot'; diff --git a/packages/playwright-core/src/server/recorder/recorderRunner.ts b/packages/playwright-core/src/server/recorder/recorderRunner.ts index 125e748396e02..dc0dc804a9713 100644 --- a/packages/playwright-core/src/server/recorder/recorderRunner.ts +++ b/packages/playwright-core/src/server/recorder/recorderRunner.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { toKeyboardModifiers } from '../codegen/language'; +import { toKeyboardModifiers } from '@isomorphic/codegen/language'; import { buildFullSelector, mainFrameForAction } from './recorderUtils'; import { Progress } from '../progress'; import type { Page } from '../page'; import type * as types from '../types'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { Frame } from '../frames'; export async function performAction(progress: Progress, pageAliases: Map, actionInContext: actions.ActionInContext) { diff --git a/packages/playwright-core/src/server/recorder/recorderSignalProcessor.ts b/packages/playwright-core/src/server/recorder/recorderSignalProcessor.ts index ce8e0349d2130..634b65b206388 100644 --- a/packages/playwright-core/src/server/recorder/recorderSignalProcessor.ts +++ b/packages/playwright-core/src/server/recorder/recorderSignalProcessor.ts @@ -19,9 +19,9 @@ import { isUnderTest } from '@utils/debug'; import { generateFrameSelector } from './recorderUtils'; import { nullProgress } from '../progress'; -import type { Signal } from '../../../../recorder/src/actions'; +import type { Signal } from '@isomorphic/codegen/actions'; import type { Frame } from '../frames'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; export interface ProcessorDelegate { addAction(actionInContext: actions.ActionInContext): void; diff --git a/packages/playwright-core/src/server/recorder/recorderUtils.ts b/packages/playwright-core/src/server/recorder/recorderUtils.ts index fcbf6c835f6b6..67349e58569ff 100644 --- a/packages/playwright-core/src/server/recorder/recorderUtils.ts +++ b/packages/playwright-core/src/server/recorder/recorderUtils.ts @@ -22,7 +22,7 @@ import { Frame } from '../frames'; import type { CallMetadata } from '../instrumentation'; import type { Page } from '../page'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; import type { CallLog, CallLogStatus } from '@recorder/recorderTypes'; import type { Progress } from '../progress'; diff --git a/packages/playwright-core/src/server/types.ts b/packages/playwright-core/src/server/types.ts index 4c3fb2cf22c5e..a834844c09036 100644 --- a/packages/playwright-core/src/server/types.ts +++ b/packages/playwright-core/src/server/types.ts @@ -16,7 +16,7 @@ */ import type { HeadersArray, Point, Size } from '@isomorphic/types'; -export type { HeadersArray, Point, Quad, Rect, Size } from '@isomorphic/types'; +export type { HeadersArray, Point, Quad, Rect, Size, DeviceDescriptor, Devices } from '@isomorphic/types'; import type * as channels from './channels'; import type { ProxySettings } from '@utils/network'; @@ -94,15 +94,6 @@ export type ForcedColors = 'active' | 'none' | 'no-override'; export type Contrast = 'no-preference' | 'more' | 'no-override'; -export type DeviceDescriptor = { - userAgent: string, - viewport: Size, - deviceScaleFactor: number, - isMobile: boolean, - hasTouch: boolean, - defaultBrowserType: 'chromium' | 'firefox' | 'webkit' -}; -export type Devices = { [name: string]: DeviceDescriptor }; export type { ProxySettings } from '@utils/network'; export type KeyboardModifier = 'Alt' | 'Control' | 'Meta' | 'Shift'; diff --git a/tests/library/inspector/recorder-api.spec.ts b/tests/library/inspector/recorder-api.spec.ts index f59d24a7e7fd5..568c09df57663 100644 --- a/tests/library/inspector/recorder-api.spec.ts +++ b/tests/library/inspector/recorder-api.spec.ts @@ -17,7 +17,7 @@ import { test, expect } from './inspectorTest'; import type { Page } from '@playwright/test'; -import type * as actions from '@recorder/actions'; +import type * as actions from '@isomorphic/codegen/actions'; class RecorderLog { actions: (actions.ActionInContext & { code: string })[] = []; diff --git a/utils/build/build.js b/utils/build/build.js index 59e147d405d48..2c0fbf6502b93 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -985,7 +985,7 @@ onChanges.push({ 'packages/playwright-core/src/server/chromium/protocol.d.ts', ], mustExist: [ - 'packages/playwright-core/lib/server/deviceDescriptorsSource.json', + 'packages/isomorphic/deviceDescriptorsSource.json', ], script: 'utils/generate_types/index.js', }); diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index 79c4ff73630c3..3dc1146727c96 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -90,7 +90,7 @@ async function run() { // Update device descriptors { - const devicesDescriptorsSourceFile = path.join(PROJECT_DIR, 'packages', 'playwright-core', 'src', 'server', 'deviceDescriptorsSource.json') + const devicesDescriptorsSourceFile = path.join(PROJECT_DIR, 'packages', 'isomorphic', 'deviceDescriptorsSource.json') const devicesDescriptors = require(devicesDescriptorsSourceFile) for (const deviceName of Object.keys(devicesDescriptors)) { switch (devicesDescriptors[deviceName].defaultBrowserType) { diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index c0cf8c46b9460..a2b61decfc4cd 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -16,7 +16,7 @@ // @ts-check const path = require('path'); -const devices = require('../../packages/playwright-core/lib/server/deviceDescriptorsSource.json'); +const devices = require('../../packages/isomorphic/deviceDescriptorsSource.json'); const md = require('../markdown'); const docs = require('../doclint/documentation'); const PROJECT_DIR = path.join(__dirname, '..', '..'); From 45897ce666dc8a551f1f7e1879890eb2d6e72bfb Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 16:05:41 +0200 Subject: [PATCH 05/10] feat(mcp): surface non-2xx navigation status (#41589) --- .../src/tools/backend/response.ts | 3 +++ .../playwright-core/src/tools/backend/tab.ts | 13 +++++++-- tests/mcp/core.spec.ts | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts index 388fb04d80673..431d4c28ab127 100644 --- a/packages/playwright-core/src/tools/backend/response.ts +++ b/packages/playwright-core/src/tools/backend/response.ts @@ -326,6 +326,9 @@ export function renderTabMarkdown(tab: TabHeader): string[] { lines.push(`- Page Title: ${tab.title}`); if (tab.crashed) lines.push(`- Page status: crashed`); + const status = tab.mainDocumentStatus; + if (status && (status.status < 200 || status.status >= 300)) + lines.push(`- HTTP status: ${status.status}${status.statusText ? ' ' + status.statusText : ''}`); if (tab.console.errors || tab.console.warnings) lines.push(`- Console: ${tab.console.errors} errors, ${tab.console.warnings} warnings`); return lines; diff --git a/packages/playwright-core/src/tools/backend/tab.ts b/packages/playwright-core/src/tools/backend/tab.ts index 09df4cba187fb..676f5da4e9040 100644 --- a/packages/playwright-core/src/tools/backend/tab.ts +++ b/packages/playwright-core/src/tools/backend/tab.ts @@ -77,6 +77,7 @@ export type TabHeader = { url: string; current: boolean; crashed: boolean; + mainDocumentStatus?: { status: number, statusText: string }; console: { total: number, warnings: number, errors: number }; }; @@ -93,6 +94,7 @@ export class Tab extends EventEmitter { private _lastHeader: TabHeader = { title: 'about:blank', url: 'about:blank', current: false, crashed: false, console: { total: 0, warnings: 0, errors: 0 } }; private _downloads: Download[] = []; private _requests: playwright.Request[] = []; + private _mainDocumentStatus: { status: number, statusText: string } | undefined; private _onPageClose: (tab: Tab) => void; crashed = false; private _modalStates: ModalState[] = []; @@ -219,6 +221,7 @@ export class Tab extends EventEmitter { private _clearCollectedArtifacts() { this._downloads.length = 0; this._requests.length = 0; + this._mainDocumentStatus = undefined; this._recentEventEntries.length = 0; this._resetLogs(); } @@ -238,9 +241,12 @@ export class Tab extends EventEmitter { } private _handleResponse(response: playwright.Response) { - const timing = response.request().timing(); + const request = response.request(); + if (request.isNavigationRequest() && response.frame() === this.page.mainFrame() && !request.redirectedTo()) + this._mainDocumentStatus = { status: response.status(), statusText: response.statusText() }; + const timing = request.timing(); const wallTime = timing.responseStart + timing.startTime; - this._addLogEntry({ type: 'request', wallTime, request: response.request() }); + this._addLogEntry({ type: 'request', wallTime, request }); } private _handleRequestFailed(request: playwright.Request) { @@ -283,6 +289,7 @@ export class Tab extends EventEmitter { url: this.page.url(), current: this.isCurrentTab(), crashed: this.crashed, + mainDocumentStatus: this._mainDocumentStatus, console: consoleCounts, }; @@ -582,6 +589,8 @@ function tabHeaderEquals(a: TabHeader, b: TabHeader): boolean { a.url === b.url && a.current === b.current && a.crashed === b.crashed && + a.mainDocumentStatus?.status === b.mainDocumentStatus?.status && + a.mainDocumentStatus?.statusText === b.mainDocumentStatus?.statusText && a.console.errors === b.console.errors && a.console.warnings === b.console.warnings && a.console.total === b.console.total; diff --git a/tests/mcp/core.spec.ts b/tests/mcp/core.spec.ts index 8e6040a3f5781..8ef418f3eb10e 100644 --- a/tests/mcp/core.spec.ts +++ b/tests/mcp/core.spec.ts @@ -30,6 +30,33 @@ test('browser_navigate', async ({ client, server }) => { }); }); +test('browser_navigate surfaces non-2xx HTTP status', async ({ client, server }) => { + server.setRoute('/locked', (req, res) => { + res.writeHead(402, { 'Content-Type': 'text/html' }); + res.end('Payment RequiredPay up'); + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.PREFIX + '/locked' }, + })).toHaveResponse({ + page: expect.stringContaining(`- HTTP status: 402 Payment Required`), + }); + + // A redirect to a 2xx page must not carry a status line: the intermediate + // 302 hop must not leak, and the final 2xx landing renders nothing. + server.setRoute('/redirect', (req, res) => { + res.writeHead(302, { location: server.HELLO_WORLD }); + res.end(); + }); + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.PREFIX + '/redirect' }, + })).not.toHaveResponse({ + page: expect.stringContaining('HTTP status'), + }); +}); + test('browser_navigate blocks file:// URLs by default', async ({ client }) => { expect(await client.callTool({ name: 'browser_navigate', From 2030ea99a2eeeb33e5928df8f9dfa73a8e798fe1 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:06:33 +0200 Subject: [PATCH 06/10] feat(webkit): roll to r2322 (#41584) Signed-off-by: Simon Knott Co-authored-by: microsoft-playwright-automation[bot] <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Co-authored-by: Simon Knott --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 512a62a7d73cf..d6cb3095de8f7 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -45,7 +45,7 @@ }, { "name": "webkit", - "revision": "2321", + "revision": "2322", "installByDefault": true, "revisionOverrides": { "mac14": "2251", From 8b507a2ac21260a24781fedc176311ae3acc8a5c Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 16:08:19 +0200 Subject: [PATCH 07/10] fix(triage): keep the GitHub token out of the untrusted shell (#41596) --- .github/workflows/triage.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 0436508a7c3cd..08f8584c688d7 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -10,11 +10,6 @@ on: description: "Issue number to triage" required: true -# One triage at a time across the whole repo; let an in-flight run finish. -concurrency: - group: triage - cancel-in-progress: false - permissions: {} jobs: @@ -45,7 +40,7 @@ jobs: - name: Triage issue with Copilot CLI id: triage env: - GH_TOKEN: ${{ github.token }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | mkdir -p output @@ -60,10 +55,11 @@ jobs: --allow-all-tools \ --allow-all-paths \ --no-ask-user \ + --enable-all-github-mcp-tools \ + --share=output/copilot-session.md \ --model claude-opus-4.8 \ --max-ai-credits 500 \ - -p "$PROMPT" \ - 2>&1 | tee "output/copilot.log" + -p "$PROMPT" if [ -s output/triage.md ]; then echo "has_draft=true" >> "$GITHUB_OUTPUT" @@ -71,15 +67,13 @@ jobs: echo "has_draft=false" >> "$GITHUB_OUTPUT" fi - - name: Add log to job summary + - name: Add session transcript to job summary if: always() run: | { - echo "## Triage run log (issue $ISSUE)" + echo "## Triage session transcript (issue $ISSUE)" echo '' - echo '```' - cat "output/copilot.log" 2>/dev/null || echo "(no log)" - echo '```' + cat "output/copilot-session.md" 2>/dev/null || echo "(no transcript)" } >> "$GITHUB_STEP_SUMMARY" - name: Upload output @@ -87,7 +81,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: triage-${{ github.event.issue.number || inputs.issue }} - path: output/** + path: output/triage.md if-no-files-found: warn post: From 49029121ab9cdd04a41a820c66024a6294dc95f4 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 2 Jul 2026 15:26:48 +0100 Subject: [PATCH 08/10] chore: move stackTrace helpers from isomorphic to utils (#41591) --- packages/isomorphic/index.ts | 1 - packages/isomorphic/manualPromise.ts | 4 +- packages/isomorphic/trace/traceModel.ts | 3 +- packages/isomorphic/trace/traceUtils.ts | 2 +- .../playwright-core/src/browserServerImpl.ts | 2 +- packages/playwright-core/src/client/DEPS.list | 1 + .../src/client/browserContext.ts | 2 +- .../src/client/channelOwner.ts | 2 +- .../src/client/clientStackTrace.ts | 8 ++-- .../playwright-core/src/client/connection.ts | 2 +- .../playwright-core/src/client/network.ts | 2 +- packages/playwright-core/src/client/waiter.ts | 2 +- packages/playwright-core/src/inprocess.ts | 3 +- packages/playwright-core/src/outofprocess.ts | 3 +- .../src/server/browserContext.ts | 2 +- .../src/server/chromium/crExecutionContext.ts | 2 +- .../src/server/chromium/crPage.ts | 2 +- .../src/server/chromium/crProtocolHelper.ts | 2 +- .../src/server/dispatchers/dispatcher.ts | 2 +- .../src/server/firefox/ffExecutionContext.ts | 2 +- .../src/server/firefox/ffPage.ts | 2 +- .../playwright-core/src/server/launchApp.ts | 2 +- .../playwright-core/src/server/network.ts | 2 +- .../src/server/protocolError.ts | 2 +- .../socksClientCertificatesInterceptor.ts | 2 +- .../src/server/webkit/webview/wvPage.ts | 2 +- .../src/server/webkit/wkPage.ts | 2 +- packages/playwright/src/common/fixtures.ts | 2 +- packages/playwright/src/common/process.ts | 2 +- packages/playwright/src/errorContext.ts | 5 +-- packages/playwright/src/matchers/expect.ts | 14 +++---- .../playwright/src/matchers/matcherHint.ts | 4 +- packages/playwright/src/program.ts | 2 +- packages/playwright/src/reporters/base.ts | 4 +- packages/playwright/src/util.ts | 4 +- .../playwright/src/worker/fixtureRunner.ts | 2 +- packages/playwright/src/worker/testInfo.ts | 6 +-- packages/playwright/src/worker/testTracing.ts | 6 +-- packages/playwright/src/worker/workerMain.ts | 2 +- packages/trace-viewer/src/ui/sourceTab.tsx | 2 +- packages/trace-viewer/src/ui/stackTrace.tsx | 2 +- packages/trace/src/trace.ts | 8 +++- packages/utils/index.ts | 1 + packages/{isomorphic => utils}/stackTrace.ts | 40 +++++++++---------- tests/config/utils.ts | 3 +- tests/library/tracing.spec.ts | 3 +- 46 files changed, 85 insertions(+), 90 deletions(-) rename packages/{isomorphic => utils}/stackTrace.ts (85%) diff --git a/packages/isomorphic/index.ts b/packages/isomorphic/index.ts index f645b878b780e..4a623a16c3e12 100644 --- a/packages/isomorphic/index.ts +++ b/packages/isomorphic/index.ts @@ -29,7 +29,6 @@ export * from './protocolFormatter'; export * from './protocolMetainfo'; export * from './rtti'; export * from './semaphore'; -export * from './stackTrace'; export * from './stringUtils'; export * from './formatUtils'; export * from './time'; diff --git a/packages/isomorphic/manualPromise.ts b/packages/isomorphic/manualPromise.ts index bb16ada8e217d..95d9e02c5b579 100644 --- a/packages/isomorphic/manualPromise.ts +++ b/packages/isomorphic/manualPromise.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { captureRawStack } from './stackTrace'; - export class ManualPromise extends Promise { private _resolve!: (t: T) => void; private _reject!: (e: Error) => void; @@ -96,7 +94,7 @@ export class LongStandingScope { private async _race(promises: Promise[], safe: boolean, defaultValue?: any): Promise { const terminatePromise = new ManualPromise(); - const frames = captureRawStack(); + const frames = (new Error().stack || '').split('\n'); if (this._terminateError) terminatePromise.resolve(this._terminateError); if (this._closeError) diff --git a/packages/isomorphic/trace/traceModel.ts b/packages/isomorphic/trace/traceModel.ts index 66da40de4f186..70c236cd710c7 100644 --- a/packages/isomorphic/trace/traceModel.ts +++ b/packages/isomorphic/trace/traceModel.ts @@ -16,7 +16,6 @@ import { getActionGroup, renderTitleForCall } from '../protocolFormatter'; -import type { StackFrame } from '../stackTrace'; import type { Language } from '../locatorGenerators'; import type { ResourceSnapshot } from '@trace/snapshot'; import type * as trace from '@trace/trace'; @@ -57,7 +56,7 @@ export type ActionTreeItem = { export type ErrorDescription = { action?: ActionTraceEventInContext; - stack?: StackFrame[]; + stack?: trace.StackFrame[]; message: string; }; diff --git a/packages/isomorphic/trace/traceUtils.ts b/packages/isomorphic/trace/traceUtils.ts index 9873a63950f7b..d9a2c1b8ce4c3 100644 --- a/packages/isomorphic/trace/traceUtils.ts +++ b/packages/isomorphic/trace/traceUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { StackFrame } from '../stackTrace'; +import type { StackFrame } from '@trace/trace'; import type { ClientSideCallMetadata } from '@protocol/structs'; export type SerializedStackFrame = [number, number, number, string]; diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index d0776eae0a204..7144ac3f25ba9 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -18,7 +18,7 @@ import EventEmitter from 'events'; import { createGuid } from '@utils/crypto'; import { isUnderTest } from '@utils/debug'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT } from '@isomorphic/time'; import { PlaywrightServer } from './remote/playwrightServer'; import { helper } from './server/helper'; diff --git a/packages/playwright-core/src/client/DEPS.list b/packages/playwright-core/src/client/DEPS.list index 94cffea59967c..6fd9f02f26fdc 100644 --- a/packages/playwright-core/src/client/DEPS.list +++ b/packages/playwright-core/src/client/DEPS.list @@ -4,5 +4,6 @@ @utils/crypto.ts @utils/debug.ts @utils/debugLogger.ts +@utils/stackTrace.ts @utils/zones.ts node_modules/colors/safe diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index a914b800dbc89..70ada2e326876 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -21,7 +21,7 @@ import path from 'path'; import { headersObjectToArray } from '@isomorphic/headers'; import { urlMatchesEqual } from '@isomorphic/urlMatch'; import { isRegExp, isString } from '@isomorphic/rtti'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { Browser } from './browser'; import { CDPSession } from './cdpSession'; import { ChannelOwner } from './channelOwner'; diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index 0c860aabc0cf8..66571bd35b3e6 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -15,7 +15,7 @@ */ import { getMetainfo } from '@isomorphic/protocolMetainfo'; -import { showInternalStackFrames, stringifyStackFrames } from '@isomorphic/stackTrace'; +import { showInternalStackFrames, stringifyStackFrames } from '@utils/stackTrace'; import { isUnderTest } from '@utils/debug'; import { debugLogger } from '@utils/debugLogger'; import { currentZone } from '@utils/zones'; diff --git a/packages/playwright-core/src/client/clientStackTrace.ts b/packages/playwright-core/src/client/clientStackTrace.ts index 7e55614ec1c95..8c66d689aca4a 100644 --- a/packages/playwright-core/src/client/clientStackTrace.ts +++ b/packages/playwright-core/src/client/clientStackTrace.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import path from 'path'; +import { captureRawStack, coreDir, filterStackFile, parseStackFrame } from '@utils/stackTrace'; -import { captureRawStack, coreDir, filterStackFile, parseStackFrame } from '@isomorphic/stackTrace'; - -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@utils/stackTrace'; export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: string } { const stack = captureRawStack(); @@ -30,7 +28,7 @@ export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: str isPlaywrightLibrary: boolean; }; let parsedFrames = stack.map(line => { - const frame = parseStackFrame(line, path.sep); + const frame = parseStackFrame(line); if (!frame || !frame.file) return null; const isPlaywrightLibrary = !!playwrightCoreDir && frame.file.startsWith(playwrightCoreDir); diff --git a/packages/playwright-core/src/client/connection.ts b/packages/playwright-core/src/client/connection.ts index 064195471fba7..2e3c1a33a0b27 100644 --- a/packages/playwright-core/src/client/connection.ts +++ b/packages/playwright-core/src/client/connection.ts @@ -15,7 +15,7 @@ */ import colors from 'colors/safe'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { isUnderTest } from '@utils/debug'; import { debugLogger } from '@utils/debugLogger'; import { emptyZone } from '@utils/zones'; diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 6986160ee7aa2..7c33d3a8b6f8a 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -22,7 +22,7 @@ import { resolveGlobToRegexPattern, serializeURLMatch, urlMatches } from '@isomo import { LongStandingScope, ManualPromise } from '@isomorphic/manualPromise'; import { MultiMap } from '@isomorphic/multimap'; import { isString } from '@isomorphic/rtti'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { getMimeTypeForPath } from '@isomorphic/mimeType'; import { currentZone } from '@utils/zones'; import { Worker } from './worker'; diff --git a/packages/playwright-core/src/client/waiter.ts b/packages/playwright-core/src/client/waiter.ts index aa82f87bad72b..9d08ccdf434f4 100644 --- a/packages/playwright-core/src/client/waiter.ts +++ b/packages/playwright-core/src/client/waiter.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { createGuid } from '@utils/crypto'; import { currentZone } from '@utils/zones'; import { AbortError, TimeoutError } from './errors'; diff --git a/packages/playwright-core/src/inprocess.ts b/packages/playwright-core/src/inprocess.ts index 8701615340736..2c342566a8ac9 100644 --- a/packages/playwright-core/src/inprocess.ts +++ b/packages/playwright-core/src/inprocess.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setCoreDir, setShowInternalStackFrames } from '@isomorphic/stackTrace'; +import { setCoreDir } from '@utils/stackTrace'; import { AndroidServerLauncherImpl } from './androidServerImpl'; import { BrowserServerLauncherImpl } from './browserServerImpl'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server'; @@ -27,7 +27,6 @@ import type { Language } from '@isomorphic/locatorGenerators'; export function createInProcessPlaywright(): PlaywrightAPI { const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript', isClientCollocatedWithServer: true }); setCoreDir(packageRoot); - setShowInternalStackFrames(!!process.env.PWDEBUGIMPL); const clientConnection = new Connection(); clientConnection.useRawBuffers(); const dispatcherConnection = new DispatcherConnection(true /* in process */); diff --git a/packages/playwright-core/src/outofprocess.ts b/packages/playwright-core/src/outofprocess.ts index 6ecbd3d520dae..f5143706c5c40 100644 --- a/packages/playwright-core/src/outofprocess.ts +++ b/packages/playwright-core/src/outofprocess.ts @@ -19,7 +19,7 @@ import path from 'path'; import { PipeTransport } from '@utils/pipeTransport'; import { ManualPromise } from '@isomorphic/manualPromise'; -import { setCoreDir, setShowInternalStackFrames } from '@isomorphic/stackTrace'; +import { setCoreDir } from '@utils/stackTrace'; import { Connection } from './client/connection'; import { packageRoot } from './package'; @@ -52,7 +52,6 @@ class PlaywrightClient { this._driverProcess.stderr!.on('data', data => process.stderr.write(data)); setCoreDir(packageRoot); - setShowInternalStackFrames(!!process.env.PWDEBUGIMPL); const connection = new Connection(); const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!); connection.onmessage = message => transport.send(JSON.stringify(message)); diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index ce354c523e19c..96d5c2154c3d0 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -17,7 +17,7 @@ import fs from 'fs'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { debugMode, isUnderTest } from '@utils/debug'; import { Clock } from './clock'; import { Credentials } from './credentials'; diff --git a/packages/playwright-core/src/server/chromium/crExecutionContext.ts b/packages/playwright-core/src/server/chromium/crExecutionContext.ts index 2b2a648ea8801..4d31aa7e25f7c 100644 --- a/packages/playwright-core/src/server/chromium/crExecutionContext.ts +++ b/packages/playwright-core/src/server/chromium/crExecutionContext.ts @@ -16,7 +16,7 @@ */ import { assert } from '@isomorphic/assert'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { parseEvaluationResultValue } from '@isomorphic/utilityScriptSerializers'; import { getExceptionMessage, releaseObject } from './crProtocolHelper'; import * as js from '../javascript'; diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 5c37d8eb8efa4..951930b8fb5bc 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -16,7 +16,7 @@ */ import { assert } from '@isomorphic/assert'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { eventsHelper } from '@utils/eventsHelper'; import * as dialog from '../dialog'; import * as dom from '../dom'; diff --git a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts index deb0158f5f2fe..a184c74a61bf6 100644 --- a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts +++ b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts @@ -17,7 +17,7 @@ import fs from 'fs'; -import { splitErrorMessage } from '@isomorphic/stackTrace'; +import { splitErrorMessage } from '@utils/stackTrace'; import { mkdirIfNeeded } from '@utils/fileUtils'; import type { CRSession } from './crConnection'; diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index d36384a4b685d..ba14ed9c14b70 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -21,7 +21,7 @@ import { eventsHelper } from '@utils/eventsHelper'; import { isUnderTest } from '@utils/debug'; import { assert } from '@isomorphic/assert'; import { monotonicTime } from '@isomorphic/time'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { ValidationError, createMetadataValidator, createWaitInfoValidator, findValidator, maybeFindValidator } from '../../protocol/validator'; import { AbortError, TargetClosedError, isTargetClosedError, serializeError } from '../errors'; import { createRootSdkObject, SdkObject } from '../instrumentation'; diff --git a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts index 8d690116cc712..b6bcc0a582b10 100644 --- a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts +++ b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts @@ -16,7 +16,7 @@ */ import { assert } from '@isomorphic/assert'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { parseEvaluationResultValue } from '@isomorphic/utilityScriptSerializers'; import * as js from '../javascript'; import * as dom from '../dom'; diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 4e3eb90df3b52..5edd22b4b3cea 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -16,7 +16,7 @@ */ import { assert } from '@isomorphic/assert'; -import { splitErrorMessage } from '@isomorphic/stackTrace'; +import { splitErrorMessage } from '@utils/stackTrace'; import { eventsHelper } from '@utils/eventsHelper'; import * as dialog from '../dialog'; import * as dom from '../dom'; diff --git a/packages/playwright-core/src/server/launchApp.ts b/packages/playwright-core/src/server/launchApp.ts index 355af0ff4b0dd..c230efb202845 100644 --- a/packages/playwright-core/src/server/launchApp.ts +++ b/packages/playwright-core/src/server/launchApp.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import path from 'path'; import { isUnderTest } from '@utils/debug'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { wrapInASCIIBox } from '@utils/ascii'; import { libPath } from '../package'; import { buildPlaywrightCLICommand, findChromiumChannelBestEffort } from './registry'; diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index e44a4e6c17325..8de38cf756a67 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -16,7 +16,7 @@ import { ManualPromise } from '@isomorphic/manualPromise'; import { assert } from '@isomorphic/assert'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { isProtocolError } from './protocolError'; import { BrowserContext } from './browserContext'; import { APIRequestContext } from './fetch'; diff --git a/packages/playwright-core/src/server/protocolError.ts b/packages/playwright-core/src/server/protocolError.ts index 097ef1bde5cf5..4455e69c1ce09 100644 --- a/packages/playwright-core/src/server/protocolError.ts +++ b/packages/playwright-core/src/server/protocolError.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; export class ProtocolError extends Error { type: 'error' | 'closed' | 'crashed'; diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index b5b050d45364c..fe39c303c8c56 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -26,7 +26,7 @@ import { debugLogger } from '@utils/debugLogger'; import { createSocket } from '@utils/happyEyeballs'; import { escapeHTML } from '@isomorphic/stringUtils'; import { generateSelfSignedCertificate } from '@utils/crypto'; -import { rewriteErrorMessage } from '@isomorphic/stackTrace'; +import { rewriteErrorMessage } from '@utils/stackTrace'; import { createProxyAgent } from '@utils/network'; import { verifyClientCertificates } from './browserContext'; import type * as types from './types'; diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index b6f2f0cf98681..3552a0bd6553f 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -20,7 +20,7 @@ import jpegjs from 'jpeg-js'; import { assert } from '@isomorphic/assert'; import { headersArrayToObject, headersObjectToArray } from '@isomorphic/headers'; import { ManualPromise } from '@isomorphic/manualPromise'; -import { splitErrorMessage } from '@isomorphic/stackTrace'; +import { splitErrorMessage } from '@utils/stackTrace'; import { debugLogger } from '@utils/debugLogger'; import { eventsHelper } from '@utils/eventsHelper'; import * as dialog from '../../dialog'; diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index f744f1cd634b8..dcc17c8b14f25 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -16,7 +16,7 @@ */ import { headersArrayToObject, headersObjectToArray } from '@isomorphic/headers'; -import { splitErrorMessage } from '@isomorphic/stackTrace'; +import { splitErrorMessage } from '@utils/stackTrace'; import { eventsHelper } from '@utils/eventsHelper'; import { hostPlatform } from '@utils/hostPlatform'; import { assert } from '@isomorphic/assert'; diff --git a/packages/playwright/src/common/fixtures.ts b/packages/playwright/src/common/fixtures.ts index 64c95426fbaba..0e69271ab53e8 100644 --- a/packages/playwright/src/common/fixtures.ts +++ b/packages/playwright/src/common/fixtures.ts @@ -15,7 +15,7 @@ */ import crypto from 'crypto'; -import { filterStackFile } from '@isomorphic/stackTrace'; +import { filterStackFile } from '@utils/stackTrace'; import { formatLocation } from '../util'; diff --git a/packages/playwright/src/common/process.ts b/packages/playwright/src/common/process.ts index ce722bdb234be..57c1e99c5d106 100644 --- a/packages/playwright/src/common/process.ts +++ b/packages/playwright/src/common/process.ts @@ -19,7 +19,7 @@ import 'playwright-core/lib/bootstrap'; import { ManualPromise } from '@isomorphic/manualPromise'; import { setTimeOrigin } from '@isomorphic/time'; import { startProfiling, stopProfiling } from '@utils/profiler'; -import { setBoxedStackPrefixes } from '@isomorphic/stackTrace'; +import { setBoxedStackPrefixes } from '@utils/stackTrace'; import { packageRoot } from '../package'; import { serializeError } from '../util'; diff --git a/packages/playwright/src/errorContext.ts b/packages/playwright/src/errorContext.ts index 87898f3ab5270..3662e8faf4d7f 100644 --- a/packages/playwright/src/errorContext.ts +++ b/packages/playwright/src/errorContext.ts @@ -15,9 +15,8 @@ */ import fs from 'fs'; -import path from 'path'; -import { parseErrorStack } from '@isomorphic/stackTrace'; +import { parseErrorStack } from '@utils/stackTrace'; import { stripAnsiEscapes } from '@isomorphic/stringUtils'; import { relativeFilePath } from './util'; @@ -105,7 +104,7 @@ function buildCodeFrame(error: TestInfoError, testLocation: { file: string; line if (!stack) return undefined; - const parsed = parseErrorStack(stack, path.sep); + const parsed = parseErrorStack(stack); const errorLocation = parsed.location; if (!errorLocation) return undefined; diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 79255f2227b4c..a027b71bc0949 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import path from 'path'; - -import { parseStackFrame, captureRawStack } from '@isomorphic/stackTrace'; +import { parseStackFrame, captureRawStack } from '@utils/stackTrace'; import { escapeWithQuotes, isString } from '@isomorphic/stringUtils'; import { pollAgainstDeadline } from '@isomorphic/timeoutRunner'; import { currentZone } from '@utils/zones'; @@ -88,7 +86,7 @@ import type { MatcherContext, MatchersObject, RawMatcherFn } from './expectLibra import type { MatcherAttachment, MatcherResult } from './matcherHint'; import type { ExpectMatcherStateInternal } from './matchers'; import type { Expect } from '../../types/test'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@utils/stackTrace'; interface ExpectStep { complete(result: { @@ -115,7 +113,7 @@ export interface ExpectTestInfo { export type ExpectConfig = { testInfo: ExpectTestInfo | null; - filteredStackTrace: (rawStack: string[], pathSeparator: string) => StackFrame[]; + filteredStackTrace: (rawStack: string[]) => StackFrame[]; ignoreSnapshots: boolean; updateSnapshots: 'all' | 'changed' | 'missing' | 'none'; timeout?: number; @@ -143,8 +141,8 @@ export type ExpectConfig = { toPass?: { timeout?: number; intervals?: number[] }; }; -function unfilteredStackTrace(rawStack: string[], pathSeparator: string): StackFrame[] { - return rawStack.map(frame => parseStackFrame(frame, pathSeparator)).filter(f => !!f); +function unfilteredStackTrace(rawStack: string[]): StackFrame[] { + return rawStack.map(frame => parseStackFrame(frame)).filter(f => !!f); } let _expectConfig: ExpectConfig = { testInfo: null, filteredStackTrace: unfilteredStackTrace, ignoreSnapshots: false, updateSnapshots: 'missing' }; @@ -339,7 +337,7 @@ function callMatcherAsStep(matcherName: string, info: ExpectMetaInfo, actual: un // This looks like it is unnecessary, but it isn't - we need to filter // out all the frames that belong to the test runner from caught runtime errors. - const stackFrames = expectConfig().filteredStackTrace(captureRawStack(), path.sep); + const stackFrames = expectConfig().filteredStackTrace(captureRawStack()); const stepData = { category: 'expect' as const, apiName, diff --git a/packages/playwright/src/matchers/matcherHint.ts b/packages/playwright/src/matchers/matcherHint.ts index 4799c9962ea9f..94131e2073737 100644 --- a/packages/playwright/src/matchers/matcherHint.ts +++ b/packages/playwright/src/matchers/matcherHint.ts @@ -16,9 +16,9 @@ import util from 'util'; -import { stringifyStackFrames } from '@isomorphic/stackTrace'; +import { stringifyStackFrames } from '@utils/stackTrace'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@utils/stackTrace'; export type MatcherAttachment = { name: string; contentType: string; path?: string; body?: string | Buffer }; diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 109aa1e54ae30..8449c12090916 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -20,7 +20,7 @@ import 'playwright-core/lib/bootstrap'; import { libCli, tools } from 'playwright-core/lib/coreBundle'; import { program } from 'commander'; -import { setBoxedStackPrefixes } from '@isomorphic/stackTrace'; +import { setBoxedStackPrefixes } from '@utils/stackTrace'; import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher'; import { builtInReporters, config, configLoader } from './common'; import { runTests, clearCache, runTestServerAction } from './cli/testActions'; diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index cc1886d7f98fc..2cfd7ac60d2b3 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -20,7 +20,7 @@ import { Writable } from 'stream'; import realColors from 'colors/safe'; import { noColors } from '@isomorphic/colors'; import { msToString } from '@isomorphic/formatUtils'; -import { parseErrorStack } from '@isomorphic/stackTrace'; +import { parseErrorStack } from '@utils/stackTrace'; import { getPackageManagerExecCommand } from '@utils/env'; import { fitToWidth } from '@utils/stringWidth'; @@ -631,7 +631,7 @@ export function prepareErrorStack(stack: string): { stackLines: string[]; location?: Location; } { - return parseErrorStack(stack, path.sep); + return parseErrorStack(stack); } function resolveFromEnv(name: string): string | undefined { diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index 6f3cf17348995..b5731d0bef151 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -24,7 +24,7 @@ import { minimatch } from 'minimatch'; import { calculateSha1 } from '@utils/crypto'; import { sanitizeForFilePath } from '@utils/fileUtils'; import { isRegExp } from '@isomorphic/rtti'; -import { stringifyStackFrames, filteredStackTrace } from '@isomorphic/stackTrace'; +import { stringifyStackFrames, filteredStackTrace } from '@utils/stackTrace'; import { ansiRegex, isString, stripAnsiEscapes } from '@isomorphic/stringUtils'; import type { Location } from './../types/testReporter'; @@ -37,7 +37,7 @@ export function filterStackTrace(e: Error): TestInfoError { if (process.env.PWDEBUGIMPL) return { message: name + e.message, stack: e.stack || '', cause }; - const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || [], path.sep)); + const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || [])); return { message: name + e.message, stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`, diff --git a/packages/playwright/src/worker/fixtureRunner.ts b/packages/playwright/src/worker/fixtureRunner.ts index b13fd9feaf499..9722b929135cf 100644 --- a/packages/playwright/src/worker/fixtureRunner.ts +++ b/packages/playwright/src/worker/fixtureRunner.ts @@ -16,7 +16,7 @@ import { ManualPromise } from '@isomorphic/manualPromise'; import { escapeWithQuotes } from '@isomorphic/stringUtils'; -import { filterStackFile } from '@isomorphic/stackTrace'; +import { filterStackFile } from '@utils/stackTrace'; import { fixtures } from '../common'; import { formatLocation } from '../util'; diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 546c63cfd9a2a..7e20abe21e0ba 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import path from 'path'; import { ManualPromise } from '@isomorphic/manualPromise'; -import { captureRawStack, stringifyStackFrames, filteredStackTrace } from '@isomorphic/stackTrace'; +import { captureRawStack, stringifyStackFrames, filteredStackTrace } from '@utils/stackTrace'; import { escapeWithQuotes } from '@isomorphic/stringUtils'; import { monotonicTime } from '@isomorphic/time'; import { createGuid } from '@utils/crypto'; @@ -35,7 +35,7 @@ import type { RunnableDescription } from './timeoutManager'; import type { FullProject, TestInfo, TestInfoError, TestStatus, TestStepInfo, TestAnnotation } from '../../types/test'; import type { FullConfig, Location } from '../../types/testReporter'; import type { config as commonConfig, FullConfigInternal, test as testNs } from '../common'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@utils/stackTrace'; export type TestStepCategory = 'expect' | 'fixture' | 'hook' | 'pw:api' | 'test.step' | 'test.attach'; @@ -295,7 +295,7 @@ export class TestInfoImpl implements TestInfo { parentStep = this._parentStep(); } - const filteredStack = filteredStackTrace(captureRawStack(), path.sep); + const filteredStack = filteredStackTrace(captureRawStack()); let boxedStack = parentStep?.boxedStack; let location = data.location; if (!boxedStack && data.box) { diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 049eae8bfbfd9..e71c2354091e5 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -24,11 +24,11 @@ import { monotonicTime } from '@isomorphic/time'; import { calculateSha1, createGuid } from '@utils/crypto'; import { SerializedFS } from '@utils/serializedFS'; import { getPlaywrightVersion } from 'playwright-core/lib/coreBundle'; -import { filteredStackTrace } from '@isomorphic/stackTrace'; +import { filteredStackTrace } from '@utils/stackTrace'; import type { TestStepCategory, TestInfoImpl } from './testInfo'; import type { PlaywrightWorkerOptions, TestInfo, TestInfoError, TraceMode } from '../../types/test'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@utils/stackTrace'; import type * as trace from '@trace/trace'; import type EventEmitter from 'events'; @@ -250,7 +250,7 @@ export class TestTracing { appendForError(error: TestInfoError) { const rawStack = error.stack?.split('\n') || []; - const stack = rawStack ? filteredStackTrace(rawStack, path.sep) : []; + const stack = rawStack ? filteredStackTrace(rawStack) : []; this._appendTraceEvent({ type: 'error', message: this._formatError(error), diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index 162f062004bca..efb16b9d71d7e 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -18,7 +18,7 @@ import colors from 'colors/safe'; import { ManualPromise } from '@isomorphic/manualPromise'; import { removeFolders } from '@utils/fileUtils'; import { gracefullyCloseAll } from '@utils/processLauncher'; -import { filteredStackTrace } from '@isomorphic/stackTrace'; +import { filteredStackTrace } from '@utils/stackTrace'; import { configLoader, fixtures, ipc, poolBuilder, ProcessRunner, suiteUtils, testLoader } from '../common'; import * as globals from '../globals'; diff --git a/packages/trace-viewer/src/ui/sourceTab.tsx b/packages/trace-viewer/src/ui/sourceTab.tsx index a6c9ad9531b2f..0221963c8ddd1 100644 --- a/packages/trace-viewer/src/ui/sourceTab.tsx +++ b/packages/trace-viewer/src/ui/sourceTab.tsx @@ -22,7 +22,7 @@ import { StackTraceView } from './stackTrace'; import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper'; import type { SourceHighlight } from '@web/components/codeMirrorWrapper'; import type { SourceLocation, SourceModel } from '@isomorphic/trace/traceModel'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@trace/trace'; import { CopyToClipboard } from './copyToClipboard'; import { ToolbarButton } from '@web/components/toolbarButton'; import { Toolbar } from '@web/components/toolbar'; diff --git a/packages/trace-viewer/src/ui/stackTrace.tsx b/packages/trace-viewer/src/ui/stackTrace.tsx index d03879631c296..f479a88a0855e 100644 --- a/packages/trace-viewer/src/ui/stackTrace.tsx +++ b/packages/trace-viewer/src/ui/stackTrace.tsx @@ -17,7 +17,7 @@ import * as React from 'react'; import './stackTrace.css'; import { ListView } from '@web/components/listView'; -import type { StackFrame } from '@isomorphic/stackTrace'; +import type { StackFrame } from '@trace/trace'; const StackFrameListView = ListView; diff --git a/packages/trace/src/trace.ts b/packages/trace/src/trace.ts index 6cdfbdfc3b70f..e6cd2187765f2 100644 --- a/packages/trace/src/trace.ts +++ b/packages/trace/src/trace.ts @@ -16,11 +16,17 @@ import type { FrameSnapshot, ResourceSnapshot } from './snapshot'; import type { Language } from '@isomorphic/locatorGenerators'; -import type { StackFrame } from '@isomorphic/stackTrace'; import type { Point } from '@isomorphic/types'; export type Size = { width: number, height: number }; +export type StackFrame = { + file: string, + line: number, + column: number, + function?: string, +}; + export type SerializedValue = { n?: number, b?: boolean, diff --git a/packages/utils/index.ts b/packages/utils/index.ts index d8de918d859e9..57ea9146b9587 100644 --- a/packages/utils/index.ts +++ b/packages/utils/index.ts @@ -31,6 +31,7 @@ export * from './profiler'; export * from './serializedFS'; export * from './socksProxy'; export * from './spawnAsync'; +export * from './stackTrace'; export * from './stringWidth'; export * from './task'; export * from './wsServer'; diff --git a/packages/isomorphic/stackTrace.ts b/packages/utils/stackTrace.ts similarity index 85% rename from packages/isomorphic/stackTrace.ts rename to packages/utils/stackTrace.ts index a973a0a465573..70f08b28bd89b 100644 --- a/packages/isomorphic/stackTrace.ts +++ b/packages/utils/stackTrace.ts @@ -19,6 +19,10 @@ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import path from 'path'; + +import { getAsBooleanFromENV } from './env'; + export type RawStack = string[]; export type StackFrame = { @@ -37,7 +41,7 @@ export function captureRawStack(): RawStack { return stack.split('\n'); } -export function parseStackFrame(text: string, pathSeparator: string): StackFrame | null { +export function parseStackFrame(text: string): StackFrame | null { const match = text && text.match(re); if (!match) return null; @@ -46,7 +50,7 @@ export function parseStackFrame(text: string, pathSeparator: string): StackFrame let file = match[7]; if (!file) return null; - if (!_showInternalStackFrames && (file.startsWith('internal') || file.startsWith('node:'))) + if (!showInternalStackFrames() && (file.startsWith('internal') || file.startsWith('node:'))) return null; const line = match[8]; @@ -96,7 +100,7 @@ export function parseStackFrame(text: string, pathSeparator: string): StackFrame if (file) { if (file.startsWith('file://')) - file = fileURLToPath(file, pathSeparator); + file = fileURLToPath(file); frame.file = file; } @@ -134,7 +138,7 @@ export function splitErrorMessage(message: string): { name: string, message: str }; } -export function parseErrorStack(stack: string, pathSeparator: string): { +export function parseErrorStack(stack: string): { message: string; stackLines: string[]; location?: StackFrame; @@ -147,10 +151,10 @@ export function parseErrorStack(stack: string, pathSeparator: string): { const stackLines = lines.slice(firstStackLine); let location: StackFrame | undefined; for (const line of stackLines) { - const frame = parseStackFrame(line, pathSeparator); + const frame = parseStackFrame(line); if (!frame || !frame.file) continue; - if (belongsToNodeModules(frame.file, pathSeparator)) + if (belongsToNodeModules(frame.file)) continue; location = { file: frame.file, column: frame.column || 0, line: frame.line || 0 }; break; @@ -158,8 +162,8 @@ export function parseErrorStack(stack: string, pathSeparator: string): { return { message, stackLines, location }; } -function belongsToNodeModules(file: string, pathSeparator: string) { - return file.includes(`${pathSeparator}node_modules${pathSeparator}`); +function belongsToNodeModules(file: string) { + return file.includes(`${path.sep}node_modules${path.sep}`); } export function filterStackFile(file: string) { @@ -172,10 +176,10 @@ export function filterStackFile(file: string) { return true; } -export function filteredStackTrace(rawStack: RawStack, pathSeparator: string): StackFrame[] { +export function filteredStackTrace(rawStack: RawStack): StackFrame[] { const frames: StackFrame[] = []; for (const line of rawStack) { - const frame = parseStackFrame(line, pathSeparator); + const frame = parseStackFrame(line); if (!frame || !frame.file) continue; if (!filterStackFile(frame.file)) @@ -212,20 +216,19 @@ const re = new RegExp('^' + const methodRe = /^(.*?) \[as (.*?)\]$/; -function fileURLToPath(fileUrl: string, pathSeparator: string): string { +function fileURLToPath(fileUrl: string): string { if (!fileUrl.startsWith('file://')) return fileUrl; - let path = decodeURIComponent(fileUrl.slice(7)); - if (path.startsWith('/') && /^[a-zA-Z]:/.test(path.slice(1))) - path = path.slice(1); + let filePath = decodeURIComponent(fileUrl.slice(7)); + if (filePath.startsWith('/') && /^[a-zA-Z]:/.test(filePath.slice(1))) + filePath = filePath.slice(1); - return path.replace(/\//g, pathSeparator); + return filePath.replace(/\//g, path.sep); } let _coreDir: string | undefined; let _boxedStackPrefixes: string[] = []; -let _showInternalStackFrames = false; export function setCoreDir(dir: string | undefined) { _coreDir = dir; @@ -239,10 +242,7 @@ export function setBoxedStackPrefixes(prefixes: string[]) { _boxedStackPrefixes = prefixes; } -export function setShowInternalStackFrames(value: boolean) { - _showInternalStackFrames = value; -} - +const _showInternalStackFrames = getAsBooleanFromENV('PWDEBUGIMPL'); export function showInternalStackFrames(): boolean { return _showInternalStackFrames; } diff --git a/tests/config/utils.ts b/tests/config/utils.ts index dcc8d7c6ebfc7..eca03a8006092 100644 --- a/tests/config/utils.ts +++ b/tests/config/utils.ts @@ -19,8 +19,7 @@ import { utils, iso } from '../../packages/playwright-core/lib/coreBundle'; import type { iso as isoType } from '../../packages/playwright-core/lib/coreBundle'; import type { Locator, Frame, Page } from 'playwright-core'; -import type { StackFrame } from '../../packages/isomorphic/stackTrace'; -import type { ActionTraceEvent, TraceEvent } from '@trace/trace'; +import type { StackFrame, ActionTraceEvent, TraceEvent } from '@trace/trace'; const { TraceLoader, TraceModel } = iso; type TraceModel = InstanceType; diff --git a/tests/library/tracing.spec.ts b/tests/library/tracing.spec.ts index 6e90b4f4539df..3984bc572b2b9 100644 --- a/tests/library/tracing.spec.ts +++ b/tests/library/tracing.spec.ts @@ -19,8 +19,7 @@ import { jpegjs } from 'playwright-core/lib/utilsBundle'; import path from 'path'; import { browserTest, contextTest as test, expect } from '../config/browserTest'; import { parseTraceRaw } from '../config/utils'; -import type { StackFrame } from '../../packages/isomorphic/stackTrace'; -import type { ActionTraceEvent } from '../../packages/trace/src/trace'; +import type { StackFrame, ActionTraceEvent } from '../../packages/trace/src/trace'; import { artifactsFolderName } from '../../packages/playwright/src/isomorphic/folders'; import { rafraf } from '../page/pageTest'; From f5264b8957659cb9137650f028da30c281672327 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 2 Jul 2026 15:27:07 +0100 Subject: [PATCH 09/10] chore: remove __testHookBeforeQuery (#41598) --- packages/playwright-core/src/client/frame.ts | 4 ++-- .../playwright-core/src/client/locator.ts | 5 ++--- .../src/server/dispatchers/frameDispatcher.ts | 2 +- .../src/server/frameSelectors.ts | 3 +-- packages/playwright-core/src/server/frames.ts | 4 ++-- tests/page/locator-query.spec.ts | 21 +++++++++++++------ 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index fac78d48a65f3..31ecc7f73cb2b 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -257,8 +257,8 @@ export class Frame extends ChannelOwner implements api.Fr return result.elements.map(e => ElementHandle.from(e) as ElementHandle); } - async _queryCount(selector: string, options?: {}): Promise { - return (await this._channel.queryCount({ selector, ...options }, undefined)).value; + async _queryCount(selector: string): Promise { + return (await this._channel.queryCount({ selector }, undefined)).value; } async content(): Promise { diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 05d8d98f16935..52c20c4269063 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -264,9 +264,8 @@ export class Locator implements api.Locator { await this._frame._channel.blur({ selector: this._selector, strict: true, ...options, timeout: this._frame._timeout(options) }, options?.signal); } - // options are only here for testing - async count(_options?: {}): Promise { - return await this._frame._queryCount(this._selector, _options); + async count(): Promise { + return await this._frame._queryCount(this._selector); } async normalize(): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 9ae0d8ffbebbb..d0225e66d5c51 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -115,7 +115,7 @@ export class FrameDispatcher extends Dispatcher { - return { value: await this._frame.queryCount(progress, params.selector, params) }; + return { value: await this._frame.queryCount(progress, params.selector) }; } async content(params: channels.FrameContentParams, progress: Progress): Promise { diff --git a/packages/playwright-core/src/server/frameSelectors.ts b/packages/playwright-core/src/server/frameSelectors.ts index 2cf448cc788a1..127bcbdd6283a 100644 --- a/packages/playwright-core/src/server/frameSelectors.ts +++ b/packages/playwright-core/src/server/frameSelectors.ts @@ -77,12 +77,11 @@ export class FrameSelectors { }, { info: resolved.info, scope: resolved.scope }); } - async queryCount(selector: string, options: any): Promise { + async queryCount(selector: string): Promise { const resolved = await this.resolveInjectedForSelector(selector); // Be careful, |this.frame| can be different from |resolved.frame|. if (!resolved) throw new Error(`Failed to find frame for selector "${selector}"`); - await options.__testHookBeforeQuery?.(); return await resolved.injected.evaluate((injected, { info }) => { const elements = injected.querySelectorAll(info.parsed, document); injected.checkDeprecatedSelectorUsage(info.parsed, elements); diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 18dadf1860e1b..6a6acc697857a 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -940,9 +940,9 @@ export class Frame extends SdkObject { return progress.race(this.selectors.queryAll(selector)); } - async queryCount(progress: Progress, selector: string, options: any): Promise { + async queryCount(progress: Progress, selector: string): Promise { try { - return await progress.race(this.selectors.queryCount(selector, options)); + return await progress.race(this.selectors.queryCount(selector)); } catch (e) { if (this.isNonRetriableError(e)) throw e; diff --git a/tests/page/locator-query.spec.ts b/tests/page/locator-query.spec.ts index 6503d93afe7f5..ff48d2cf8fb1c 100644 --- a/tests/page/locator-query.spec.ts +++ b/tests/page/locator-query.spec.ts @@ -273,10 +273,19 @@ it('alias methods coverage', async ({ page }) => { await expect(page.mainFrame().locator('button')).toHaveCount(1); }); -it('count() should not throw during navigation', async ({ page, mode }) => { - it.skip(mode !== 'default', 'No test hooks'); - await page.setContent(`
A
`); - const __testHookBeforeQuery = () => page.goto('data:text/html,
A
B
'); - // @ts-expect-error - expect(await page.locator('div').count({ __testHookBeforeQuery })).toBe(0); +it('count() should not throw during navigation', async ({ playwright, page, server }) => { + const createDummySelector = () => ({ + query(root, selector) { + window.location.href = './frames/one-frame.html'; + return document.querySelector(selector); + }, + queryAll(root: HTMLElement, selector: string) { + window.location.href = './frames/one-frame.html'; + return Array.from(document.querySelectorAll(selector)); + } + }); + await playwright.selectors.register('locatorCountHelper', createDummySelector); + await page.goto(server.PREFIX + '/frames/one-frame.html'); + await page.frames()[1].setContent(`
A
`); + expect(await page.frameLocator('locatorCountHelper=iframe').locator('locatorCountHelper=div').count()).toBe(0); }); From 868eb26e1101a31c9c27fd2596c10b01f3f9d26b Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jul 2026 16:27:38 +0200 Subject: [PATCH 10/10] docs(params): fix AbortSignal signal option since version to v1.62 (#41597) --- docs/src/api/params.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 382de448fdca1..1e751238a7b12 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1001,7 +1001,7 @@ The default value can be changed by using the [`method: BrowserContext.setDefaul ## wait-for-event-signal * langs: js -* since: v1.61 +* since: v1.62 - `signal` <[AbortSignal]> Allows to cancel the waiting using an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the waiting will be aborted and the operation will throw an error. @@ -1009,7 +1009,7 @@ Note that providing a signal does not disable the default timeout, which can be ## input-signal * langs: js -* since: v1.61 +* since: v1.62 - `signal` <[AbortSignal]> Allows to cancel the operation using an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). If the signal is aborted, the operation will be aborted and throw an error.