diff --git a/.github/workflows/cherry_pick_into_release_branch.yml b/.github/workflows/cherry_pick_into_release_branch.yml index f48028b14b..08c5562f35 100644 --- a/.github/workflows/cherry_pick_into_release_branch.yml +++ b/.github/workflows/cherry_pick_into_release_branch.yml @@ -60,7 +60,7 @@ jobs: git checkout -b "$BRANCH_NAME" git push origin $BRANCH_NAME - name: Create Pull Request - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} script: | diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index 4da024272d..8ae54609e4 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -58,7 +58,7 @@ jobs: path: '.' - name: Comment on PR - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/pr_check_client_side_changes.yml b/.github/workflows/pr_check_client_side_changes.yml index c048bc3ca0..236da1cc88 100644 --- a/.github/workflows/pr_check_client_side_changes.yml +++ b/.github/workflows/pr_check_client_side_changes.yml @@ -15,11 +15,13 @@ jobs: runs-on: ubuntu-20.04 if: github.repository == 'microsoft/playwright' steps: + - uses: actions/checkout@v4 - name: Create GitHub issue - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} script: | + const currentPlaywrightVersion = require('./package.json').version.match(/\d+\.\d+/)[0]; const { data } = await github.rest.git.getCommit({ owner: context.repo.owner, repo: context.repo.repo, @@ -27,10 +29,10 @@ jobs: }); const commitHeader = data.message.split('\n')[0]; - const title = '[Ports]: Backport client side changes'; + const title = '[Ports]: Backport client side changes for ' + currentPlaywrightVersion; for (const repo of ['playwright-python', 'playwright-java', 'playwright-dotnet']) { const { data: issuesData } = await github.rest.search.issuesAndPullRequests({ - q: `is:issue is:open repo:microsoft/${repo} in:title "${title}"` + q: `is:issue is:open repo:microsoft/${repo} in:title "${title}" author:playwrightmachine"` }) let issueNumber = null; let issueBody = ''; diff --git a/.github/workflows/roll_browser_into_playwright.yml b/.github/workflows/roll_browser_into_playwright.yml index 69dba0804d..da90513160 100644 --- a/.github/workflows/roll_browser_into_playwright.yml +++ b/.github/workflows/roll_browser_into_playwright.yml @@ -38,7 +38,7 @@ jobs: git commit -m "feat(${{ github.event.client_payload.browser }}): roll to r${{ github.event.client_payload.revision }}" git push origin $BRANCH_NAME - name: Create Pull Request - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} script: | diff --git a/.github/workflows/roll_driver_nodejs.yml b/.github/workflows/roll_driver_nodejs.yml index 2019303d58..ee0d3d262c 100644 --- a/.github/workflows/roll_driver_nodejs.yml +++ b/.github/workflows/roll_driver_nodejs.yml @@ -35,7 +35,7 @@ jobs: git push origin $BRANCH_NAME - name: Create Pull Request if: ${{ steps.prepare-branch.outputs.HAS_CHANGES == '1' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} script: | diff --git a/docs/src/api/class-android.md b/docs/src/api/class-android.md index c889ee5d0b..c976c9bba8 100644 --- a/docs/src/api/class-android.md +++ b/docs/src/api/class-android.md @@ -202,6 +202,12 @@ Prevents automatic playwright driver installation on attach. Assumes that the dr Optional device serial number to launch the browser on. If not specified, it will throw if multiple devices are connected. +### option: Android.launchServer.host +* since: v1.45 +- `host` <[string]> + +Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider hardening it with picking a specific interface. + ### option: Android.launchServer.port * since: v1.28 - `port` <[int]> diff --git a/docs/src/api/class-browserserver.md b/docs/src/api/class-browserserver.md index 21f318a70a..0de5c1228b 100644 --- a/docs/src/api/class-browserserver.md +++ b/docs/src/api/class-browserserver.md @@ -31,3 +31,5 @@ Browser websocket url. Browser websocket endpoint which can be used as an argument to [`method: BrowserType.connect`] to establish connection to the browser. + +Note that if the listen `host` option in `launchServer` options is not specified, localhost will be output anyway, even if the actual listening address is an unspecified address. diff --git a/docs/src/api/class-browsertype.md b/docs/src/api/class-browsertype.md index 6a776f1cee..176f696e1d 100644 --- a/docs/src/api/class-browsertype.md +++ b/docs/src/api/class-browsertype.md @@ -380,6 +380,12 @@ const { chromium } = require('playwright'); // Or 'webkit' or 'firefox'. ### option: BrowserType.launchServer.logger = %%-browser-option-logger-%% * since: v1.8 +### option: BrowserType.launchServer.host +* since: v1.45 +- `host` <[string]> + +Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider hardening it with picking a specific interface. + ### option: BrowserType.launchServer.port * since: v1.8 - `port` <[int]> diff --git a/docs/src/codegen.md b/docs/src/codegen.md index 5f11bdade3..b23c023ac2 100644 --- a/docs/src/codegen.md +++ b/docs/src/codegen.md @@ -127,8 +127,6 @@ When you have finished interacting with the page, press the **record** button to Use the **clear** button to clear the code to start recording again. Once finished close the Playwright inspector window or stop the terminal command. -To learn more about generating tests check out or detailed guide on [Codegen](./codegen.md). - ### Generating locators You can generate [locators](/locators.md) with the test generator. diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index e92a2f0313..00c9dedc0f 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -164,7 +164,7 @@ export default defineConfig({ * since: v1.10 - type: ?<[RegExp]|[Array]<[RegExp]>> -Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run tests with "cart" in the title. Also available in the [command line](../test-cli.md) with the `-g` option. The regular expression will be tested against the string that consists of the test file name, `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. +Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run tests with "cart" in the title. Also available in the [command line](../test-cli.md) with the `-g` option. The regular expression will be tested against the string that consists of the project name, the test file name, the `test.describe` name (if any), the test name and the test tags divided by spaces, e.g. `chromium my-test.spec.ts my-suite my-test`. `grep` option is also useful for [tagging tests](../test-annotations.md#tag-tests). diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index e8cbfa373c..6a4dfd55c7 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -559,7 +559,7 @@ Whether to record trace for each test. Defaults to `'off'`. * `'on-first-retry'`: Record trace only when retrying a test for the first time. * `'on-all-retries'`: Record trace only when retrying a test. * `'retain-on-failure'`: Record trace for each test. When test run passes, remove the recorded trace. -* `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retires. When test run passes, remove the recorded trace. +* `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retries. When test run passes, remove the recorded trace. For more control, pass an object that specifies `mode` and trace features to enable. diff --git a/docs/src/test-api/class-testproject.md b/docs/src/test-api/class-testproject.md index 562dc76e09..a15a836010 100644 --- a/docs/src/test-api/class-testproject.md +++ b/docs/src/test-api/class-testproject.md @@ -123,7 +123,7 @@ You can configure entire test project to concurrently run all tests in all files * since: v1.10 - type: ?<[RegExp]|[Array]<[RegExp]>> -Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run tests with "cart" in the title. Also available globally and in the [command line](../test-cli.md) with the `-g` option. The regular expression will be tested against the string that consists of the test file name, `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. +Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run tests with "cart" in the title. Also available globally and in the [command line](../test-cli.md) with the `-g` option. The regular expression will be tested against the string that consists of the project name, the test file name, the `test.describe` name (if any), the test name and the test tags divided by spaces, e.g. `chromium my-test.spec.ts my-suite my-test`. `grep` option is also useful for [tagging tests](../test-annotations.md#tag-tests). diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index aa4430bd42..290fb7df96 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -100,7 +100,6 @@ Complete set of Playwright Test options is available in the [configuration file] | `--reporter ` | Choose a reporter: minimalist `dot`, concise `line` or detailed `list`. See [reporters](./test-reporters.md) for more information. You can also pass a path to a [custom reporter](./test-reporters.md#custom-reporters) file. | | `--retries ` | The maximum number of [retries](./test-retries.md#retries) for flaky tests, defaults to zero (no retries). | | `--shard ` | [Shard](./test-parallel.md#shard-tests-between-multiple-machines) tests and execute only selected shard, specified in the form `current/all`, 1-based, for example `3/5`.| -| `--tag ` | Only run tests with a tag matching this tag expression. Learn more about [tagging](./test-annotations.md#tag-tests). | | `--timeout ` | Maximum timeout in milliseconds for each test, defaults to 30 seconds. Learn more about [various timeouts](./test-timeouts.md).| | `--trace ` | Force tracing mode, can be `on`, `off`, `on-first-retry`, `on-all-retries`, `retain-on-failure` | | `--update-snapshots` or `-u` | Whether to update [snapshots](./test-snapshots.md) with actual results instead of comparing them. Use this when snapshot expectations have changed.| diff --git a/docs/src/test-fixtures-js.md b/docs/src/test-fixtures-js.md index 48c9d074ba..0971922d35 100644 --- a/docs/src/test-fixtures-js.md +++ b/docs/src/test-fixtures-js.md @@ -926,9 +926,9 @@ export default defineConfig({ Each fixture has a setup and teardown phase separated by the `await use()` call in the fixture. Setup is executed before the fixture is used by the test/hook, and teardown is executed when the fixture will not be used by the test/hook anymore. Fixtures follow these rules to determine the execution order: -* When fixture A depends on fixture B: B is always set up before A and teared down after A. +* When fixture A depends on fixture B: B is always set up before A and torn down after A. * Non-automatic fixtures are executed lazily, only when the test/hook needs them. -* Test-scoped fixtures are teared down after each test, while worker-scoped fixtures are only teared down when the worker process executing tests is shutdown. +* Test-scoped fixtures are torn down after each test, while worker-scoped fixtures are only torn down when the worker process executing tests is shutdown. Consider the following example: @@ -1036,8 +1036,8 @@ Normally, if all tests pass and no errors are thrown, the order of execution is * `beforeEach` runs. * `first test` runs. * `afterEach` runs. - * `page` teardown because it is a test-scoped fixture and should be teared down after the test finishes. - * `autoTestFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes. + * `page` teardown because it is a test-scoped fixture and should be torn down after the test finishes. + * `autoTestFixture` teardown because it is a test-scoped fixture and should be torn down after the test finishes. * `second test` section: * `autoTestFixture` setup because automatic test fixtures are always set up before test and `beforeEach` hooks. * `page` setup because it is required in `beforeEach` hook. @@ -1046,20 +1046,20 @@ Normally, if all tests pass and no errors are thrown, the order of execution is * `testFixture` setup because it is required by the `second test`. * `second test` runs. * `afterEach` runs. - * `testFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes. - * `page` teardown because it is a test-scoped fixture and should be teared down after the test finishes. - * `autoTestFixture` teardown because it is a test-scoped fixture and should be teared down after the test finishes. + * `testFixture` teardown because it is a test-scoped fixture and should be torn down after the test finishes. + * `page` teardown because it is a test-scoped fixture and should be torn down after the test finishes. + * `autoTestFixture` teardown because it is a test-scoped fixture and should be torn down after the test finishes. * `afterAll` and worker teardown section: * `afterAll` runs. - * `workerFixture` teardown because it is a workers-scoped fixture and should be teared down once at the end. - * `autoWorkerFixture` teardown because it is a workers-scoped fixture and should be teared down once at the end. - * `browser` teardown because it is a workers-scoped fixture and should be teared down once at the end. + * `workerFixture` teardown because it is a workers-scoped fixture and should be torn down once at the end. + * `autoWorkerFixture` teardown because it is a workers-scoped fixture and should be torn down once at the end. + * `browser` teardown because it is a workers-scoped fixture and should be torn down once at the end. A few observations: -* `page` and `autoTestFixture` are set up and teared down for each test, as test-scoped fixtures. +* `page` and `autoTestFixture` are set up and torn down for each test, as test-scoped fixtures. * `unusedFixture` is never set up because it is not used by any tests/hooks. * `testFixture` depends on `workerFixture` and triggers its setup. -* `workerFixture` is lazily set up before the second test, but teared down once during worker shutdown, as a worker-scoped fixture. +* `workerFixture` is lazily set up before the second test, but torn down once during worker shutdown, as a worker-scoped fixture. * `autoWorkerFixture` is set up for `beforeAll` hook, but `autoTestFixture` is not. ## Combine custom fixtures from multiple modules diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 062ec55ea1..4f0533c3e9 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1223", + "revision": "1226", "installByDefault": false, - "browserVersion": "127.0.6492.0" + "browserVersion": "127.0.6505.0" }, { "name": "firefox", @@ -27,7 +27,7 @@ }, { "name": "webkit", - "revision": "2012", + "revision": "2013", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", diff --git a/packages/playwright-core/src/androidServerImpl.ts b/packages/playwright-core/src/androidServerImpl.ts index 4c3685076e..a0d7bb5496 100644 --- a/packages/playwright-core/src/androidServerImpl.ts +++ b/packages/playwright-core/src/androidServerImpl.ts @@ -50,7 +50,7 @@ export class AndroidServerLauncherImpl { // 2. Start the server const server = new PlaywrightServer({ mode: 'launchServer', path, maxConnections: 1, preLaunchedAndroidDevice: device }); - const wsEndpoint = await server.listen(options.port); + const wsEndpoint = await server.listen(options.port, options.host); // 3. Return the BrowserServer interface const browserServer = new ws.EventEmitter() as (BrowserServer & WebSocketEventEmitter); diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 41f9c2c2e4..dfe960c5ea 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -58,7 +58,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher { // 2. Start the server const server = new PlaywrightServer({ mode: 'launchServer', path, maxConnections: Infinity, preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy }); - const wsEndpoint = await server.listen(options.port); + const wsEndpoint = await server.listen(options.port, options.host); // 3. Return the BrowserServer interface const browserServer = new ws.EventEmitter() as (BrowserServer & WebSocketEventEmitter); diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 9932050d3e..97d82de92d 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -235,11 +235,11 @@ export class BrowserType extends ChannelOwner imple context.setDefaultTimeout(this._defaultContextTimeout); if (this._defaultContextNavigationTimeout !== undefined) context.setDefaultNavigationTimeout(this._defaultContextNavigationTimeout); - await this._instrumentation.onDidCreateBrowserContext(context); + await this._instrumentation.runAfterCreateBrowserContext(context); } async _willCloseContext(context: BrowserContext) { this._contexts.delete(context); - await this._instrumentation.onWillCloseBrowserContext(context); + await this._instrumentation.runBeforeCloseBrowserContext(context); } } diff --git a/packages/playwright-core/src/client/clientInstrumentation.ts b/packages/playwright-core/src/client/clientInstrumentation.ts index dd73299420..6d90911acd 100644 --- a/packages/playwright-core/src/client/clientInstrumentation.ts +++ b/packages/playwright-core/src/client/clientInstrumentation.ts @@ -24,21 +24,23 @@ export interface ClientInstrumentation { removeAllListeners(): void; onApiCallBegin(apiCall: string, params: Record, frames: StackFrame[], userData: any, out: { stepId?: string }): void; onApiCallEnd(userData: any, error?: Error): void; - onDidCreateBrowserContext(context: BrowserContext): Promise; - onDidCreateRequestContext(context: APIRequestContext): Promise; onWillPause(): void; - onWillCloseBrowserContext(context: BrowserContext): Promise; - onWillCloseRequestContext(context: APIRequestContext): Promise; + + runAfterCreateBrowserContext(context: BrowserContext): Promise; + runAfterCreateRequestContext(context: APIRequestContext): Promise; + runBeforeCloseBrowserContext(context: BrowserContext): Promise; + runBeforeCloseRequestContext(context: APIRequestContext): Promise; } export interface ClientInstrumentationListener { onApiCallBegin?(apiName: string, params: Record, frames: StackFrame[], userData: any, out: { stepId?: string }): void; onApiCallEnd?(userData: any, error?: Error): void; - onDidCreateBrowserContext?(context: BrowserContext): Promise; - onDidCreateRequestContext?(context: APIRequestContext): Promise; onWillPause?(): void; - onWillCloseBrowserContext?(context: BrowserContext): Promise; - onWillCloseRequestContext?(context: APIRequestContext): Promise; + + runAfterCreateBrowserContext?(context: BrowserContext): Promise; + runAfterCreateRequestContext?(context: APIRequestContext): Promise; + runBeforeCloseBrowserContext?(context: BrowserContext): Promise; + runBeforeCloseRequestContext?(context: APIRequestContext): Promise; } export function createInstrumentation(): ClientInstrumentation { @@ -53,12 +55,19 @@ export function createInstrumentation(): ClientInstrumentation { return (listener: ClientInstrumentationListener) => listeners.splice(listeners.indexOf(listener), 1); if (prop === 'removeAllListeners') return () => listeners.splice(0, listeners.length); - if (!prop.startsWith('on')) - return obj[prop]; - return async (...params: any[]) => { - for (const listener of listeners) - await (listener as any)[prop]?.(...params); - }; + if (prop.startsWith('run')) { + return async (...params: any[]) => { + for (const listener of listeners) + await (listener as any)[prop]?.(...params); + }; + } + if (prop.startsWith('on')) { + return (...params: any[]) => { + for (const listener of listeners) + (listener as any)[prop]?.(...params); + }; + } + return obj[prop]; }, }); } diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 0ac7a5f8db..4598ac2418 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -77,7 +77,7 @@ export class APIRequest implements api.APIRequest { this._contexts.add(context); context._request = this; context._tracing._tracesDir = tracesDir; - await context._instrumentation.onDidCreateRequestContext(context); + await context._instrumentation.runAfterCreateRequestContext(context); return context; } } @@ -102,7 +102,7 @@ export class APIRequestContext extends ChannelOwner { this._closeReason = options.reason; - await this._instrumentation.onWillCloseRequestContext(this); + await this._instrumentation.runBeforeCloseRequestContext(this); try { await this._channel.dispose(options); } catch (e) { diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 2fc2cea0d1..e649a21303 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -111,6 +111,7 @@ export type LaunchServerOptions = { }, downloadsPath?: string, chromiumSandbox?: boolean, + host?: string, port?: number, wsPath?: string, logger?: Logger, @@ -122,6 +123,7 @@ export type LaunchAndroidServerOptions = { adbHost?: string, adbPort?: number, omitDriverInstall?: boolean, + host?: string, port?: number, wsPath?: string, }; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index fa24e066b6..54d79f9ca1 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13745,6 +13745,13 @@ export interface BrowserType { */ headless?: boolean; + /** + * Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the + * unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider + * hardening it with picking a specific interface. + */ + host?: string; + /** * If `true`, Playwright does not pass its own configurations args and only uses the ones from `args`. If an array is * given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`. @@ -14621,6 +14628,13 @@ export interface Android { */ deviceSerialNumber?: string; + /** + * Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the + * unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider + * hardening it with picking a specific interface. + */ + host?: string; + /** * Prevents automatic playwright driver installation on attach. Assumes that the drivers have been installed already. */ @@ -17201,6 +17215,9 @@ export interface BrowserServer { * Browser websocket endpoint which can be used as an argument to * [browserType.connect(wsEndpoint[, options])](https://playwright.dev/docs/api/class-browsertype#browser-type-connect) * to establish connection to the browser. + * + * Note that if the listen `host` option in `launchServer` options is not specified, localhost will be output anyway, + * even if the actual listening address is an unspecified address. */ wsEndpoint(): string; diff --git a/packages/playwright-ct-core/src/mount.ts b/packages/playwright-ct-core/src/mount.ts index 7220916a4b..e743a6aaa1 100644 --- a/packages/playwright-ct-core/src/mount.ts +++ b/packages/playwright-ct-core/src/mount.ts @@ -102,6 +102,7 @@ async function innerMount(page: Page, componentRef: JsxComponent | ImportRef, op const selector = await page.evaluate(async ({ component, hooksConfig }) => { component = await window.__pwUnwrapObject(component); + hooksConfig = await window.__pwUnwrapObject(hooksConfig); let rootElement = document.getElementById('root'); if (!rootElement) { rootElement = document.createElement('div'); diff --git a/packages/playwright-ct-core/types/component.d.ts b/packages/playwright-ct-core/types/component.d.ts index 5bd5d7e017..ed5c616bfa 100644 --- a/packages/playwright-ct-core/types/component.d.ts +++ b/packages/playwright-ct-core/types/component.d.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -type JsonPrimitive = string | number | boolean | null; -type JsonValue = JsonPrimitive | JsonObject | JsonArray; -type JsonArray = JsonValue[]; -export type JsonObject = { [Key in string]?: JsonValue }; - export type JsxComponent = { __pw_type: 'jsx', type: any, @@ -47,10 +42,10 @@ declare global { playwrightMount(component: Component, rootElement: Element, hooksConfig?: any): Promise; playwrightUnmount(rootElement: Element): Promise; playwrightUpdate(rootElement: Element, component: Component): Promise; - __pw_hooks_before_mount?: (( + __pw_hooks_before_mount?: (( params: { hooksConfig?: HooksConfig; [key: string]: any } ) => Promise)[]; - __pw_hooks_after_mount?: (( + __pw_hooks_after_mount?: (( params: { hooksConfig?: HooksConfig; [key: string]: any } ) => Promise)[]; // Can't start with __pw due to core reuse bindings logic for __pw*. diff --git a/packages/playwright-ct-react/hooks.d.ts b/packages/playwright-ct-react/hooks.d.ts index 0fa7cf3e1a..1093f1a3a3 100644 --- a/packages/playwright-ct-react/hooks.d.ts +++ b/packages/playwright-ct-react/hooks.d.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; - -export declare function beforeMount( +export declare function beforeMount( callback: (params: { hooksConfig?: HooksConfig; App: () => JSX.Element }) => Promise ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig }) => Promise ): void; diff --git a/packages/playwright-ct-react/index.d.ts b/packages/playwright-ct-react/index.d.ts index 7cfcf04ba0..c086d4bec5 100644 --- a/packages/playwright-ct-react/index.d.ts +++ b/packages/playwright-ct-react/index.d.ts @@ -15,10 +15,9 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { TestType } from '@playwright/experimental-ct-core'; -export interface MountOptions { +export interface MountOptions { hooksConfig?: HooksConfig; } @@ -28,7 +27,7 @@ export interface MountResult extends Locator { } export const test: TestType<{ - mount( + mount( component: JSX.Element, options?: MountOptions ): Promise; diff --git a/packages/playwright-ct-react17/hooks.d.ts b/packages/playwright-ct-react17/hooks.d.ts index 0fa7cf3e1a..1093f1a3a3 100644 --- a/packages/playwright-ct-react17/hooks.d.ts +++ b/packages/playwright-ct-react17/hooks.d.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; - -export declare function beforeMount( +export declare function beforeMount( callback: (params: { hooksConfig?: HooksConfig; App: () => JSX.Element }) => Promise ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig }) => Promise ): void; diff --git a/packages/playwright-ct-react17/index.d.ts b/packages/playwright-ct-react17/index.d.ts index 7cfcf04ba0..c086d4bec5 100644 --- a/packages/playwright-ct-react17/index.d.ts +++ b/packages/playwright-ct-react17/index.d.ts @@ -15,10 +15,9 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { TestType } from '@playwright/experimental-ct-core'; -export interface MountOptions { +export interface MountOptions { hooksConfig?: HooksConfig; } @@ -28,7 +27,7 @@ export interface MountResult extends Locator { } export const test: TestType<{ - mount( + mount( component: JSX.Element, options?: MountOptions ): Promise; diff --git a/packages/playwright-ct-solid/hooks.d.ts b/packages/playwright-ct-solid/hooks.d.ts index 38d483ec6d..097c8cf11f 100644 --- a/packages/playwright-ct-solid/hooks.d.ts +++ b/packages/playwright-ct-solid/hooks.d.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -import { JSXElement } from "solid-js"; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; +import { JSXElement } from 'solid-js'; -export declare function beforeMount( +export declare function beforeMount( callback: (params: { hooksConfig?: HooksConfig, App: () => JSXElement }) => Promise ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig }) => Promise ): void; diff --git a/packages/playwright-ct-solid/index.d.ts b/packages/playwright-ct-solid/index.d.ts index 7cfcf04ba0..c086d4bec5 100644 --- a/packages/playwright-ct-solid/index.d.ts +++ b/packages/playwright-ct-solid/index.d.ts @@ -15,10 +15,9 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { TestType } from '@playwright/experimental-ct-core'; -export interface MountOptions { +export interface MountOptions { hooksConfig?: HooksConfig; } @@ -28,7 +27,7 @@ export interface MountResult extends Locator { } export const test: TestType<{ - mount( + mount( component: JSX.Element, options?: MountOptions ): Promise; diff --git a/packages/playwright-ct-svelte/hooks.d.ts b/packages/playwright-ct-svelte/hooks.d.ts index 03f801c338..dfa037c7de 100644 --- a/packages/playwright-ct-svelte/hooks.d.ts +++ b/packages/playwright-ct-svelte/hooks.d.ts @@ -15,15 +15,14 @@ */ import type { ComponentConstructorOptions, SvelteComponent } from 'svelte'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; -export declare function beforeMount( +export declare function beforeMount( callback: (params: { hooksConfig?: HooksConfig, App: new (options: Partial) => SvelteComponent }) => Promise ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig; svelteComponent: SvelteComponent; diff --git a/packages/playwright-ct-svelte/index.d.ts b/packages/playwright-ct-svelte/index.d.ts index bd1ddd5b36..eb6d464032 100644 --- a/packages/playwright-ct-svelte/index.d.ts +++ b/packages/playwright-ct-svelte/index.d.ts @@ -15,7 +15,6 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { SvelteComponent, ComponentProps } from 'svelte/types/runtime'; import type { TestType } from '@playwright/experimental-ct-core'; @@ -23,7 +22,7 @@ type ComponentSlot = string | string[]; type ComponentSlots = Record & { default?: ComponentSlot }; type ComponentEvents = Record; -export interface MountOptions { +export interface MountOptions { props?: ComponentProps; slots?: ComponentSlots; on?: ComponentEvents; @@ -39,7 +38,7 @@ export interface MountResult extends Locator } export const test: TestType<{ - mount( + mount( component: new (...args: any[]) => Component, options?: MountOptions ): Promise>; diff --git a/packages/playwright-ct-vue/hooks.d.ts b/packages/playwright-ct-vue/hooks.d.ts index 2a18496007..69b91cc2cb 100644 --- a/packages/playwright-ct-vue/hooks.d.ts +++ b/packages/playwright-ct-vue/hooks.d.ts @@ -15,12 +15,11 @@ */ import type { App, ComponentPublicInstance } from 'vue'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; -export declare function beforeMount( +export declare function beforeMount( callback: (params: { app: App; hooksConfig?: HooksConfig }) => Promise ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { app: App; hooksConfig?: HooksConfig; diff --git a/packages/playwright-ct-vue/index.d.ts b/packages/playwright-ct-vue/index.d.ts index 308e47c18c..f755a6bcf3 100644 --- a/packages/playwright-ct-vue/index.d.ts +++ b/packages/playwright-ct-vue/index.d.ts @@ -15,7 +15,6 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { TestType } from '@playwright/experimental-ct-core'; type ComponentSlot = string | string[]; @@ -29,14 +28,14 @@ type ComponentProps = T extends (props: infer P, ...args: any) => any ? P : {}; -export interface MountOptions { +export interface MountOptions { props?: ComponentProps; slots?: ComponentSlots; on?: ComponentEvents; hooksConfig?: HooksConfig; } -export interface MountOptionsJsx { +export interface MountOptionsJsx { hooksConfig?: HooksConfig; } @@ -55,11 +54,11 @@ export interface MountResultJsx extends Locator { } export const test: TestType<{ - mount( + mount( component: JSX.Element, options: MountOptionsJsx ): Promise; - mount( + mount( component: Component, options?: MountOptions ): Promise>; diff --git a/packages/playwright-ct-vue2/hooks.d.ts b/packages/playwright-ct-vue2/hooks.d.ts index 4c3a391117..5009f44348 100644 --- a/packages/playwright-ct-vue2/hooks.d.ts +++ b/packages/playwright-ct-vue2/hooks.d.ts @@ -14,17 +14,16 @@ * limitations under the License. */ -import { ComponentOptions } from 'vue'; -import { CombinedVueInstance, Vue, VueConstructor } from 'vue/types/vue'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; +import type { ComponentOptions } from 'vue'; +import type { CombinedVueInstance, Vue, VueConstructor } from 'vue/types/vue'; -export declare function beforeMount( - callback: (params: { - hooksConfig?: HooksConfig, - Vue: VueConstructor, +export declare function beforeMount( + callback: (params: { + hooksConfig?: HooksConfig, + Vue: VueConstructor, }) => Promise & Record> ): void; -export declare function afterMount( +export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig; instance: CombinedVueInstance< diff --git a/packages/playwright-ct-vue2/index.d.ts b/packages/playwright-ct-vue2/index.d.ts index f76285fb8f..b4fd75e395 100644 --- a/packages/playwright-ct-vue2/index.d.ts +++ b/packages/playwright-ct-vue2/index.d.ts @@ -15,7 +15,6 @@ */ import type { Locator } from 'playwright/test'; -import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; import type { TestType } from '@playwright/experimental-ct-core'; type Slot = string | string[]; @@ -29,14 +28,14 @@ type ComponentProps = T extends (props: infer P, ...args: any) => any ? P : {}; -export interface MountOptions { +export interface MountOptions { props?: ComponentProps; slots?: ComponentSlots; on?: ComponentEvents; hooksConfig?: HooksConfig; } -export interface MountOptionsJsx { +export interface MountOptionsJsx { hooksConfig?: HooksConfig; } @@ -55,11 +54,11 @@ export interface MountResultJsx extends Locator { } export const test: TestType<{ - mount( + mount( component: JSX.Element, options?: MountOptionsJsx ): Promise; - mount( + mount( component: Component, options?: MountOptions ): Promise>; diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 0a6651f362..c196dc2d2f 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -268,19 +268,19 @@ const playwrightFixtures: Fixtures = ({ onWillPause: () => { currentTestInfo()?.setTimeout(0); }, - onDidCreateBrowserContext: async (context: BrowserContext) => { + runAfterCreateBrowserContext: async (context: BrowserContext) => { await artifactsRecorder?.didCreateBrowserContext(context); const testInfo = currentTestInfo(); if (testInfo) attachConnectedHeaderIfNeeded(testInfo, context.browser()); }, - onDidCreateRequestContext: async (context: APIRequestContext) => { + runAfterCreateRequestContext: async (context: APIRequestContext) => { await artifactsRecorder?.didCreateRequestContext(context); }, - onWillCloseBrowserContext: async (context: BrowserContext) => { + runBeforeCloseBrowserContext: async (context: BrowserContext) => { await artifactsRecorder?.willCloseBrowserContext(context); }, - onWillCloseRequestContext: async (context: APIRequestContext) => { + runBeforeCloseRequestContext: async (context: APIRequestContext) => { await artifactsRecorder?.willCloseRequestContext(context); }, }; diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index d6cfb21320..df77370dd2 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -32,6 +32,9 @@ export class TeleReporterEmitter implements ReporterV2 { private _messageSink: (message: teleReceiver.JsonEvent) => void; private _rootDir!: string; private _emitterOptions: TeleReporterEmitterOptions; + // In case there is blob reporter and UI mode, make sure one does override + // the id assigned by the other. + private readonly _idSymbol = Symbol('id'); constructor(messageSink: (message: teleReceiver.JsonEvent) => void, options: TeleReporterEmitterOptions = {}) { this._messageSink = messageSink; @@ -55,7 +58,7 @@ export class TeleReporterEmitter implements ReporterV2 { } onTestBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void { - (result as any)[idSymbol] = createGuid(); + (result as any)[this._idSymbol] = createGuid(); this._messageSink({ method: 'onTestBegin', params: { @@ -82,12 +85,12 @@ export class TeleReporterEmitter implements ReporterV2 { } onStepBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void { - (step as any)[idSymbol] = createGuid(); + (step as any)[this._idSymbol] = createGuid(); this._messageSink({ method: 'onStepBegin', params: { testId: test.id, - resultId: (result as any)[idSymbol], + resultId: (result as any)[this._idSymbol], step: this._serializeStepStart(step) } }); @@ -98,7 +101,7 @@ export class TeleReporterEmitter implements ReporterV2 { method: 'onStepEnd', params: { testId: test.id, - resultId: (result as any)[idSymbol], + resultId: (result as any)[this._idSymbol], step: this._serializeStepEnd(step) } }); @@ -126,7 +129,7 @@ export class TeleReporterEmitter implements ReporterV2 { const data = isBase64 ? chunk.toString('base64') : chunk; this._messageSink({ method: 'onStdIO', - params: { testId: test?.id, resultId: result ? (result as any)[idSymbol] : undefined, type, data, isBase64 } + params: { testId: test?.id, resultId: result ? (result as any)[this._idSymbol] : undefined, type, data, isBase64 } }); } @@ -214,7 +217,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeResultStart(result: reporterTypes.TestResult): teleReceiver.JsonTestResultStart { return { - id: (result as any)[idSymbol], + id: (result as any)[this._idSymbol], retry: result.retry, workerIndex: result.workerIndex, parallelIndex: result.parallelIndex, @@ -224,7 +227,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeResultEnd(result: reporterTypes.TestResult): teleReceiver.JsonTestResultEnd { return { - id: (result as any)[idSymbol], + id: (result as any)[this._idSymbol], duration: result.duration, status: result.status, errors: result.errors, @@ -244,8 +247,8 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeStepStart(step: reporterTypes.TestStep): teleReceiver.JsonTestStepStart { return { - id: (step as any)[idSymbol], - parentStepId: (step.parent as any)?.[idSymbol], + id: (step as any)[this._idSymbol], + parentStepId: (step.parent as any)?.[this._idSymbol], title: step.title, category: step.category, startTime: +step.startTime, @@ -255,7 +258,7 @@ export class TeleReporterEmitter implements ReporterV2 { private _serializeStepEnd(step: reporterTypes.TestStep): teleReceiver.JsonTestStepEnd { return { - id: (step as any)[idSymbol], + id: (step as any)[this._idSymbol], duration: step.duration, error: step.error, }; @@ -280,5 +283,3 @@ export class TeleReporterEmitter implements ReporterV2 { return path.relative(this._rootDir, absolutePath); } } - -const idSymbol = Symbol('id'); diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index a1c05bbb34..9d49c2f48f 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -266,8 +266,9 @@ interface TestProject { /** * Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only * run tests with "cart" in the title. Also available globally and in the [command line](https://playwright.dev/docs/test-cli) with the `-g` - * option. The regular expression will be tested against the string that consists of the test file name, - * `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. + * option. The regular expression will be tested against the string that consists of the project name, the test file + * name, the `test.describe` name (if any), the test name and the test tags divided by spaces, e.g. `chromium + * my-test.spec.ts my-suite my-test`. * * `grep` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). */ @@ -1130,8 +1131,9 @@ interface TestConfig { /** * Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only * run tests with "cart" in the title. Also available in the [command line](https://playwright.dev/docs/test-cli) with the `-g` option. The - * regular expression will be tested against the string that consists of the test file name, `test.describe` name (if - * any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. + * regular expression will be tested against the string that consists of the project name, the test file name, the + * `test.describe` name (if any), the test name and the test tags divided by spaces, e.g. `chromium my-test.spec.ts + * my-suite my-test`. * * `grep` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). * @@ -5088,7 +5090,7 @@ export interface PlaywrightWorkerOptions { * - `'on-first-retry'`: Record trace only when retrying a test for the first time. * - `'on-all-retries'`: Record trace only when retrying a test. * - `'retain-on-failure'`: Record trace for each test. When test run passes, remove the recorded trace. - * - `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retires. When test run + * - `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retries. When test run * passes, remove the recorded trace. * * For more control, pass an object that specifies `mode` and trace features to enable. diff --git a/packages/trace-viewer/snapshot.html b/packages/trace-viewer/snapshot.html index bee3af79db..3f27586af5 100644 --- a/packages/trace-viewer/snapshot.html +++ b/packages/trace-viewer/snapshot.html @@ -26,7 +26,7 @@ const traceUrl = new URL(location.href).searchParams.get('trace'); const params = new URLSearchParams(); params.set('trace', traceUrl); - await fetch('context?' + params.toString()).then(r => r.json()); + await fetch('contexts?' + params.toString()).then(r => r.json()); await location.reload(); })(); diff --git a/tests/android/launch-server.spec.ts b/tests/android/launch-server.spec.ts index c90a25b0a0..80c9760f2e 100644 --- a/tests/android/launch-server.spec.ts +++ b/tests/android/launch-server.spec.ts @@ -30,6 +30,17 @@ test('android.launchServer should connect to a device', async ({ playwright }) = await browserServer.close(); }); +test('android.launchServer should work with host', async ({ playwright }) => { + const host = '0.0.0.0'; + const browserServer = await playwright._android.launchServer({ host }); + expect(browserServer.wsEndpoint()).toContain(String(host)); + const device = await playwright._android.connect(browserServer.wsEndpoint()); + const output = await device.shell('echo 123'); + expect(output.toString()).toBe('123\n'); + await device.close(); + await browserServer.close(); +}); + test('android.launchServer should handle close event correctly', async ({ playwright }) => { const receivedEvents: string[] = []; const browserServer = await playwright._android.launchServer(); diff --git a/tests/components/ct-vue-vite/playwright/index.ts b/tests/components/ct-vue-vite/playwright/index.ts index 977ee33845..f23ab5a90a 100644 --- a/tests/components/ct-vue-vite/playwright/index.ts +++ b/tests/components/ct-vue-vite/playwright/index.ts @@ -4,14 +4,17 @@ import Button from '../src/components/Button.vue'; import '../src/assets/index.css'; export type HooksConfig = { - route?: string; routing?: boolean; + components?: Record; } beforeMount(async ({ app, hooksConfig }) => { - if (hooksConfig?.routing) + if (hooksConfig?.routing) app.use(router as any); // TODO: remove any and fix the various installed conflicting Vue versions - app.component('Button', Button); + + for (const [name, component] of Object.entries(hooksConfig?.components || {})) + app.component(name, component); + console.log(`Before mount: ${JSON.stringify(hooksConfig)}, app: ${!!app}`); }); diff --git a/tests/components/ct-vue-vite/tests/slots/slots.spec.js b/tests/components/ct-vue-vite/tests/slots/slots.spec.js index 2af38036b5..a33c9dac92 100644 --- a/tests/components/ct-vue-vite/tests/slots/slots.spec.js +++ b/tests/components/ct-vue-vite/tests/slots/slots.spec.js @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/experimental-ct-vue'; import DefaultSlot from '@/components/DefaultSlot.vue'; import NamedSlots from '@/components/NamedSlots.vue'; +import Button from '@/components/Button.vue'; test('render a default slot', async ({ mount }) => { const component = await mount(DefaultSlot, { @@ -16,6 +17,9 @@ test('render a component as slot', async ({ mount }) => { slots: { default: '