From 9c14cccc24db0a486e5fab3f3f4f4be887eb2d39 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:17:29 -0800 Subject: [PATCH 01/83] feat(chromium-tip-of-tree): roll to r1288 (#34092) --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index c9bab0dffa..0b673fef08 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1287", + "revision": "1288", "installByDefault": false, - "browserVersion": "133.0.6901.0" + "browserVersion": "133.0.6905.0" }, { "name": "firefox", From 94ffbcb9c5634f9acc0fc6de178ef315ac08f4a1 Mon Sep 17 00:00:00 2001 From: Volodymyr Momot <86200333+mmtv-qa@users.noreply.github.com> Date: Thu, 19 Dec 2024 20:36:02 +0200 Subject: [PATCH 02/83] feat(fetch/network): add generic to json method (#34091) --- packages/playwright-core/src/client/fetch.ts | 4 ++-- packages/playwright-core/src/client/network.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 58928532ac..2cfbbff14b 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -350,9 +350,9 @@ export class APIResponse implements api.APIResponse { return content.toString('utf8'); } - async json(): Promise { + async json(): Promise { const content = await this.text(); - return JSON.parse(content); + return JSON.parse(content) as T; } async [Symbol.asyncDispose]() { diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index cb18681ccf..25007db199 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -718,9 +718,9 @@ export class Response extends ChannelOwner implements return content.toString('utf8'); } - async json(): Promise { + async json(): Promise { const content = await this.text(); - return JSON.parse(content); + return JSON.parse(content) as T; } request(): Request { From 8e721fac858053fd0e7e6087b3863b4bfcff5ff5 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 19 Dec 2024 11:55:10 -0800 Subject: [PATCH 03/83] chore(bidi): no retries on CI (#34080) --- tests/bidi/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index 39df88f537..adaef490ea 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -58,7 +58,7 @@ const config: Config Date: Thu, 19 Dec 2024 21:03:33 +0100 Subject: [PATCH 04/83] devops: run bidi tests for bidi changes (#34099) --- .github/workflows/tests_bidi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index 46b16aac7b..4fee45e34c 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -7,6 +7,7 @@ on: - main paths: - .github/workflows/tests_bidi.yml + - packages/playwright-core/src/server/bidi/* schedule: # Run every day at midnight - cron: '0 0 * * *' From 7d3a1885304f8ce0f0b313b30b2649fd69b3c889 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Thu, 19 Dec 2024 12:14:58 -0800 Subject: [PATCH 05/83] chore(ui): Clean up settings component for shared uses (#34090) --- packages/trace-viewer/src/ui/settingsView.css | 25 ++++++------ packages/trace-viewer/src/ui/settingsView.tsx | 38 ++++++++++++------- packages/trace-viewer/src/ui/uiModeView.tsx | 8 ++-- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/packages/trace-viewer/src/ui/settingsView.css b/packages/trace-viewer/src/ui/settingsView.css index 3ac8597e35..f759f0b07a 100644 --- a/packages/trace-viewer/src/ui/settingsView.css +++ b/packages/trace-viewer/src/ui/settingsView.css @@ -16,24 +16,25 @@ .settings-view { flex: none; - margin-top: 4px; + padding: 4px 0px; + row-gap: 8px; + user-select: none; +} + +.settings-view .setting { + display: flex; + align-items: center; } .settings-view .setting label { - display: flex; - flex-direction: row; - align-items: center; - margin: 4px 2px; -} + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; -.settings-view .setting:first-of-type label { - margin-top: 2px; -} - -.settings-view .setting:last-of-type label { - margin-bottom: 2px; + cursor: pointer; } .settings-view .setting input { margin-right: 5px; + flex-shrink: 0; } diff --git a/packages/trace-viewer/src/ui/settingsView.tsx b/packages/trace-viewer/src/ui/settingsView.tsx index 0a4340b2b6..69439e773c 100644 --- a/packages/trace-viewer/src/ui/settingsView.tsx +++ b/packages/trace-viewer/src/ui/settingsView.tsx @@ -18,22 +18,32 @@ import * as React from 'react'; import './settingsView.css'; export type Setting = { - value: T, - set: (value: T) => void, - title: string + value: T; + set: (value: T) => void; + name: string; + title?: string; }; export const SettingsView: React.FunctionComponent<{ - settings: Setting[], + settings: Setting[]; }> = ({ settings }) => { - return
- {settings.map(({ value, set, title }) => { - return
- -
; - })} -
; + return ( +
+ {settings.map(({ value, set, name, title }) => { + const labelId = `setting-${name}`; + + return ( +
+ set(!value)} + /> + +
+ ); + })} +
+ ); }; diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index aa15e5a0a5..6ae2fdbd8c 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -508,9 +508,9 @@ export const UIModeView: React.FC<{}> = ({
Testing Options
{testingOptionsVisible && } } setSettingsVisible(!settingsVisible)}> @@ -522,7 +522,7 @@ export const UIModeView: React.FC<{}> = ({
Settings
{settingsVisible && } } From edd789780ac473083f6fb0b8740faddc43b439ab Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Thu, 19 Dec 2024 21:26:01 +0100 Subject: [PATCH 06/83] WebDriver BiDi: "browsingContext.captureScreenshot" accepts quality from 0 to 1 (#34097) --- packages/playwright-core/src/server/bidi/bidiPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index 9b501c5484..cf0662738b 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -399,7 +399,7 @@ export class BidiPage implements PageDelegate { context: this._session.sessionId, format: { type: `image/${format === 'png' ? 'png' : 'jpeg'}`, - quality: quality || 80, + quality: quality ? quality / 100 : 0.8, }, origin: documentRect ? 'document' : 'viewport', clip: { From ec1d3313c3796504a967612206c42bfad0991e1b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 19 Dec 2024 12:46:39 -0800 Subject: [PATCH 07/83] Revert "feat(fetch/network): add generic to json method" (#34098) --- packages/playwright-core/src/client/fetch.ts | 4 ++-- packages/playwright-core/src/client/network.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 2cfbbff14b..58928532ac 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -350,9 +350,9 @@ export class APIResponse implements api.APIResponse { return content.toString('utf8'); } - async json(): Promise { + async json(): Promise { const content = await this.text(); - return JSON.parse(content) as T; + return JSON.parse(content); } async [Symbol.asyncDispose]() { diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 25007db199..cb18681ccf 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -718,9 +718,9 @@ export class Response extends ChannelOwner implements return content.toString('utf8'); } - async json(): Promise { + async json(): Promise { const content = await this.text(); - return JSON.parse(content) as T; + return JSON.parse(content); } request(): Request { From 6505a3e34cc9e2dad8e55c6b5bc713710ded5d14 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 19 Dec 2024 12:46:54 -0800 Subject: [PATCH 08/83] fix(yaml): escape to disambiguate yaml arrays (#34096) --- packages/playwright-core/src/server/injected/yaml.ts | 4 ++++ tests/page/page-aria-snapshot.spec.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/packages/playwright-core/src/server/injected/yaml.ts b/packages/playwright-core/src/server/injected/yaml.ts index 7daebc574a..a9365c15bd 100644 --- a/packages/playwright-core/src/server/injected/yaml.ts +++ b/packages/playwright-core/src/server/injected/yaml.ts @@ -82,6 +82,10 @@ function yamlStringNeedsQuotes(str: string): boolean { if (/[{}`]/.test(str)) return true; + // YAML array starts with [ + if (/^\[/.test(str)) + return true; + // Non-string types recognized by YAML if (!isNaN(Number(str)) || ['y', 'n', 'yes', 'no', 'true', 'false', 'on', 'off', 'null'].includes(str.toLowerCase())) return true; diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index 77dc45a01e..1dafe14fa7 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -482,6 +482,7 @@ it('should escape yaml text in text nodes', async ({ page }) => { {four} [five] +
[Select all]
`); await checkAndMatchSnapshot(page.locator('body'), ` @@ -504,6 +505,7 @@ it('should escape yaml text in text nodes', async ({ page }) => { - text: "} [" - link "five" - text: "]" + - text: "[Select all]" `); }); From 61ce37cd53c25ff8de23e8b379ee2e480d987471 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 19 Dec 2024 22:09:49 +0100 Subject: [PATCH 09/83] test: use checkInstalledSoftwareOnDisk for itest (#34103) --- tests/installation/npmTest.ts | 13 ++++--- tests/installation/npx-global.spec.ts | 12 +++--- tests/installation/playwright-cdn.spec.ts | 4 +- ...playwright-cli-install-should-work.spec.ts | 14 +++---- ...aywright-packages-install-behavior.spec.ts | 38 +++++++++---------- .../skip-browser-download.spec.ts | 4 +- 6 files changed, 44 insertions(+), 41 deletions(-) diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index ab2b9b2a88..4801f967e8 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -75,7 +75,7 @@ type NPMTestFixtures = { _auto: void; _browsersPath: string; tmpWorkspace: string; - installedSoftwareOnDisk: () => Promise; + checkInstalledSoftwareOnDisk: (browsers: string[]) => Promise; writeFiles: (nameToContents: Record) => Promise; exec: (cmd: string, ...argsAndOrOptions: ArgsOrOptions) => Promise; tsc: (args: string) => Promise; @@ -146,10 +146,13 @@ export const test = _test await use(registry); await registry.shutdown(); }, - installedSoftwareOnDisk: async ({ isolateBrowsers, _browsersPath }, use) => { - if (!isolateBrowsers) - throw new Error(`Test that checks browser installation must set "isolateBrowsers" to true`); - await use(async () => fs.promises.readdir(_browsersPath).catch(() => []).then(files => files.map(f => f.split('-')[0].replace(/_/g, '-')).filter(f => !f.startsWith('.')))); + checkInstalledSoftwareOnDisk: async ({ isolateBrowsers, _browsersPath }, use) => { + await use(async expected => { + if (!isolateBrowsers) + throw new Error(`Test that checks browser installation must set "isolateBrowsers" to true`); + const actual = await fs.promises.readdir(_browsersPath).catch(() => []).then(files => files.map(f => f.split('-')[0].replace(/_/g, '-')).filter(f => !f.startsWith('.'))); + expect(new Set(actual)).toEqual(new Set(expected)); + }); }, exec: async ({ tmpWorkspace, _browsersPath, isolateBrowsers }, use, testInfo) => { await use(async (cmd: string, ...argsAndOrOptions: [] | [...string[]] | [...string[], ExecOptions] | [ExecOptions]) => { diff --git a/tests/installation/npx-global.spec.ts b/tests/installation/npx-global.spec.ts index 6a3da572ef..0df4d4829b 100755 --- a/tests/installation/npx-global.spec.ts +++ b/tests/installation/npx-global.spec.ts @@ -17,26 +17,26 @@ import { test, expect } from './npmTest'; test.use({ isolateBrowsers: true, allowGlobalInstall: true }); -test('npx playwright --help should not download browsers', async ({ exec, installedSoftwareOnDisk }) => { +test('npx playwright --help should not download browsers', async ({ exec, checkInstalledSoftwareOnDisk }) => { const result = await exec('npx playwright --help'); expect(result).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); expect(result).not.toContain(`To avoid unexpected behavior, please install your dependencies first`); }); -test('npx playwright codegen', async ({ exec, installedSoftwareOnDisk }) => { +test('npx playwright codegen', async ({ exec, checkInstalledSoftwareOnDisk }) => { const stdio = await exec('npx playwright codegen', { expectToExitWithError: true }); expect(stdio).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); expect(stdio).toContain(`Please run the following command to download new browsers`); }); -test('npx playwright install global', async ({ exec, installedSoftwareOnDisk }) => { +test('npx playwright install global', async ({ exec, checkInstalledSoftwareOnDisk }) => { test.skip(process.platform === 'win32', 'isLikelyNpxGlobal() does not work in this setup on our bots'); const result = await exec('npx playwright install'); expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); expect(result).not.toContain(`Please run the following command to download new browsers`); expect(result).toContain(`To avoid unexpected behavior, please install your dependencies first`); }); diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index 3b472625e9..9b74345025 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -38,11 +38,11 @@ const parsedDownloads = (rawLogs: string) => { test.use({ isolateBrowsers: true }); for (const cdn of CDNS) { - test(`playwright cdn failover should work (${cdn})`, async ({ exec, installedSoftwareOnDisk }) => { + test(`playwright cdn failover should work (${cdn})`, async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); const result = await exec('npx playwright install', { env: { PW_TEST_CDN_THAT_SHOULD_WORK: cdn, DEBUG: 'pw:install' } }); expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); const dls = parsedDownloads(result); for (const software of ['chromium', 'ffmpeg', 'firefox', 'webkit']) expect(dls).toContainEqual({ status: 200, name: software, url: expect.stringContaining(cdn) }); diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index d1a8bd3d04..064568f3e2 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -19,19 +19,19 @@ import path from 'path'; test.use({ isolateBrowsers: true }); -test('install command should work', async ({ exec, installedSoftwareOnDisk }) => { +test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await test.step('playwright install chromium', async () => { const result = await exec('npx playwright install chromium'); expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); }); await test.step('playwright install', async () => { const result = await exec('npx playwright install'); expect(result).toHaveLoggedSoftwareDownload(['firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); }); await exec('node sanity.js playwright', { env: { PLAYWRIGHT_BROWSERS_PATH: '0' } }); @@ -48,12 +48,12 @@ test('install command should work', async ({ exec, installedSoftwareOnDisk }) => } }); -test('should be able to remove browsers', async ({ exec, installedSoftwareOnDisk }) => { +test('should be able to remove browsers', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await exec('npx playwright install chromium'); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); await exec('npx playwright uninstall'); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); }); test('should print the right install command without browsers', async ({ exec }) => { @@ -91,7 +91,7 @@ test('subsequent installs works', async ({ exec }) => { await exec('node --unhandled-rejections=strict', path.join('node_modules', '@playwright', 'browser-chromium', 'install.js')); }); -test('install playwright-chromium should work', async ({ exec, installedSoftwareOnDisk }) => { +test('install playwright-chromium should work', async ({ exec }) => { await exec('npm i playwright-chromium'); await exec('npx playwright install chromium'); await exec('node sanity.js playwright-chromium chromium'); diff --git a/tests/installation/playwright-packages-install-behavior.spec.ts b/tests/installation/playwright-packages-install-behavior.spec.ts index 9975abce99..9ace1eaa67 100755 --- a/tests/installation/playwright-packages-install-behavior.spec.ts +++ b/tests/installation/playwright-packages-install-behavior.spec.ts @@ -19,7 +19,7 @@ import { test, expect } from './npmTest'; test.use({ isolateBrowsers: true }); for (const browser of ['chromium', 'firefox', 'webkit']) { - test(`playwright-${browser} should work`, async ({ exec, installedSoftwareOnDisk }) => { + test(`playwright-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `playwright-${browser}`; const result = await exec('npm i --foreground-scripts', pkg); const browserName = pkg.split('-')[1]; @@ -27,7 +27,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { if (browserName === 'chromium') expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); expect(result).toHaveLoggedSoftwareDownload(expectedSoftware as any); - expect(await installedSoftwareOnDisk()).toEqual(expectedSoftware); + await checkInstalledSoftwareOnDisk(expectedSoftware); expect(result).not.toContain(`To avoid unexpected behavior, please install your dependencies first`); await exec('node sanity.js', pkg, browser); await exec('node', `esm-${pkg}.mjs`); @@ -35,7 +35,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { } for (const browser of ['chromium', 'firefox', 'webkit']) { - test(`@playwright/browser-${browser} should work`, async ({ exec, installedSoftwareOnDisk }) => { + test(`@playwright/browser-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `@playwright/browser-${browser}`; const expectedSoftware = [browser]; if (browser === 'chromium') @@ -43,67 +43,67 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { const result1 = await exec('npm i --foreground-scripts', pkg); expect(result1).toHaveLoggedSoftwareDownload(expectedSoftware as any); - expect(await installedSoftwareOnDisk()).toEqual(expectedSoftware); + await checkInstalledSoftwareOnDisk(expectedSoftware); expect(result1).not.toContain(`To avoid unexpected behavior, please install your dependencies first`); const result2 = await exec('npm i --foreground-scripts playwright'); expect(result2).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual(expectedSoftware); + await checkInstalledSoftwareOnDisk(expectedSoftware); await exec('node sanity.js playwright', browser); await exec('node browser-only.js', pkg); }); } -test(`playwright-core should work`, async ({ exec, installedSoftwareOnDisk }) => { +test(`playwright-core should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts playwright-core'); expect(result1).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); const stdio = await exec('npx playwright-core', 'test', '-c', '.', { expectToExitWithError: true }); expect(stdio).toContain(`Please install @playwright/test package`); }); -test(`playwright should work`, async ({ exec, installedSoftwareOnDisk }) => { +test(`playwright should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts playwright'); expect(result1).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install'); expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); await exec('node sanity.js playwright chromium firefox webkit'); await exec('node esm-playwright.mjs'); }); -test(`playwright should work with chromium --no-shell`, async ({ exec, installedSoftwareOnDisk }) => { +test(`playwright should work with chromium --no-shell`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts playwright'); expect(result1).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install chromium --no-shell'); expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg']); + await checkInstalledSoftwareOnDisk(['chromium', 'ffmpeg']); }); -test(`playwright should work with chromium --only-shell`, async ({ exec, installedSoftwareOnDisk }) => { +test(`playwright should work with chromium --only-shell`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts playwright'); expect(result1).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install --only-shell'); expect(result2).toHaveLoggedSoftwareDownload(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); }); -test('@playwright/test should work', async ({ exec, installedSoftwareOnDisk }) => { +test('@playwright/test should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts @playwright/test'); expect(result1).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); await exec('npx playwright test -c . sample.spec.js', { expectToExitWithError: true, message: 'should not be able to run tests without installing browsers' }); const result2 = await exec('npx playwright install'); expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); await exec('node sanity.js @playwright/test chromium firefox webkit'); await exec('node', 'esm-playwright-test.mjs'); diff --git a/tests/installation/skip-browser-download.spec.ts b/tests/installation/skip-browser-download.spec.ts index 7cf9cdf602..21d4443dca 100755 --- a/tests/installation/skip-browser-download.spec.ts +++ b/tests/installation/skip-browser-download.spec.ts @@ -17,10 +17,10 @@ import { test, expect } from './npmTest'; test.use({ isolateBrowsers: true }); -test('should skip browser installs', async ({ exec, installedSoftwareOnDisk }) => { +test('should skip browser installs', async ({ exec, checkInstalledSoftwareOnDisk }) => { const result = await exec('npm i --foreground-scripts playwright @playwright/browser-firefox', { env: { PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' } }); expect(result).toHaveLoggedSoftwareDownload([]); - expect(await installedSoftwareOnDisk()).toEqual([]); + await checkInstalledSoftwareOnDisk([]); expect(result).toContain(`Skipping browsers download because`); if (process.platform === 'linux') { From d7a52347e5190170a794ca561bae6d499a42b9fb Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 19 Dec 2024 14:04:05 -0800 Subject: [PATCH 10/83] chore(bidi): skip tooling tests (#34105) --- tests/bidi/playwright.config.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index adaef490ea..481fa42434 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -66,7 +66,6 @@ const config: Config Date: Thu, 19 Dec 2024 23:29:21 +0100 Subject: [PATCH 11/83] chore: move winldd to CDN (#34078) --- packages/playwright-core/bin/PrintDeps.exe | Bin 275456 -> 0 bytes packages/playwright-core/bin/README.md | 2 - packages/playwright-core/browsers.json | 5 ++ packages/playwright-core/src/cli/program.ts | 3 + .../src/server/registry/dependencies.ts | 10 ++-- .../src/server/registry/index.ts | 56 +++++++++++++++++- tests/installation/npmTest.ts | 8 +-- tests/installation/playwright-cdn.spec.ts | 6 +- ...playwright-cli-install-should-work.spec.ts | 12 ++-- ...aywright-packages-install-behavior.spec.ts | 22 +++---- .../playwright-test-plugin.spec.ts | 1 - 11 files changed, 94 insertions(+), 31 deletions(-) delete mode 100644 packages/playwright-core/bin/PrintDeps.exe delete mode 100644 packages/playwright-core/bin/README.md diff --git a/packages/playwright-core/bin/PrintDeps.exe b/packages/playwright-core/bin/PrintDeps.exe deleted file mode 100644 index eb8ddf4d7b508ace4c40bd551fd821a05b92574f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275456 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 diff --git a/packages/playwright-core/bin/README.md b/packages/playwright-core/bin/README.md deleted file mode 100644 index 2426643de5..0000000000 --- a/packages/playwright-core/bin/README.md +++ /dev/null @@ -1,2 +0,0 @@ -See building instructions at [`/browser_patches/winldd/README.md`](../../../browser_patches/winldd/README.md) - diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 0b673fef08..a163c7a607 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -52,6 +52,11 @@ "mac12-arm64": "1010" } }, + { + "name": "winldd", + "revision": "1007", + "installByDefault": false + }, { "name": "android", "revision": "1001", diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 7ce1c4f928..5cd941d5d4 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -116,6 +116,9 @@ function checkBrowsersToInstall(args: string[], options: { noShell?: boolean, on } } + if (process.platform === 'win32') + executables.push(registry.findExecutable('winldd')!); + if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`); return executables; diff --git a/packages/playwright-core/src/server/registry/dependencies.ts b/packages/playwright-core/src/server/registry/dependencies.ts index 44f56f3d2f..60ef80d846 100644 --- a/packages/playwright-core/src/server/registry/dependencies.ts +++ b/packages/playwright-core/src/server/registry/dependencies.ts @@ -21,7 +21,7 @@ import childProcess from 'child_process'; import * as utils from '../../utils'; import { spawnAsync } from '../../utils/spawnAsync'; import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; -import { buildPlaywrightCLICommand } from '.'; +import { buildPlaywrightCLICommand, registry } from '.'; import { deps } from './nativeDeps'; import { getPlaywrightVersion } from '../../utils/userAgent'; @@ -122,12 +122,12 @@ export async function installDependenciesLinux(targets: Set, dr }); } -export async function validateDependenciesWindows(windowsExeAndDllDirectories: string[]) { +export async function validateDependenciesWindows(sdkLanguage: string, windowsExeAndDllDirectories: string[]) { const directoryPaths = windowsExeAndDllDirectories; const lddPaths: string[] = []; for (const directoryPath of directoryPaths) lddPaths.push(...(await executablesOrSharedLibraries(directoryPath))); - const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath))); + const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(sdkLanguage, lddPath))); const missingDeps: Set = new Set(); for (const deps of allMissingDeps) { for (const dep of deps) @@ -302,8 +302,8 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise> { - const executable = path.join(__dirname, '..', '..', '..', 'bin', 'PrintDeps.exe'); +async function missingFileDependenciesWindows(sdkLanguage: string, filePath: string): Promise> { + const executable = registry.findExecutable('winldd')!.executablePathOrDie(sdkLanguage); const dirname = path.dirname(filePath); const { stdout, code } = await spawnAsync(executable, [filePath], { cwd: dirname, diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index f12d5ceb4c..c4d7f2ffbb 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -79,6 +79,11 @@ const EXECUTABLE_PATHS = { 'mac': ['ffmpeg-mac'], 'win': ['ffmpeg-win64.exe'], }, + 'winldd': { + 'linux': undefined, + 'mac': undefined, + 'win': ['PrintDeps.exe'], + }, }; type DownloadPaths = Record; @@ -315,6 +320,35 @@ const DOWNLOAD_PATHS: Record = { 'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip', 'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip', }, + 'winldd': { + '': undefined, + 'ubuntu18.04-x64': undefined, + 'ubuntu20.04-x64': undefined, + 'ubuntu22.04-x64': undefined, + 'ubuntu24.04-x64': undefined, + 'ubuntu18.04-arm64': undefined, + 'ubuntu20.04-arm64': undefined, + 'ubuntu22.04-arm64': undefined, + 'ubuntu24.04-arm64': undefined, + 'debian11-x64': undefined, + 'debian11-arm64': undefined, + 'debian12-x64': undefined, + 'debian12-arm64': undefined, + 'mac10.13': undefined, + 'mac10.14': undefined, + 'mac10.15': undefined, + 'mac11': undefined, + 'mac11-arm64': undefined, + 'mac12': undefined, + 'mac12-arm64': undefined, + 'mac13': undefined, + 'mac13-arm64': undefined, + 'mac14': undefined, + 'mac14-arm64': undefined, + 'mac15': undefined, + 'mac15-arm64': undefined, + 'win64': 'builds/winldd/%s/winldd-win64.zip', + }, 'android': { '': 'builds/android/%s/android.zip', 'ubuntu18.04-x64': undefined, @@ -442,7 +476,7 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { } export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; -type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; +type InternalTool = 'ffmpeg' | 'winldd' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium'; type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell', 'chromium-tip-of-tree-headless-shell']; @@ -772,6 +806,22 @@ export class Registry { _dependencyGroup: 'tools', _isHermeticInstallation: true, }); + const winldd = descriptors.find(d => d.name === 'winldd')!; + const winlddExecutable = findExecutablePath(winldd.dir, 'winldd'); + this._executables.push({ + type: 'tool', + name: 'winldd', + browserName: undefined, + directory: winldd.dir, + executablePath: () => winlddExecutable, + executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('winldd', winlddExecutable, winldd.installByDefault, sdkLanguage), + installType: process.platform === 'win32' ? 'download-by-default' : 'none', + _validateHostRequirements: () => Promise.resolve(), + downloadURLs: this._downloadURLs(winldd), + _install: () => this._downloadExecutable(winldd, winlddExecutable), + _dependencyGroup: 'tools', + _isHermeticInstallation: true, + }); const android = descriptors.find(d => d.name === 'android')!; this._executables.push({ type: 'tool', @@ -944,7 +994,7 @@ export class Registry { if (os.platform() === 'linux') return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries); if (os.platform() === 'win32' && os.arch() === 'x64') - return await validateDependenciesWindows(windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d))); + return await validateDependenciesWindows(sdkLanguage, windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d))); } async installDeps(executablesToInstallDeps: Executable[], dryRun: boolean) { @@ -1265,6 +1315,8 @@ export async function installBrowsersForNpmInstall(browsers: string[]) { return false; } const executables: Executable[] = []; + if (process.platform === 'win32') + executables.push(registry.findExecutable('winldd')!); for (const browserName of browsers) { const executable = registry.findExecutable(browserName); if (!executable || executable.installType === 'none') diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 4801f967e8..4e39fb8c98 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -31,22 +31,22 @@ export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : os const debug = debugLogger('itest'); const expect = _expect.extend({ - toHaveLoggedSoftwareDownload(received: any, browsers: ('chromium' | 'chromium-headless-shell' | 'firefox' | 'webkit' | 'ffmpeg')[]) { + toHaveLoggedSoftwareDownload(received: string, browsers: ('chromium' | 'chromium-headless-shell' | 'firefox' | 'webkit' | 'winldd' |'ffmpeg')[]) { if (typeof received !== 'string') throw new Error(`Expected argument to be a string.`); const downloaded = new Set(); let index = 0; while (true) { - const match = received.substring(index).match(/(chromium|chromium headless shell|firefox|webkit|ffmpeg)[\s\d\.]+\(?playwright build v\d+\)? downloaded/im); + const match = received.substring(index).match(/(chromium|chromium headless shell|firefox|webkit|winldd|ffmpeg)[\s\d\.]+\(?playwright build v\d+\)? downloaded/im); if (!match) break; downloaded.add(match[1].replace(/\s/g, '-').toLowerCase()); index += match.index + 1; } - const expected = browsers; - if (expected.length === downloaded.size && expected.every(browser => downloaded.has(browser))) { + const expected = new Set(browsers); + if (expected.size === downloaded.size && [...expected].every(browser => downloaded.has(browser))) { return { pass: true, message: () => 'Expected not to download browsers, but did.' diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index 9b74345025..776aec42ad 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -37,12 +37,14 @@ const parsedDownloads = (rawLogs: string) => { test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + for (const cdn of CDNS) { test(`playwright cdn failover should work (${cdn})`, async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); const result = await exec('npx playwright install', { env: { PW_TEST_CDN_THAT_SHOULD_WORK: cdn, DEBUG: 'pw:install' } }); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk((['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware])); const dls = parsedDownloads(result); for (const software of ['chromium', 'ffmpeg', 'firefox', 'webkit']) expect(dls).toContainEqual({ status: 200, name: software, url: expect.stringContaining(cdn) }); diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index 064568f3e2..2825a9a44f 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -19,19 +19,21 @@ import path from 'path'; test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await test.step('playwright install chromium', async () => { const result = await exec('npx playwright install chromium'); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); }); await test.step('playwright install', async () => { const result = await exec('npx playwright install'); expect(result).toHaveLoggedSoftwareDownload(['firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); }); await exec('node sanity.js playwright', { env: { PLAYWRIGHT_BROWSERS_PATH: '0' } }); @@ -51,9 +53,9 @@ test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk test('should be able to remove browsers', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await exec('npx playwright install chromium'); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); await exec('npx playwright uninstall'); - await checkInstalledSoftwareOnDisk([]); + await checkInstalledSoftwareOnDisk([...extraInstalledSoftware]); }); test('should print the right install command without browsers', async ({ exec }) => { diff --git a/tests/installation/playwright-packages-install-behavior.spec.ts b/tests/installation/playwright-packages-install-behavior.spec.ts index 9ace1eaa67..2474d889eb 100755 --- a/tests/installation/playwright-packages-install-behavior.spec.ts +++ b/tests/installation/playwright-packages-install-behavior.spec.ts @@ -18,12 +18,14 @@ import { test, expect } from './npmTest'; test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + for (const browser of ['chromium', 'firefox', 'webkit']) { test(`playwright-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `playwright-${browser}`; const result = await exec('npm i --foreground-scripts', pkg); const browserName = pkg.split('-')[1]; - const expectedSoftware = [browserName]; + const expectedSoftware = [browserName, ...extraInstalledSoftware]; if (browserName === 'chromium') expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); expect(result).toHaveLoggedSoftwareDownload(expectedSoftware as any); @@ -37,7 +39,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { for (const browser of ['chromium', 'firefox', 'webkit']) { test(`@playwright/browser-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `@playwright/browser-${browser}`; - const expectedSoftware = [browser]; + const expectedSoftware = [browser, ...extraInstalledSoftware]; if (browser === 'chromium') expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); @@ -69,8 +71,8 @@ test(`playwright should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); await exec('node sanity.js playwright chromium firefox webkit'); await exec('node esm-playwright.mjs'); @@ -81,8 +83,8 @@ test(`playwright should work with chromium --no-shell`, async ({ exec, checkInst expect(result1).toHaveLoggedSoftwareDownload([]); await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install chromium --no-shell'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg']); - await checkInstalledSoftwareOnDisk(['chromium', 'ffmpeg']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'ffmpeg', ...extraInstalledSoftware]); }); test(`playwright should work with chromium --only-shell`, async ({ exec, checkInstalledSoftwareOnDisk }) => { @@ -90,8 +92,8 @@ test(`playwright should work with chromium --only-shell`, async ({ exec, checkIn expect(result1).toHaveLoggedSoftwareDownload([]); await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install --only-shell'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); }); test('@playwright/test should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { @@ -102,8 +104,8 @@ test('@playwright/test should work', async ({ exec, checkInstalledSoftwareOnDisk await exec('npx playwright test -c . sample.spec.js', { expectToExitWithError: true, message: 'should not be able to run tests without installing browsers' }); const result2 = await exec('npx playwright install'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); await exec('node sanity.js @playwright/test chromium firefox webkit'); await exec('node', 'esm-playwright-test.mjs'); diff --git a/tests/installation/playwright-test-plugin.spec.ts b/tests/installation/playwright-test-plugin.spec.ts index 5762b49900..fe4d24cd93 100755 --- a/tests/installation/playwright-test-plugin.spec.ts +++ b/tests/installation/playwright-test-plugin.spec.ts @@ -26,7 +26,6 @@ function patchPackageJsonForPreReleaseIfNeeded(tmpWorkspace: string) { // Workaround per https://stackoverflow.com/questions/71479750/npm-install-pre-release-versions-for-peer-dependency. const pkg = JSON.parse(fs.readFileSync(path.resolve(tmpWorkspace, 'package.json'), 'utf-8')); if (pkg.dependencies['@playwright/test'].match(/\d+\.\d+-\w+/)) { - console.log(`Setting overrides in package.json to make pre-release version of peer dependency work.`); pkg.overrides = { '@playwright/test': '$@playwright/test' }; fs.writeFileSync(path.resolve(tmpWorkspace, 'package.json'), JSON.stringify(pkg, null, 2)); } From 04e670c909969869cb45ed3061dc17f726501b47 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 19 Dec 2024 15:34:54 -0800 Subject: [PATCH 12/83] fix(locator): do not explode locators (#34104) --- .../src/utils/isomorphic/locatorGenerators.ts | 4 ++-- tests/library/locator-generator.spec.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts index 04f3040547..355d0cec5f 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts @@ -36,7 +36,7 @@ export interface LocatorFactory { } export function asLocator(lang: Language, selector: string, isFrameLocator: boolean = false): string { - return asLocators(lang, selector, isFrameLocator)[0]; + return asLocators(lang, selector, isFrameLocator, 1)[0]; } export function asLocators(lang: Language, selector: string, isFrameLocator: boolean = false, maxOutputSize = 20, preferredQuote?: Quote): string[] { @@ -220,7 +220,7 @@ function combineTokens(factory: LocatorFactory, tokens: string[][], maxOutputSiz const visit = (index: number) => { if (index === tokens.length) { result.push(factory.chainLocators(currentTokens)); - return currentTokens.length < maxOutputSize; + return result.length < maxOutputSize; } for (const taken of tokens[index]) { currentTokens[index] = taken; diff --git a/tests/library/locator-generator.spec.ts b/tests/library/locator-generator.spec.ts index cceff133d4..417e096e29 100644 --- a/tests/library/locator-generator.spec.ts +++ b/tests/library/locator-generator.spec.ts @@ -615,3 +615,27 @@ it('parseLocator frames', async () => { expect.soft(parseLocator('java', `locator("iframe").contentFrame().getByText("foo")`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); expect.soft(parseLocator('java', `frameLocator("iframe").getByText("foo")`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); }); + +it('should not oom in locator parser', async ({ page }) => { + const l = page.locator.bind(page); + const locator = page.locator('text=L1').or(l('text=L2').or(l('text=L3').or(l('text=L4')).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L5').or(l('text=L6'))).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L7') + .or(l('text=L8'))))).or(l('text=L9').or(l('text=L10').or(l('text=L11')).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L12').or(l('text=L13'))).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L14').or(l('text=L15'))))).or(l('text=L16').or(l('text=L17').or(l('text=L18')).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L19').or(l('text=L20'))).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L21').or(l('text=L22'))))).or(l('text=L23').or(l('text=L24').or(l('text=L25')).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L26').or(l('text=L27'))).or(l('#f0') + .contentFrame().locator('#f0_mid_0') + .contentFrame().locator('text=L28').or(l('text=L29'))))); + const error = await locator.count().catch(e => e); + expect(error.message).toContain('Frame locators are not allowed inside composite locators'); +}); From a94952b87fc6164601133f1020c94b6f2cecc694 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 19 Dec 2024 22:59:05 -0800 Subject: [PATCH 13/83] chore: make ts happy with zip import (#34108) --- packages/trace-viewer/src/sw/traceModelBackends.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trace-viewer/src/sw/traceModelBackends.ts b/packages/trace-viewer/src/sw/traceModelBackends.ts index 95efffd502..ee694b2fba 100644 --- a/packages/trace-viewer/src/sw/traceModelBackends.ts +++ b/packages/trace-viewer/src/sw/traceModelBackends.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type zip from '@zip.js/zip.js'; +import type * as zip from '@zip.js/zip.js'; // @ts-ignore import * as zipImport from '@zip.js/zip.js/lib/zip-no-worker-inflate.js'; import type { TraceModelBackend } from './traceModel'; From cc98166aaa6341bde2293cad6420abbbd43b974b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 19 Dec 2024 22:59:24 -0800 Subject: [PATCH 14/83] chore(bidi): add csv report (#34107) --- .github/workflows/tests_bidi.yml | 7 +++ tests/bidi/csvReporter.ts | 82 ++++++++++++++++++++++++++++++++ tests/bidi/playwright.config.ts | 1 + 3 files changed, 90 insertions(+) create mode 100644 tests/bidi/csvReporter.ts diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index 4fee45e34c..db54550a4c 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -44,3 +44,10 @@ jobs: run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run biditest -- --project=${{ matrix.channel }}* env: PWTEST_USE_BIDI_EXPECTATIONS: '1' + - name: Upload csv report to GitHub + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: csv-report + path: test-results/report.csv + retention-days: 7 diff --git a/tests/bidi/csvReporter.ts b/tests/bidi/csvReporter.ts new file mode 100644 index 0000000000..8fb936dd11 --- /dev/null +++ b/tests/bidi/csvReporter.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + FullConfig, FullResult, Reporter, Suite +} from '@playwright/test/reporter'; +import { stripAnsi } from '../config/utils'; +import fs from 'fs'; +import path from 'path'; + + +type ReporterOptions = { + outputFile?: string, + configDir: string, +}; + +class CsvReporter implements Reporter { + private _suite: Suite; + private _options: ReporterOptions; + private _pendingWrite: Promise; + + constructor(options: ReporterOptions) { + this._options = options; + } + + onBegin(config: FullConfig, suite: Suite) { + this._suite = suite; + } + + onEnd(result: FullResult) { + const rows = [['File Name', 'Test Name', 'Expected Status', 'Status', 'Error Message']]; + for (const project of this._suite.suites) { + for (const file of project.suites) { + for (const test of file.allTests()) { + if (test.ok()) + continue; + const row = []; + row.push(file.title); + row.push(csvEscape(test.title)); + row.push(test.expectedStatus); + row.push(test.outcome()); + const result = test.results.find(r => r.error); + const errorMessage = stripAnsi(result?.error?.message.replace(/\s+/g, ' ').trim().substring(0, 1024)); + row.push(csvEscape(errorMessage ?? '')); + rows.push(row); + } + } + } + const csv = rows.map(r => r.join(',')).join('\n'); + const reportFile = path.resolve(this._options.configDir, this._options.outputFile || 'test-results.csv'); + this._pendingWrite = fs.promises.writeFile(reportFile, csv); + } + + async onExit() { + await this._pendingWrite; + } + + printsToStdio(): boolean { + return false; + } +} + +function csvEscape(str) { + if (str.includes('"') || str.includes(',') || str.includes('\n')) + return `"${str.replace(/"/g, '""')}"`; + return str; +} + +export default CsvReporter; \ No newline at end of file diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index 481fa42434..8dbe5b2b9d 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -39,6 +39,7 @@ const reporters = () => { hasDebugOutput ? ['list'] : ['dot'], ['json', { outputFile: path.join(outputDir, 'report.json') }], ['blob', { fileName: `${process.env.PWTEST_BOT_NAME}.zip` }], + ['./csvReporter', { outputFile: path.join(outputDir, 'report.csv') }], ] : [ ['html', { open: 'on-failure' }], ['./expectationReporter', { rebase: false }], From 05472f5ef69a440d04ede3e85c94b1a9bc6a5682 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 20 Dec 2024 05:01:16 -0800 Subject: [PATCH 15/83] feat: Add time information to Call and Network tabs in Trace Viewer (#33935) --- packages/trace-viewer/src/ui/callTab.tsx | 67 +++++++++++-------- packages/trace-viewer/src/ui/metadataView.tsx | 4 +- .../src/ui/networkResourceDetails.tsx | 14 ++-- packages/trace-viewer/src/ui/networkTab.tsx | 2 +- packages/trace-viewer/src/ui/workbench.tsx | 2 +- tests/library/trace-viewer.spec.ts | 6 +- 6 files changed, 58 insertions(+), 37 deletions(-) diff --git a/packages/trace-viewer/src/ui/callTab.tsx b/packages/trace-viewer/src/ui/callTab.tsx index 1ab3b5b46f..1c78920f86 100644 --- a/packages/trace-viewer/src/ui/callTab.tsx +++ b/packages/trace-viewer/src/ui/callTab.tsx @@ -27,50 +27,63 @@ import type { ActionTraceEventInContext } from './modelUtil'; export const CallTab: React.FunctionComponent<{ action: ActionTraceEventInContext | undefined, + startTimeOffset: number, sdkLanguage: Language | undefined, -}> = ({ action, sdkLanguage }) => { +}> = ({ action, startTimeOffset, sdkLanguage }) => { + // We never need the waitForEventInfo (`info`). + const paramKeys = React.useMemo(() => Object.keys(action?.params ?? {}).filter(name => name !== 'info'), [action]); + if (!action) return ; - const params = { ...action.params }; - // Strip down the waitForEventInfo data, we never need it. - delete params.info; - const paramKeys = Object.keys(params); - const timeMillis = action.startTime + (action.context.wallTime - action.context.startTime); - const wallTime = new Date(timeMillis).toLocaleString(); + + // Calculate execution time relative to the test runner's start time + const startTimeMillis = action.startTime - startTimeOffset; + const startTime = msToString(startTimeMillis); + const duration = action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'; - return

-
{action.apiName}
- {<> -
Time
- {wallTime &&
wall time:{wallTime}
} -
duration:{duration}
- } - { !!paramKeys.length &&
Parameters
} - { - !!paramKeys.length && paramKeys.map((name, index) => renderProperty(propertyToString(action, name, params[name], sdkLanguage), 'param-' + index)) - } - { !!action.result &&
Return value
} - { - !!action.result && Object.keys(action.result).map((name, index) => - renderProperty(propertyToString(action, name, action.result[name], sdkLanguage), 'result-' + index) - ) - } -
; + return ( +
+
{action.apiName}
+ { + <> +
Time
+ + + + } + { + !!paramKeys.length && <> +
Parameters
+ {paramKeys.map(name => renderProperty(propertyToString(action, name, action.params[name], sdkLanguage)))} + + } + { + !!action.result && <> +
Return value
+ {Object.keys(action.result).map(name => + renderProperty(propertyToString(action, name, action.result[name], sdkLanguage)) + )} + + } +
+ ); }; +const DateTimeCallLine: React.FC<{ name: string, value: string }> = ({ name, value }) =>
{name}{value}
; + type Property = { name: string; type: 'string' | 'number' | 'object' | 'locator' | 'handle' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'function'; text: string; }; -function renderProperty(property: Property, key: string) { +function renderProperty(property: Property) { let text = property.text.replace(/\n/g, '↵'); if (property.type === 'string') text = `"${text}"`; return ( -
+
{property.name}:{text} { ['string', 'number', 'object', 'locator'].includes(property.type) && diff --git a/packages/trace-viewer/src/ui/metadataView.tsx b/packages/trace-viewer/src/ui/metadataView.tsx index c1802a4b4d..88c2e2bf93 100644 --- a/packages/trace-viewer/src/ui/metadataView.tsx +++ b/packages/trace-viewer/src/ui/metadataView.tsx @@ -25,9 +25,11 @@ export const MetadataView: React.FunctionComponent<{ if (!model) return <>; + const wallTime = model.wallTime !== undefined ? new Date(model.wallTime).toLocaleString(undefined, { timeZoneName: 'short' }) : undefined; + return
Time
- {!!model.wallTime &&
start time:{new Date(model.wallTime).toLocaleString()}
} + {!!wallTime &&
start time:{wallTime}
}
duration:{msToString(model.endTime - model.startTime)}
Browser
engine:{model.browserName}
diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx index 0c4af4e969..aaa78d1786 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx +++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx @@ -24,12 +24,14 @@ import { generateCurlCommand, generateFetchCall } from '../third_party/devtools' import { CopyToClipboardTextButton } from './copyToClipboard'; import { getAPIRequestCodeGen } from './codegen'; import type { Language } from '@isomorphic/locatorGenerators'; +import { msToString } from '@web/uiUtils'; export const NetworkResourceDetails: React.FunctionComponent<{ resource: ResourceSnapshot; - onClose: () => void; sdkLanguage: Language; -}> = ({ resource, onClose, sdkLanguage }) => { + startTimeOffset: number; + onClose: () => void; +}> = ({ resource, sdkLanguage, startTimeOffset, onClose }) => { const [selectedTab, setSelectedTab] = React.useState('request'); return , + render: () => , }, { id: 'response', @@ -59,7 +61,8 @@ export const NetworkResourceDetails: React.FunctionComponent<{ const RequestTab: React.FunctionComponent<{ resource: ResourceSnapshot; sdkLanguage: Language; -}> = ({ resource, sdkLanguage }) => { + startTimeOffset: number; +}> = ({ resource, sdkLanguage, startTimeOffset }) => { const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null); React.useEffect(() => { @@ -96,6 +99,9 @@ const RequestTab: React.FunctionComponent<{ : null}
Request Headers
{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}
+
Time
+
{`Start: ${msToString(startTimeOffset)}`}
+
{`Duration: ${msToString(resource.time)}`}
generateCurlCommand(resource)} /> diff --git a/packages/trace-viewer/src/ui/networkTab.tsx b/packages/trace-viewer/src/ui/networkTab.tsx index 56cf9325b4..ceaafdcec5 100644 --- a/packages/trace-viewer/src/ui/networkTab.tsx +++ b/packages/trace-viewer/src/ui/networkTab.tsx @@ -117,7 +117,7 @@ export const NetworkTab: React.FunctionComponent<{ sidebarIsFirst={true} orientation='horizontal' settingName='networkResourceDetails' - main={ setSelectedEntry(undefined)} sdkLanguage={sdkLanguage} />} + main={ setSelectedEntry(undefined)} />} sidebar={grid} />} ; diff --git a/packages/trace-viewer/src/ui/workbench.tsx b/packages/trace-viewer/src/ui/workbench.tsx index ad8a099ea4..7a14d495c6 100644 --- a/packages/trace-viewer/src/ui/workbench.tsx +++ b/packages/trace-viewer/src/ui/workbench.tsx @@ -176,7 +176,7 @@ export const Workbench: React.FunctionComponent<{ const callTab: TabbedPaneTabModel = { id: 'call', title: 'Call', - render: () => + render: () => }; const logTab: TabbedPaneTabModel = { id: 'log', diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 3ffba28582..53c6bcf6c4 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -240,7 +240,7 @@ test('should show params and return value', async ({ showTraceViewer }) => { await traceViewer.selectAction('page.evaluate'); await expect(traceViewer.callLines).toHaveText([ /page.evaluate/, - /wall time:[0-9/:,APM ]+/, + /start:[\d\.]+m?s/, /duration:[\d]+ms/, /expression:"\({↵ a↵ }\) => {↵ console\.log\(\'Info\'\);↵ console\.warn\(\'Warning\'\);↵ console/, 'isFunction:true', @@ -251,7 +251,7 @@ test('should show params and return value', async ({ showTraceViewer }) => { await traceViewer.selectAction(`locator('button')`); await expect(traceViewer.callLines).toContainText([ /expect.toHaveText/, - /wall time:[0-9/:,APM ]+/, + /start:[\d\.]+m?s/, /duration:[\d]+ms/, /locator:locator\('button'\)/, /expression:"to.have.text"/, @@ -266,7 +266,7 @@ test('should show null as a param', async ({ showTraceViewer, browserName }) => await traceViewer.selectAction('page.evaluate', 1); await expect(traceViewer.callLines).toHaveText([ /page.evaluate/, - /wall time:[0-9/:,APM ]+/, + /start:[\d\.]+m?s/, /duration:[\d]+ms/, 'expression:"() => 1 + 1"', 'isFunction:true', From a8dfdc8ac5b81db585888d0728b63c387b37cd33 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 20 Dec 2024 08:43:12 -0800 Subject: [PATCH 16/83] chore(ui): Dialog UI for upcoming settings menu (#34058) --- .../trace-viewer/src/ui/shared/dialog.tsx | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 packages/trace-viewer/src/ui/shared/dialog.tsx diff --git a/packages/trace-viewer/src/ui/shared/dialog.tsx b/packages/trace-viewer/src/ui/shared/dialog.tsx new file mode 100644 index 0000000000..a58119eca7 --- /dev/null +++ b/packages/trace-viewer/src/ui/shared/dialog.tsx @@ -0,0 +1,145 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import * as React from 'react'; + +export interface DialogProps { + className?: string; + open: boolean; + width: number; + verticalOffset?: number; + requestClose?: () => void; + anchor?: React.RefObject; +} + +export const Dialog: React.FC> = ({ + className, + open, + width, + verticalOffset, + requestClose, + anchor, + children, +}) => { + const dialogRef = React.useRef(null); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, setRecalculateDimensionsCount] = React.useState(0); + + let style: React.CSSProperties | undefined = undefined; + + if (anchor?.current) { + const bounds = anchor.current.getBoundingClientRect(); + + style = { + margin: 0, + top: bounds.bottom + (verticalOffset ?? 0), + left: buildTopLeftCoord(bounds, width), + width, + zIndex: 1, + }; + } + + React.useEffect(() => { + const onClick = (event: MouseEvent) => { + if (!dialogRef.current || !(event.target instanceof Node)) + return; + + if (!dialogRef.current.contains(event.target)) + requestClose?.(); + }; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') + requestClose?.(); + }; + + if (open) { + document.addEventListener('mousedown', onClick); + document.addEventListener('keydown', onKeyDown); + + return () => { + document.removeEventListener('mousedown', onClick); + document.removeEventListener('keydown', onKeyDown); + }; + } + + return () => {}; + }, [open, requestClose]); + + React.useEffect(() => { + const onResize = () => setRecalculateDimensionsCount(count => count + 1); + + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }, []); + + return ( + open && ( + + {children} + + ) + ); +}; + +const buildTopLeftCoord = (bounds: DOMRect, width: number): number => { + const leftAlignCoord = buildTopLeftCoordWithAlignment(bounds, width, 'left'); + + if (leftAlignCoord.inBounds) + return leftAlignCoord.value; + + const rightAlignCoord = buildTopLeftCoordWithAlignment( + bounds, + width, + 'right' + ); + + if (rightAlignCoord.inBounds) + return rightAlignCoord.value; + + return leftAlignCoord.value; +}; + +const buildTopLeftCoordWithAlignment = ( + bounds: DOMRect, + width: number, + alignment: 'left' | 'right' +): { + value: number; + inBounds: boolean; +} => { + const maxLeft = document.documentElement.clientWidth; + + if (alignment === 'left') { + const value = bounds.left; + + return { + value, + inBounds: value + width <= maxLeft, + }; + } else { + const value = bounds.right - width; + + return { + value, + inBounds: bounds.right - width >= 0, + }; + } +}; From 3bc72eb84136839ff15895027cc54873fc70426d Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 20 Dec 2024 08:58:15 -0800 Subject: [PATCH 17/83] chore(bidi): disambiguate report.csv artifact name (#34110) --- .github/workflows/tests_bidi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index db54550a4c..b559a87563 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -48,6 +48,6 @@ jobs: if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: - name: csv-report + name: csv-report-${{ matrix.channel }} path: test-results/report.csv retention-days: 7 From 875436855ea247f636d5d24e17d51bd96e51f24b Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 20 Dec 2024 09:17:09 -0800 Subject: [PATCH 18/83] chore(lint): Ensure EOL newlines (#34117) --- .eslintignore | 3 +++ .eslintrc.js | 3 ++- packages/html-reporter/bundle.ts | 2 +- packages/html-reporter/src/utils.ts | 1 - packages/playwright-core/src/common/types.ts | 2 +- packages/playwright-core/src/image_tools/stats.ts | 1 - packages/playwright-core/src/server/android/android.ts | 2 -- packages/playwright-core/src/server/fileUploadUtils.ts | 2 +- packages/playwright-core/src/server/firefox/ffBrowser.ts | 1 - packages/playwright-core/src/server/firefox/firefox.ts | 1 - packages/playwright-core/src/server/registry/nativeDeps.ts | 1 - .../src/server/socksClientCertificatesInterceptor.ts | 2 +- packages/playwright-core/src/server/socksInterceptor.ts | 1 - .../playwright-core/src/server/webkit/wkProvisionalPage.ts | 2 +- packages/playwright-core/src/utils/isomorphic/mimeType.ts | 2 +- packages/playwright-core/src/utils/linuxUtils.ts | 1 - packages/playwright-core/src/utils/sequence.ts | 2 +- packages/playwright/jsx-runtime.mjs | 2 +- packages/playwright/src/common/config.ts | 2 +- packages/playwright/src/runner/vcs.ts | 2 +- packages/trace-viewer/bundle.ts | 2 +- packages/trace-viewer/src/sw/traceModelBackends.ts | 2 +- packages/trace-viewer/src/third_party/devtools.ts | 2 +- packages/trace-viewer/src/ui/actionList.tsx | 2 +- packages/web/src/components/sourceChooser.tsx | 2 +- packages/web/src/components/splitView.spec.tsx | 1 - tests/android/webview.spec.ts | 2 +- tests/bidi/csvReporter.ts | 2 +- tests/bidi/expectationReporter.ts | 2 +- tests/expect/toThrowMatchers.test.ts | 2 +- tests/library/browsercontext-har.spec.ts | 2 +- tests/library/chromium/chromium.spec.ts | 2 +- tests/library/inspector/cli-codegen-test.spec.ts | 2 +- tests/library/popup.spec.ts | 2 +- tests/page/frame-evaluate.spec.ts | 1 - tests/page/locator-misc-2.spec.ts | 1 - tests/page/page-event-request.spec.ts | 2 +- tests/page/page-mouse.spec.ts | 1 - tests/page/page-request-fulfill.spec.ts | 1 - tests/page/selectors-react.spec.ts | 1 - tests/page/selectors-vue.spec.ts | 1 - tests/playwright-test/command-line-filter.spec.ts | 2 +- tests/playwright-test/only-changed.spec.ts | 1 - tests/playwright-test/playwright.reuse.browser.spec.ts | 2 +- tests/playwright-test/reporter-blob.spec.ts | 2 +- tests/playwright-test/reporter-dot.spec.ts | 2 +- tests/playwright-test/reporter-github.spec.ts | 2 +- tests/playwright-test/reporter-junit.spec.ts | 2 +- tests/playwright-test/reporter-line.spec.ts | 2 +- tests/playwright-test/reporter-list.spec.ts | 1 - tests/playwright-test/reporter-markdown.spec.ts | 2 +- tests/playwright-test/snapshot-path-template.spec.ts | 1 - tests/playwright-test/test-ignore.spec.ts | 2 +- tests/playwright-test/test-use.spec.ts | 1 - tests/playwright-test/ui-mode-test-annotations.spec.ts | 1 - utils/doclint/linting-code-snippets/cli.js | 1 + 56 files changed, 39 insertions(+), 55 deletions(-) diff --git a/.eslintignore b/.eslintignore index 9d22f618d8..fdcc76dda0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,9 @@ test/assets/modernizr.js /packages/*/lib/ *.js /packages/playwright-core/src/generated/* +/packages/playwright-core/src/protocol/debug.ts +/packages/playwright-core/src/protocol/validator.ts +/packages/playwright-core/src/server/injected/recorder/clipPaths.ts /packages/playwright-core/src/third_party/ /packages/playwright-core/types/* /packages/playwright-ct-core/src/generated/* diff --git a/.eslintrc.js b/.eslintrc.js index a116a37036..e71a4ffd09 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -115,7 +115,7 @@ module.exports = { "@typescript-eslint/type-annotation-spacing": 2, // file whitespace - "no-multiple-empty-lines": [2, {"max": 2}], + "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], "no-mixed-spaces-and-tabs": 2, "no-trailing-spaces": 2, "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ], @@ -123,6 +123,7 @@ module.exports = { "key-spacing": [2, { "beforeColon": false }], + "eol-last": 2, // copyright "notice/notice": [2, { diff --git a/packages/html-reporter/bundle.ts b/packages/html-reporter/bundle.ts index 4c6bc02632..63530cfaad 100644 --- a/packages/html-reporter/bundle.ts +++ b/packages/html-reporter/bundle.ts @@ -51,4 +51,4 @@ export function bundle(): Plugin { } }, }; -} \ No newline at end of file +} diff --git a/packages/html-reporter/src/utils.ts b/packages/html-reporter/src/utils.ts index 65404b2fe7..eec765969b 100644 --- a/packages/html-reporter/src/utils.ts +++ b/packages/html-reporter/src/utils.ts @@ -47,4 +47,3 @@ export function hashStringToInt(str: string) { hash = str.charCodeAt(i) + ((hash << 8) - hash); return Math.abs(hash % 6); } - diff --git a/packages/playwright-core/src/common/types.ts b/packages/playwright-core/src/common/types.ts index 55145b5565..f71d4237cd 100644 --- a/packages/playwright-core/src/common/types.ts +++ b/packages/playwright-core/src/common/types.ts @@ -20,4 +20,4 @@ export type Rect = Size & Point; export type Quad = [ Point, Point, Point, Point ]; export type TimeoutOptions = { timeout?: number }; export type NameValue = { name: string, value: string }; -export type HeadersArray = NameValue[]; \ No newline at end of file +export type HeadersArray = NameValue[]; diff --git a/packages/playwright-core/src/image_tools/stats.ts b/packages/playwright-core/src/image_tools/stats.ts index b35371b4a1..79b170243c 100644 --- a/packages/playwright-core/src/image_tools/stats.ts +++ b/packages/playwright-core/src/image_tools/stats.ts @@ -124,4 +124,3 @@ export class FastStats implements Stats { return (this._sum(this._partialSumMult, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) * this._sum(this._partialSumC2, x1, y1, x2, y2) / N) / N; } } - diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index 1af083916c..b6303935b7 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -524,5 +524,3 @@ class ClankBrowserProcess implements BrowserProcess { await this._browser.close(); } } - - diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 22ac13b127..2696ba0116 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -77,4 +77,4 @@ export async function prepareFilesForUpload(frame: Frame, params: channels.Eleme })); return { localPaths, localDirectory, filePayloads }; -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 92998a7946..1d1c60e6ce 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -436,4 +436,3 @@ function toJugglerProxyOptions(proxy: types.ProxySettings) { // Prefs for quick fixes that didn't make it to the build. // Should all be moved to `playwright.cfg`. const kBandaidFirefoxUserPrefs = {}; - diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index 9fbc409a56..1e0e4cc055 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -101,4 +101,3 @@ class JugglerReadyState extends BrowserReadyState { this._wsEndpoint.resolve(undefined); } } - diff --git a/packages/playwright-core/src/server/registry/nativeDeps.ts b/packages/playwright-core/src/server/registry/nativeDeps.ts index 6e25e3a14f..9653210a45 100644 --- a/packages/playwright-core/src/server/registry/nativeDeps.ts +++ b/packages/playwright-core/src/server/registry/nativeDeps.ts @@ -1104,4 +1104,3 @@ deps['debian12-arm64'] = { ...deps['debian12-x64'].lib2package, }, }; - diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 4e850f4a84..2517ea24ce 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -354,4 +354,4 @@ export function rewriteOpenSSLErrorIfNeeded(error: Error): Error { 'For more details, see https://github.com/openssl/openssl/blob/master/README-PROVIDERS.md#the-legacy-provider', 'You could probably modernize the certificate by following the steps at https://github.com/nodejs/node/issues/40672#issuecomment-1243648223', ].join('\n')); -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/server/socksInterceptor.ts b/packages/playwright-core/src/server/socksInterceptor.ts index 498e8bfe73..6b29636a00 100644 --- a/packages/playwright-core/src/server/socksInterceptor.ts +++ b/packages/playwright-core/src/server/socksInterceptor.ts @@ -83,4 +83,3 @@ export class SocksInterceptor { function tChannelForSocks(names: '*' | string[], arg: any, path: string, context: ValidatorContext) { throw new ValidationError(`${path}: channels are not expected in SocksSupport`); } - diff --git a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts index b8af1b9ca3..6d7459c978 100644 --- a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts +++ b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts @@ -105,4 +105,4 @@ export class WKProvisionalPage { assert(!frameTree.frame.parentId); this._mainFrameId = frameTree.frame.id; } -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/utils/isomorphic/mimeType.ts b/packages/playwright-core/src/utils/isomorphic/mimeType.ts index 2f8b9d4829..407d935281 100644 --- a/packages/playwright-core/src/utils/isomorphic/mimeType.ts +++ b/packages/playwright-core/src/utils/isomorphic/mimeType.ts @@ -20,4 +20,4 @@ export function isJsonMimeType(mimeType: string) { export function isTextualMimeType(mimeType: string) { return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/); -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/utils/linuxUtils.ts b/packages/playwright-core/src/utils/linuxUtils.ts index d8e227f107..5d98c823a5 100644 --- a/packages/playwright-core/src/utils/linuxUtils.ts +++ b/packages/playwright-core/src/utils/linuxUtils.ts @@ -77,4 +77,3 @@ function parseOSReleaseText(osReleaseText: string): Map { } return fields; } - diff --git a/packages/playwright-core/src/utils/sequence.ts b/packages/playwright-core/src/utils/sequence.ts index 27756fabeb..b063e5c488 100644 --- a/packages/playwright-core/src/utils/sequence.ts +++ b/packages/playwright-core/src/utils/sequence.ts @@ -63,4 +63,4 @@ export function findRepeatedSubsequences(s: string[]): { sequence: string[]; cou } return result; -} \ No newline at end of file +} diff --git a/packages/playwright/jsx-runtime.mjs b/packages/playwright/jsx-runtime.mjs index 742708825e..1dfd5835aa 100644 --- a/packages/playwright/jsx-runtime.mjs +++ b/packages/playwright/jsx-runtime.mjs @@ -18,4 +18,4 @@ import jsxRuntime from './jsx-runtime.js'; export const jsx = jsxRuntime.jsx; export const jsxs = jsxRuntime.jsxs; -export const Fragment = jsxRuntime.Fragment; \ No newline at end of file +export const Fragment = jsxRuntime.Fragment; diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index 0e8babce10..a86fbb5157 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -298,4 +298,4 @@ const configInternalSymbol = Symbol('configInternalSymbol'); export function getProjectId(project: FullProject): string { return (project as any).__projectId!; -} \ No newline at end of file +} diff --git a/packages/playwright/src/runner/vcs.ts b/packages/playwright/src/runner/vcs.ts index 6f7ed55c9a..da7c4c2cc8 100644 --- a/packages/playwright/src/runner/vcs.ts +++ b/packages/playwright/src/runner/vcs.ts @@ -54,4 +54,4 @@ export async function detectChangedTestFiles(baseCommit: string, configDir: stri const trackedFilesWithChanges = gitFileList(`diff ${baseCommit} --name-only`).map(file => path.join(gitRoot, file)); return new Set(affectedTestFiles([...untrackedFiles, ...trackedFilesWithChanges])); -} \ No newline at end of file +} diff --git a/packages/trace-viewer/bundle.ts b/packages/trace-viewer/bundle.ts index eaf09a6cc3..38375f8d76 100644 --- a/packages/trace-viewer/bundle.ts +++ b/packages/trace-viewer/bundle.ts @@ -28,4 +28,4 @@ export function bundle(): Plugin { }, }, }; -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/sw/traceModelBackends.ts b/packages/trace-viewer/src/sw/traceModelBackends.ts index ee694b2fba..4f8ea94c3d 100644 --- a/packages/trace-viewer/src/sw/traceModelBackends.ts +++ b/packages/trace-viewer/src/sw/traceModelBackends.ts @@ -160,4 +160,4 @@ export class TraceViewerServer { return; return response; } -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/third_party/devtools.ts b/packages/trace-viewer/src/third_party/devtools.ts index 27c520cbce..cc03330240 100644 --- a/packages/trace-viewer/src/third_party/devtools.ts +++ b/packages/trace-viewer/src/third_party/devtools.ts @@ -282,4 +282,4 @@ export async function generateFetchCall(resource: Entry, style: FetchStyle = Fet async function fetchRequestPostData(resource: Entry) { return resource.request.postData?._sha1 ? await fetch(`sha1/${resource.request.postData._sha1}`).then(r => r.text()) : resource.request.postData?.text; -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 101c532aea..1deb8ecd88 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -150,4 +150,4 @@ function excludeOrigin(url: string): string { } catch (error) { return url; } -} \ No newline at end of file +} diff --git a/packages/web/src/components/sourceChooser.tsx b/packages/web/src/components/sourceChooser.tsx index 22b91c61a5..091dce8f98 100644 --- a/packages/web/src/components/sourceChooser.tsx +++ b/packages/web/src/components/sourceChooser.tsx @@ -59,4 +59,4 @@ export function emptySource(): Source { label: '', highlight: [] }; -} \ No newline at end of file +} diff --git a/packages/web/src/components/splitView.spec.tsx b/packages/web/src/components/splitView.spec.tsx index a9260a2d48..ca11520912 100644 --- a/packages/web/src/components/splitView.spec.tsx +++ b/packages/web/src/components/splitView.spec.tsx @@ -90,4 +90,3 @@ test('drag resize', async ({ page, mount }) => { expect.soft(mainBox).toEqual({ x: 0, y: 0, width: 500, height: 100 }); expect.soft(sidebarBox).toEqual({ x: 0, y: 101, width: 500, height: 399 }); }); - diff --git a/tests/android/webview.spec.ts b/tests/android/webview.spec.ts index a0d03b69b6..fac61b5a9a 100644 --- a/tests/android/webview.spec.ts +++ b/tests/android/webview.spec.ts @@ -68,4 +68,4 @@ test('select webview from socketName', async function({ androidDevice }) { await newPage.close(); await context.close(); -}); \ No newline at end of file +}); diff --git a/tests/bidi/csvReporter.ts b/tests/bidi/csvReporter.ts index 8fb936dd11..4986a736c3 100644 --- a/tests/bidi/csvReporter.ts +++ b/tests/bidi/csvReporter.ts @@ -79,4 +79,4 @@ function csvEscape(str) { return str; } -export default CsvReporter; \ No newline at end of file +export default CsvReporter; diff --git a/tests/bidi/expectationReporter.ts b/tests/bidi/expectationReporter.ts index e136149cc4..65371165ff 100644 --- a/tests/bidi/expectationReporter.ts +++ b/tests/bidi/expectationReporter.ts @@ -86,4 +86,4 @@ function getOutcome(test: TestCase): TestExpectation { return 'unknown'; } -export default ExpectationReporter; \ No newline at end of file +export default ExpectationReporter; diff --git a/tests/expect/toThrowMatchers.test.ts b/tests/expect/toThrowMatchers.test.ts index a5d5e52ba8..bc64d13240 100644 --- a/tests/expect/toThrowMatchers.test.ts +++ b/tests/expect/toThrowMatchers.test.ts @@ -588,4 +588,4 @@ for (const toThrow of ['toThrowError', 'toThrow'] as const) { ).toThrowErrorMatchingSnapshot(); }); }); -} \ No newline at end of file +} diff --git a/tests/library/browsercontext-har.spec.ts b/tests/library/browsercontext-har.spec.ts index 4a7834013a..796a37426a 100644 --- a/tests/library/browsercontext-har.spec.ts +++ b/tests/library/browsercontext-har.spec.ts @@ -556,4 +556,4 @@ it('should ignore aborted requests', async ({ contextFactory, server }) => { const result = await Promise.race([evalPromise, page2.waitForTimeout(1000).then(() => 'timeout')]); expect(result).toBe('timeout'); } -}); \ No newline at end of file +}); diff --git a/tests/library/chromium/chromium.spec.ts b/tests/library/chromium/chromium.spec.ts index 61aa811dca..910e0830ff 100644 --- a/tests/library/chromium/chromium.spec.ts +++ b/tests/library/chromium/chromium.spec.ts @@ -629,4 +629,4 @@ test.describe('PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1', () => { const req = await requestPromise; expect(req.headers['x-custom-header']).toBe('custom!'); }); -}); \ No newline at end of file +}); diff --git a/tests/library/inspector/cli-codegen-test.spec.ts b/tests/library/inspector/cli-codegen-test.spec.ts index ad68e75c48..b24871877f 100644 --- a/tests/library/inspector/cli-codegen-test.spec.ts +++ b/tests/library/inspector/cli-codegen-test.spec.ts @@ -122,4 +122,4 @@ test('should generate routeFromHAR with --save-har and --save-har-glob', async ( await cli.waitForCleanExit(); const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8')); expect(json.log.creator.name).toBe('Playwright'); -}); \ No newline at end of file +}); diff --git a/tests/library/popup.spec.ts b/tests/library/popup.spec.ts index 1dcede604b..8f2f1df7dc 100644 --- a/tests/library/popup.spec.ts +++ b/tests/library/popup.spec.ts @@ -288,4 +288,4 @@ async function waitForRafs(page: Page, count: number): Promise { }; window.builtinRequestAnimationFrame(onRaf); }), count); -} \ No newline at end of file +} diff --git a/tests/page/frame-evaluate.spec.ts b/tests/page/frame-evaluate.spec.ts index 16bcf6eac7..6908f03e86 100644 --- a/tests/page/frame-evaluate.spec.ts +++ b/tests/page/frame-evaluate.spec.ts @@ -180,4 +180,3 @@ it('evaluateHandle should work', async ({ page, server }) => { const windowHandle = await mainFrame.evaluateHandle(() => window); expect(windowHandle).toBeTruthy(); }); - diff --git a/tests/page/locator-misc-2.spec.ts b/tests/page/locator-misc-2.spec.ts index b32913faba..202b9a2957 100644 --- a/tests/page/locator-misc-2.spec.ts +++ b/tests/page/locator-misc-2.spec.ts @@ -173,4 +173,3 @@ it('Locator.locator() and FrameLocator.locator() should accept locator', async ( expect(await divLocator.locator('input').inputValue()).toBe('outer'); expect(await page.frameLocator('iframe').locator(divLocator).locator('input').inputValue()).toBe('inner'); }); - diff --git a/tests/page/page-event-request.spec.ts b/tests/page/page-event-request.spec.ts index 2c1d7a7eba..e1fc29eb59 100644 --- a/tests/page/page-event-request.spec.ts +++ b/tests/page/page-event-request.spec.ts @@ -272,4 +272,4 @@ it(' resource should have type image', async ({ page }) => { `) ]); expect(request.resourceType()).toBe('image'); -}); \ No newline at end of file +}); diff --git a/tests/page/page-mouse.spec.ts b/tests/page/page-mouse.spec.ts index f93ca4e2e2..7f4ce8a85d 100644 --- a/tests/page/page-mouse.spec.ts +++ b/tests/page/page-mouse.spec.ts @@ -309,4 +309,3 @@ it('should dispatch mouse move after context menu was opened', async ({ page, br } } }); - diff --git a/tests/page/page-request-fulfill.spec.ts b/tests/page/page-request-fulfill.spec.ts index 3edcd0ba0a..0d939a0a5c 100644 --- a/tests/page/page-request-fulfill.spec.ts +++ b/tests/page/page-request-fulfill.spec.ts @@ -485,4 +485,3 @@ it('should not go to the network for fulfilled requests body', { expect(body).toBeTruthy(); expect(serverHit).toBe(false); }); - diff --git a/tests/page/selectors-react.spec.ts b/tests/page/selectors-react.spec.ts index ce47f1bb25..c7bfc3f68a 100644 --- a/tests/page/selectors-react.spec.ts +++ b/tests/page/selectors-react.spec.ts @@ -176,4 +176,3 @@ for (const [name, url] of Object.entries(reacts)) { }); }); } - diff --git a/tests/page/selectors-vue.spec.ts b/tests/page/selectors-vue.spec.ts index 175e1246ee..71b60d1cb3 100644 --- a/tests/page/selectors-vue.spec.ts +++ b/tests/page/selectors-vue.spec.ts @@ -168,4 +168,3 @@ for (const [name, url] of Object.entries(vues)) { }); }); } - diff --git a/tests/playwright-test/command-line-filter.spec.ts b/tests/playwright-test/command-line-filter.spec.ts index 8937694b39..e33cb8b318 100644 --- a/tests/playwright-test/command-line-filter.spec.ts +++ b/tests/playwright-test/command-line-filter.spec.ts @@ -195,4 +195,4 @@ test('should focus a single test suite', async ({ runInlineTest }) => { expect(result.skipped).toBe(0); expect(result.report.suites[0].suites[0].suites[0].specs[0].title).toEqual('pass2'); expect(result.report.suites[0].suites[0].suites[0].specs[1].title).toEqual('pass3'); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/only-changed.spec.ts b/tests/playwright-test/only-changed.spec.ts index 5a9c6ee121..00614598de 100644 --- a/tests/playwright-test/only-changed.spec.ts +++ b/tests/playwright-test/only-changed.spec.ts @@ -427,4 +427,3 @@ test('exits successfully if there are no changes', async ({ runInlineTest, git, expect(result.exitCode).toBe(0); }); - diff --git a/tests/playwright-test/playwright.reuse.browser.spec.ts b/tests/playwright-test/playwright.reuse.browser.spec.ts index bccce95b3e..4340550f64 100644 --- a/tests/playwright-test/playwright.reuse.browser.spec.ts +++ b/tests/playwright-test/playwright.reuse.browser.spec.ts @@ -148,4 +148,4 @@ test('should produce correct test steps', async ({ runInlineTest, runServer }) = 'onStepEnd fixture: context', 'onStepEnd After Hooks' ]); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 91b32e76a2..5ddb271b70 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -2052,4 +2052,4 @@ test('project filter in report name', async ({ runInlineTest }) => { const reportFiles = await fs.promises.readdir(reportDir); expect(reportFiles.sort()).toEqual(['report-foo-b-r-6d9d49e-1.zip']); } -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/reporter-dot.spec.ts b/tests/playwright-test/reporter-dot.spec.ts index 5afda3f7bd..04a9337ec0 100644 --- a/tests/playwright-test/reporter-dot.spec.ts +++ b/tests/playwright-test/reporter-dot.spec.ts @@ -112,4 +112,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { colors.green('·').repeat(3)); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-github.spec.ts b/tests/playwright-test/reporter-github.spec.ts index 100feb157f..292c407b9f 100644 --- a/tests/playwright-test/reporter-github.spec.ts +++ b/tests/playwright-test/reporter-github.spec.ts @@ -98,4 +98,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(result.exitCode).toBe(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-junit.spec.ts b/tests/playwright-test/reporter-junit.spec.ts index 2b182e00c0..9947484903 100644 --- a/tests/playwright-test/reporter-junit.spec.ts +++ b/tests/playwright-test/reporter-junit.spec.ts @@ -594,4 +594,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(time).toBeGreaterThan(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-line.spec.ts b/tests/playwright-test/reporter-line.spec.ts index 22441d567a..7322af433f 100644 --- a/tests/playwright-test/reporter-line.spec.ts +++ b/tests/playwright-test/reporter-line.spec.ts @@ -189,4 +189,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(result.exitCode).toBe(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-list.spec.ts b/tests/playwright-test/reporter-list.spec.ts index 752d29c649..1d488cc253 100644 --- a/tests/playwright-test/reporter-list.spec.ts +++ b/tests/playwright-test/reporter-list.spec.ts @@ -319,4 +319,3 @@ function simpleAnsiRenderer(text, ttyWidth) { return screenLines.map(line => line.join('')).join('\n'); } - diff --git a/tests/playwright-test/reporter-markdown.spec.ts b/tests/playwright-test/reporter-markdown.spec.ts index 076e28d66e..558a1f8a84 100644 --- a/tests/playwright-test/reporter-markdown.spec.ts +++ b/tests/playwright-test/reporter-markdown.spec.ts @@ -157,4 +157,4 @@ test('report with worker error', async ({ runInlineTest }) => { **0 passed** :heavy_check_mark::heavy_check_mark::heavy_check_mark: `); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/snapshot-path-template.spec.ts b/tests/playwright-test/snapshot-path-template.spec.ts index 4f260469b7..2fd79403f4 100644 --- a/tests/playwright-test/snapshot-path-template.spec.ts +++ b/tests/playwright-test/snapshot-path-template.spec.ts @@ -137,4 +137,3 @@ test('arg should receive default arg', async ({ runInlineTest }, testInfo) => { expect(result.output).toContain(`A snapshot doesn't exist at ${snapshotOutputPath}, writing actual`); expect(fs.existsSync(snapshotOutputPath)).toBe(true); }); - diff --git a/tests/playwright-test/test-ignore.spec.ts b/tests/playwright-test/test-ignore.spec.ts index b552380faa..17e3b3adae 100644 --- a/tests/playwright-test/test-ignore.spec.ts +++ b/tests/playwright-test/test-ignore.spec.ts @@ -370,4 +370,4 @@ test('should always work with unix separators', async ({ runInlineTest }) => { expect(result.passed).toBe(1); expect(result.report.suites.map(s => s.file).sort()).toEqual(['a.test.ts']); expect(result.exitCode).toBe(0); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/test-use.spec.ts b/tests/playwright-test/test-use.spec.ts index 8a2bcd2401..1687cbe2e7 100644 --- a/tests/playwright-test/test-use.spec.ts +++ b/tests/playwright-test/test-use.spec.ts @@ -186,4 +186,3 @@ test('test.use() should throw if called from beforeAll ', async ({ runInlineTest expect(result.exitCode).toBe(1); expect(result.output).toContain('Playwright Test did not expect test.use() to be called here'); }); - diff --git a/tests/playwright-test/ui-mode-test-annotations.spec.ts b/tests/playwright-test/ui-mode-test-annotations.spec.ts index eeff6a5aca..cc1f0b5f04 100644 --- a/tests/playwright-test/ui-mode-test-annotations.spec.ts +++ b/tests/playwright-test/ui-mode-test-annotations.spec.ts @@ -46,4 +46,3 @@ test('should display annotations', async ({ runUITest }) => { await expect(annotations.locator('.annotation-item').filter({ hasText: 'test repo' }).locator('a')) .toHaveAttribute('href', 'https://github.com/microsoft/playwright'); }); - diff --git a/utils/doclint/linting-code-snippets/cli.js b/utils/doclint/linting-code-snippets/cli.js index 5d3200aa9e..7139bb105a 100644 --- a/utils/doclint/linting-code-snippets/cli.js +++ b/utils/doclint/linting-code-snippets/cli.js @@ -153,6 +153,7 @@ class JSLintingService extends LintingService { '@typescript-eslint/no-unused-vars': 'off', 'max-len': ['error', { code: 100 }], 'react/react-in-jsx-scope': 'off', + 'eol-last': 'off', }, } }); From a74c488b2562ce72af417fa6036e4f9a49af4f10 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 20 Dec 2024 10:24:10 -0800 Subject: [PATCH 19/83] docs: document --no-shell option (#34120) --- docs/src/api/params.md | 2 +- docs/src/browsers.md | 30 ++++++++++++++++++++--- docs/src/chrome-extensions-js-python.md | 2 +- packages/playwright-core/types/types.d.ts | 6 ++--- packages/playwright/types/test.d.ts | 2 +- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e059fffe46..a1f909a4fb 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1003,7 +1003,7 @@ Additional arguments to pass to the browser instance. The list of Chromium flags Browser distribution channel. -Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). +Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 90c0b2850b..1cc10d7a8d 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -338,11 +338,11 @@ dotnet test --settings:webkit.runsettings For Google Chrome, Microsoft Edge and other Chromium-based browsers, by default, Playwright uses open source Chromium builds. Since the Chromium project is ahead of the branded browsers, when the world is on Google Chrome N, Playwright already supports Chromium N+1 that will be released in Google Chrome and Microsoft Edge a few weeks later. -Playwright ships a regular Chromium build for headed operations and a separate [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details. +### Chromium: headless shell -#### Optimize download size on CI +Playwright ships a regular Chromium build for headed operations and a separate [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode. -If you are only running tests in headless shell (i.e. the `channel` option is not specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation. +If you are only running tests in headless shell (i.e. the `channel` option is **not** specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation. ```bash js # only running tests headlessly @@ -364,7 +364,7 @@ playwright install --with-deps --only-shell pwsh bin/Debug/netX/playwright.ps1 install --with-deps --only-shell ``` -#### Opt-in to new headless mode +### Chromium: new headless mode You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell): @@ -419,6 +419,28 @@ pytest test_login.py --browser-channel chromium dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium ``` +With the new headless mode, you can skip downloading the headless shell during browser installation by using the `--no-shell` option: + +```bash js +# only running tests headlessly +npx playwright install --with-deps --no-shell +``` + +```bash java +# only running tests headlessly +mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps --no-shell" +``` + +```bash python +# only running tests headlessly +playwright install --with-deps --no-shell +``` + +```bash csharp +# only running tests headlessly +pwsh bin/Debug/netX/playwright.ps1 install --with-deps --no-shell +``` + ### Google Chrome & Microsoft Edge While Playwright can download and use the recent Chromium build, it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). In particular, the current Playwright version will support Stable and Beta channels of these browsers. diff --git a/docs/src/chrome-extensions-js-python.md b/docs/src/chrome-extensions-js-python.md index 1142e9b3a7..edbe7c06c9 100644 --- a/docs/src/chrome-extensions-js-python.md +++ b/docs/src/chrome-extensions-js-python.md @@ -214,7 +214,7 @@ def test_popup_page(page: Page, extension_id: str) -> None: ## Headless mode -By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#opt-in-to-new-headless-mode): +By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#chromium-new-headless-mode): ```js title="fixtures.ts" // ... diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index ba83204e7e..460a0c7edf 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -14716,7 +14716,7 @@ export interface BrowserType { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). @@ -15215,7 +15215,7 @@ export interface BrowserType { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). @@ -21566,7 +21566,7 @@ export interface LaunchOptions { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index caed95b8d5..85e3d37847 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6002,7 +6002,7 @@ export interface PlaywrightWorkerOptions { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). From c89e213eff6d03ba79202d369a393e6124e42fa6 Mon Sep 17 00:00:00 2001 From: Evan Cahill Date: Fri, 20 Dec 2024 13:23:01 -0800 Subject: [PATCH 20/83] docs: Use locator.first() in locator.or examples (#34106) --- docs/src/api/class-locator.md | 17 +++++++++++------ packages/playwright-core/types/types.d.ts | 10 +++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index e93d02d9d8..38a3546e41 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1717,16 +1717,21 @@ var banana = await page.GetByRole(AriaRole.Listitem).Nth(2); Creates a locator matching all elements that match one or both of the two locators. -Note that when both locators match something, the resulting locator will have multiple matches and violate [locator strictness](../locators.md#strictness) guidelines. +Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a [locator strictness](../locators.md#strictness) violation. **Usage** Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly. +:::note +If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, +possibly throwing the ["strict mode violation" error](../locators.md#strictness). In this case, you can use [`method: Locator.first`] to only match one of them. +::: + ```js const newEmail = page.getByRole('button', { name: 'New' }); const dialog = page.getByText('Confirm security settings'); -await expect(newEmail.or(dialog)).toBeVisible(); +await expect(newEmail.or(dialog).first()).toBeVisible(); if (await dialog.isVisible()) await page.getByRole('button', { name: 'Dismiss' }).click(); await newEmail.click(); @@ -1735,7 +1740,7 @@ await newEmail.click(); ```java Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New")); Locator dialog = page.getByText("Confirm security settings"); -assertThat(newEmail.or(dialog)).isVisible(); +assertThat(newEmail.or(dialog).first()).isVisible(); if (dialog.isVisible()) page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click(); newEmail.click(); @@ -1744,7 +1749,7 @@ newEmail.click(); ```python async new_email = page.get_by_role("button", name="New") dialog = page.get_by_text("Confirm security settings") -await expect(new_email.or_(dialog)).to_be_visible() +await expect(new_email.or_(dialog).first).to_be_visible() if (await dialog.is_visible()): await page.get_by_role("button", name="Dismiss").click() await new_email.click() @@ -1753,7 +1758,7 @@ await new_email.click() ```python sync new_email = page.get_by_role("button", name="New") dialog = page.get_by_text("Confirm security settings") -expect(new_email.or_(dialog)).to_be_visible() +expect(new_email.or_(dialog).first).to_be_visible() if (dialog.is_visible()): page.get_by_role("button", name="Dismiss").click() new_email.click() @@ -1762,7 +1767,7 @@ new_email.click() ```csharp var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" }); var dialog = page.GetByText("Confirm security settings"); -await Expect(newEmail.Or(dialog)).ToBeVisibleAsync(); +await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync(); if (await dialog.IsVisibleAsync()) await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync(); await newEmail.ClickAsync(); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 460a0c7edf..78c1c668c4 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13853,18 +13853,22 @@ export interface Locator { /** * Creates a locator matching all elements that match one or both of the two locators. * - * Note that when both locators match something, the resulting locator will have multiple matches and violate - * [locator strictness](https://playwright.dev/docs/locators#strictness) guidelines. + * Note that when both locators match something, the resulting locator will have multiple matches, potentially causing + * a [locator strictness](https://playwright.dev/docs/locators#strictness) violation. * * **Usage** * * Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog * shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly. * + * **NOTE** If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, + * possibly throwing the ["strict mode violation" error](https://playwright.dev/docs/locators#strictness). In this case, you can use + * [locator.first()](https://playwright.dev/docs/api/class-locator#locator-first) to only match one of them. + * * ```js * const newEmail = page.getByRole('button', { name: 'New' }); * const dialog = page.getByText('Confirm security settings'); - * await expect(newEmail.or(dialog)).toBeVisible(); + * await expect(newEmail.or(dialog).first()).toBeVisible(); * if (await dialog.isVisible()) * await page.getByRole('button', { name: 'Dismiss' }).click(); * await newEmail.click(); From cce8e8e0e5c367bf3510eec59d2ab1c17017c7b6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 20 Dec 2024 14:03:38 -0800 Subject: [PATCH 21/83] chore(html): use api prefix to qualify public types (#34121) --- packages/playwright/src/reporters/html.ts | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 302d532641..75d345e319 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -21,7 +21,7 @@ import path from 'path'; import type { TransformCallback } from 'stream'; import { Transform } from 'stream'; import { codeFrameColumns } from '../transform/babelBundle'; -import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic, TestError } from '../../types/testReporter'; +import type * as api from '../../types/testReporter'; import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils'; import { colors, formatError, formatResultFailure, stripAnsiEscapes } from './base'; import { resolveReporterOutputPath } from '../util'; @@ -56,8 +56,8 @@ type HtmlReporterOptions = { }; class HtmlReporter implements ReporterV2 { - private config!: FullConfig; - private suite!: Suite; + private config!: api.FullConfig; + private suite!: api.Suite; private _options: HtmlReporterOptions; private _outputFolder!: string; private _attachmentsBaseURL!: string; @@ -65,7 +65,7 @@ class HtmlReporter implements ReporterV2 { private _port: number | undefined; private _host: string | undefined; private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined; - private _topLevelErrors: TestError[] = []; + private _topLevelErrors: api.TestError[] = []; constructor(options: HtmlReporterOptions) { this._options = options; @@ -79,11 +79,11 @@ class HtmlReporter implements ReporterV2 { return false; } - onConfigure(config: FullConfig) { + onConfigure(config: api.FullConfig) { this.config = config; } - onBegin(suite: Suite) { + onBegin(suite: api.Suite) { const { outputFolder, open, attachmentsBaseURL, host, port } = this._resolveOptions(); this._outputFolder = outputFolder; this._open = open; @@ -125,11 +125,11 @@ class HtmlReporter implements ReporterV2 { return !!relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath); } - onError(error: TestError): void { + onError(error: api.TestError): void { this._topLevelErrors.push(error); } - async onEnd(result: FullResult) { + async onEnd(result: api.FullResult) { const projectSuites = this.suite.suites; await removeFolders([this._outputFolder]); const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL); @@ -223,14 +223,14 @@ export function startHtmlReportServer(folder: string): HttpServer { } class HtmlBuilder { - private _config: FullConfig; + private _config: api.FullConfig; private _reportFolder: string; private _stepsInFile = new MultiMap(); private _dataZipFile: ZipFile; private _hasTraces = false; private _attachmentsBaseURL: string; - constructor(config: FullConfig, outputDir: string, attachmentsBaseURL: string) { + constructor(config: api.FullConfig, outputDir: string, attachmentsBaseURL: string) { this._config = config; this._reportFolder = outputDir; fs.mkdirSync(this._reportFolder, { recursive: true }); @@ -238,7 +238,7 @@ class HtmlBuilder { this._attachmentsBaseURL = attachmentsBaseURL; } - async build(metadata: Metadata, projectSuites: Suite[], result: FullResult, topLevelErrors: TestError[]): Promise<{ ok: boolean, singleTestId: string | undefined }> { + async build(metadata: Metadata, projectSuites: api.Suite[], result: api.FullResult, topLevelErrors: api.TestError[]): Promise<{ ok: boolean, singleTestId: string | undefined }> { const data = new Map(); for (const projectSuite of projectSuites) { for (const fileSuite of projectSuite.suites) { @@ -378,7 +378,7 @@ class HtmlBuilder { this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName); } - private _processSuite(suite: Suite, projectName: string, path: string[], outTests: TestEntry[]) { + private _processSuite(suite: api.Suite, projectName: string, path: string[], outTests: TestEntry[]) { const newPath = [...path, suite.title]; suite.entries().forEach(e => { if (e.type === 'test') @@ -388,7 +388,7 @@ class HtmlBuilder { }); } - private _createTestEntry(test: TestCasePublic, projectName: string, path: string[]): TestEntry { + private _createTestEntry(test: api.TestCase, projectName: string, path: string[]): TestEntry { const duration = test.results.reduce((a, r) => a + r.duration, 0); const location = this._relativeLocation(test.location)!; path = path.slice(1).filter(path => path.length > 0); @@ -500,7 +500,7 @@ class HtmlBuilder { }).filter(Boolean) as TestAttachment[]; } - private _createTestResult(test: TestCasePublic, result: TestResultPublic): TestResult { + private _createTestResult(test: api.TestCase, result: api.TestResult): TestResult { return { duration: result.duration, startTime: result.startTime.toISOString(), @@ -531,7 +531,7 @@ class HtmlBuilder { return result; } - private _relativeLocation(location: Location | undefined): Location | undefined { + private _relativeLocation(location: api.Location | undefined): api.Location | undefined { if (!location) return undefined; const file = toPosixPath(path.relative(this._config.rootDir, location.file)); @@ -609,9 +609,9 @@ function stdioAttachment(chunk: Buffer | string, type: 'stdout' | 'stderr'): Jso }; } -type DedupedStep = { step: TestStepPublic, count: number, duration: number }; +type DedupedStep = { step: api.TestStep, count: number, duration: number }; -function dedupeSteps(steps: TestStepPublic[]) { +function dedupeSteps(steps: api.TestStep[]) { const result: DedupedStep[] = []; let lastResult = undefined; for (const step of steps) { From 03cf7429a4b69627a7114ff3fc984a553d05139e Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 20 Dec 2024 18:04:21 -0800 Subject: [PATCH 22/83] chore(bidi): upload report.csv to azure (#34122) --- .github/workflows/tests_bidi.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index b559a87563..6be824869a 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -51,3 +51,20 @@ jobs: name: csv-report-${{ matrix.channel }} path: test-results/report.csv retention-days: 7 + + - name: Azure Login + if: ${{ !cancelled() && github.ref == 'refs/heads/main' }} + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_BLOB_REPORTS_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_BLOB_REPORTS_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_BLOB_REPORTS_SUBSCRIPTION_ID }} + + - name: Upload report.csv to Azure + if: ${{ !cancelled() && github.ref == 'refs/heads/main' }} + run: | + REPORT_DIR='bidi-reports' + azcopy cp "./test-results/report.csv" "https://mspwblobreport.blob.core.windows.net/\$web/$REPORT_DIR/${{ matrix.channel }}.csv" + echo "Report url: https://mspwblobreport.z1.web.core.windows.net/$REPORT_DIR/${{ matrix.channel }}.csv" + env: + AZCOPY_AUTO_LOGIN_TYPE: AZCLI From 1c8e6f0921b1cc517afb5f8549609f356e81cf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gautier=20Ben=20A=C3=AFm?= <48261497+GauBen@users.noreply.github.com> Date: Sat, 21 Dec 2024 18:59:50 +0100 Subject: [PATCH 23/83] docs: fixed typo (#34129) --- docs/src/test-webserver-js.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/test-webserver-js.md b/docs/src/test-webserver-js.md index aba072d56a..bf01a5cd27 100644 --- a/docs/src/test-webserver-js.md +++ b/docs/src/test-webserver-js.md @@ -36,7 +36,7 @@ export default defineConfig({ | `cwd` | Current working directory of the spawned process, defaults to the directory of the configuration file. | | `stdout` | If `"pipe"`, it will pipe the stdout of the command to the process stdout. If `"ignore"`, it will ignore the stdout of the command. Default to `"ignore"`. | | `stderr` | Whether to pipe the stderr of the command to the process stderr or ignore it. Defaults to `"pipe"`. | -| `timeout` | `How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. | +| `timeout` | How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. | ## Adding a server timeout From 08644003d2a8770045e9eb3c7fa9a0f8bb812413 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:27:56 -0800 Subject: [PATCH 24/83] feat(chromium-tip-of-tree): roll to r1290 (#34144) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index a163c7a607..f5b2495459 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1288", + "revision": "1290", "installByDefault": false, - "browserVersion": "133.0.6905.0" + "browserVersion": "133.0.6919.0" }, { "name": "firefox", From 3ec8ee7a9b9ee682f63ff155a36ea868951d8ae5 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 26 Dec 2024 23:51:06 -0800 Subject: [PATCH 25/83] feat(chromium): roll to r1153 (#34118) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 4 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 913aac3269..7d100113c5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.57-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 132.0.6834.46 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 132.0.6834.57 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 132.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index f5b2495459..3462c09224 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,9 +3,9 @@ "browsers": [ { "name": "chromium", - "revision": "1152", + "revision": "1153", "installByDefault": true, - "browserVersion": "132.0.6834.46" + "browserVersion": "132.0.6834.57" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 49d9edde41..d2777c80e9 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36 Edg/132.0.6834.46", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36 Edg/132.0.6834.57", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36 Edg/132.0.6834.46", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.57 Safari/537.36 Edg/132.0.6834.57", "screen": { "width": 1920, "height": 1080 From 7f141b2c4202a2ea3754bbf8e8d4240e89f41641 Mon Sep 17 00:00:00 2001 From: Pengoose Date: Fri, 27 Dec 2024 18:54:16 +0900 Subject: [PATCH 26/83] feat: expect(locator).toHaveAccessibleErrorMessage (#33904) --- docs/src/api/class-locatorassertions.md | 50 +++++++ .../src/server/injected/injectedScript.ts | 4 +- .../src/server/injected/roleUtils.ts | 56 ++++++++ packages/playwright/src/matchers/expect.ts | 2 + packages/playwright/src/matchers/matchers.ts | 12 ++ packages/playwright/types/test.d.ts | 28 ++++ tests/page/expect-misc.spec.ts | 130 ++++++++++++++++++ 7 files changed, 281 insertions(+), 1 deletion(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 7e61e1b3a5..c2adf3afc5 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -1217,6 +1217,56 @@ Expected accessible description. * since: v1.44 +## async method: LocatorAssertions.toHaveAccessibleErrorMessage +* since: v1.50 +* langs: + - alias-java: hasAccessibleErrorMessage + +Ensures the [Locator] points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage). + +**Usage** + +```js +const locator = page.getByTestId('username-input'); +await expect(locator).toHaveAccessibleErrorMessage('Username is required.'); +``` + +```java +Locator locator = page.getByTestId("username-input"); +assertThat(locator).hasAccessibleErrorMessage("Username is required."); +``` + +```python async +locator = page.get_by_test_id("username-input") +await expect(locator).to_have_accessible_error_message("Username is required.") +``` + +```python sync +locator = page.get_by_test_id("username-input") +expect(locator).to_have_accessible_error_message("Username is required.") +``` + +```csharp +var locator = Page.GetByTestId("username-input"); +await Expect(locator).ToHaveAccessibleErrorMessageAsync("Username is required."); +``` + +### param: LocatorAssertions.toHaveAccessibleErrorMessage.errorMessage +* since: v1.50 +- `errorMessage` <[string]|[RegExp]> + +Expected accessible error message. + +### option: LocatorAssertions.toHaveAccessibleErrorMessage.timeout = %%-js-assertions-timeout-%% +* since: v1.50 + +### option: LocatorAssertions.toHaveAccessibleErrorMessage.timeout = %%-csharp-java-python-assertions-timeout-%% +* since: v1.50 + +### option: LocatorAssertions.toHaveAccessibleErrorMessage.ignoreCase = %%-assertions-ignore-case-%% +* since: v1.50 + + ## async method: LocatorAssertions.toHaveAccessibleName * since: v1.44 * langs: diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index a1ffdf893c..21fcca69a4 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser'; import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator'; import type * as channels from '@protocol/channels'; import { Highlight } from './highlight'; -import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, getReadonly } from './roleUtils'; +import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, getReadonly, getElementAccessibleErrorMessage } from './roleUtils'; import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; @@ -1321,6 +1321,8 @@ export class InjectedScript { received = getElementAccessibleName(element, false /* includeHidden */); } else if (expression === 'to.have.accessible.description') { received = getElementAccessibleDescription(element, false /* includeHidden */); + } else if (expression === 'to.have.accessible.error.message') { + received = getElementAccessibleErrorMessage(element); } else if (expression === 'to.have.role') { received = getAriaRole(element) || ''; } else if (expression === 'to.have.title') { diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts index cc9e6a70d0..f74c893c1b 100644 --- a/packages/playwright-core/src/server/injected/roleUtils.ts +++ b/packages/playwright-core/src/server/injected/roleUtils.ts @@ -461,6 +461,59 @@ export function getElementAccessibleDescription(element: Element, includeHidden: return accessibleDescription; } +// https://www.w3.org/TR/wai-aria-1.2/#aria-invalid +const kAriaInvalidRoles = ['application', 'checkbox', 'combobox', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'tree', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid']; + +function getAriaInvalid(element: Element): 'false' | 'true' | 'grammar' | 'spelling' { + const role = getAriaRole(element) || ''; + if (!role || !kAriaInvalidRoles.includes(role)) + return 'false'; + const ariaInvalid = element.getAttribute('aria-invalid'); + if (!ariaInvalid || ariaInvalid.trim() === '' || ariaInvalid.toLocaleLowerCase() === 'false') + return 'false'; + if (ariaInvalid === 'true' || ariaInvalid === 'grammar' || ariaInvalid === 'spelling') + return ariaInvalid; + return 'true'; +} + +function getValidityInvalid(element: Element) { + if ('validity' in element){ + const validity = element.validity as ValidityState | undefined; + return validity?.valid === false; + } + return false; +} + +export function getElementAccessibleErrorMessage(element: Element): string { + // SPEC: https://w3c.github.io/aria/#aria-errormessage + // + // TODO: support https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/validationMessage + const cache = cacheAccessibleErrorMessage; + let accessibleErrorMessage = cacheAccessibleErrorMessage?.get(element); + + if (accessibleErrorMessage === undefined) { + accessibleErrorMessage = ''; + + const isAriaInvalid = getAriaInvalid(element) !== 'false'; + const isValidityInvalid = getValidityInvalid(element); + if (isAriaInvalid || isValidityInvalid) { + const errorMessageId = element.getAttribute('aria-errormessage'); + const errorMessages = getIdRefs(element, errorMessageId); + // Ideally, this should be a separate "embeddedInErrorMessage", but it would follow the exact same rules. + // Relevant vague spec: https://w3c.github.io/core-aam/#ariaErrorMessage. + const parts = errorMessages.map(errorMessage => asFlatString( + getTextAlternativeInternal(errorMessage, { + visitedElements: new Set(), + embeddedInDescribedBy: { element: errorMessage, hidden: isElementHiddenForAria(errorMessage) }, + }) + )); + accessibleErrorMessage = parts.join(' ').trim(); + } + cache?.set(element, accessibleErrorMessage); + } + return accessibleErrorMessage; +} + type AccessibleNameOptions = { visitedElements: Set, includeHidden?: boolean, @@ -972,6 +1025,7 @@ let cacheAccessibleName: Map | undefined; let cacheAccessibleNameHidden: Map | undefined; let cacheAccessibleDescription: Map | undefined; let cacheAccessibleDescriptionHidden: Map | undefined; +let cacheAccessibleErrorMessage: Map | undefined; let cacheIsHidden: Map | undefined; let cachePseudoContentBefore: Map | undefined; let cachePseudoContentAfter: Map | undefined; @@ -983,6 +1037,7 @@ export function beginAriaCaches() { cacheAccessibleNameHidden ??= new Map(); cacheAccessibleDescription ??= new Map(); cacheAccessibleDescriptionHidden ??= new Map(); + cacheAccessibleErrorMessage ??= new Map(); cacheIsHidden ??= new Map(); cachePseudoContentBefore ??= new Map(); cachePseudoContentAfter ??= new Map(); @@ -994,6 +1049,7 @@ export function endAriaCaches() { cacheAccessibleNameHidden = undefined; cacheAccessibleDescription = undefined; cacheAccessibleDescriptionHidden = undefined; + cacheAccessibleErrorMessage = undefined; cacheIsHidden = undefined; cachePseudoContentBefore = undefined; cachePseudoContentAfter = undefined; diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 0bd116e7a1..d4c3287d33 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -35,6 +35,7 @@ import { toContainText, toHaveAccessibleDescription, toHaveAccessibleName, + toHaveAccessibleErrorMessage, toHaveAttribute, toHaveClass, toHaveCount, @@ -224,6 +225,7 @@ const customAsyncMatchers = { toContainText, toHaveAccessibleDescription, toHaveAccessibleName, + toHaveAccessibleErrorMessage, toHaveAttribute, toHaveClass, toHaveCount, diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 8a8089e91e..3962c0cae9 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -205,6 +205,18 @@ export function toHaveAccessibleName( } } +export function toHaveAccessibleErrorMessage( + this: ExpectMatcherState, + locator: LocatorEx, + expected: string | RegExp, + options?: { timeout?: number; ignoreCase?: boolean }, +) { + return toMatchText.call(this, 'toHaveAccessibleErrorMessage', locator, 'Locator', async (isNot, timeout) => { + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true }); + return await locator._expect('to.have.accessible.error.message', { expectedText: expectedText, isNot, timeout }); + }, expected, options); +} + export function toHaveAttribute( this: ExpectMatcherState, locator: LocatorEx, diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 85e3d37847..7c9cae5415 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -8112,6 +8112,34 @@ interface LocatorAssertions { timeout?: number; }): Promise; + /** + * Ensures the [Locator](https://playwright.dev/docs/api/class-locator) points to an element with a given + * [aria errormessage](https://w3c.github.io/aria/#aria-errormessage). + * + * **Usage** + * + * ```js + * const locator = page.getByTestId('username-input'); + * await expect(locator).toHaveAccessibleErrorMessage('Username is required.'); + * ``` + * + * @param errorMessage Expected accessible error message. + * @param options + */ + toHaveAccessibleErrorMessage(errorMessage: string|RegExp, options?: { + /** + * Whether to perform case-insensitive match. + * [`ignoreCase`](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-accessible-error-message-option-ignore-case) + * option takes precedence over the corresponding regular expression flag if specified. + */ + ignoreCase?: boolean; + + /** + * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; + }): Promise; + /** * Ensures the [Locator](https://playwright.dev/docs/api/class-locator) points to an element with a given * [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index 0ab5707a62..a1fb6637b1 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -491,6 +491,136 @@ test('toHaveAccessibleDescription', async ({ page }) => { await expect(page.locator('div')).toHaveAccessibleDescription('foo bar baz'); }); +test('toHaveAccessibleErrorMessage', async ({ page }) => { + await page.setContent(` +
+ +
Hello
+
This should not be considered.
+
+ `); + + const locator = page.locator('input[role="textbox"]'); + await expect(locator).toHaveAccessibleErrorMessage('Hello'); + await expect(locator).not.toHaveAccessibleErrorMessage('hello'); + await expect(locator).toHaveAccessibleErrorMessage('hello', { ignoreCase: true }); + await expect(locator).toHaveAccessibleErrorMessage(/ell\w/); + await expect(locator).not.toHaveAccessibleErrorMessage(/hello/); + await expect(locator).toHaveAccessibleErrorMessage(/hello/, { ignoreCase: true }); + await expect(locator).not.toHaveAccessibleErrorMessage('This should not be considered.'); +}); + +test('toHaveAccessibleErrorMessage should handle multiple aria-errormessage references', async ({ page }) => { + await page.setContent(` +
+ +
First error message.
+
Second error message.
+
This should not be considered.
+
+ `); + + const locator = page.locator('input[role="textbox"]'); + + await expect(locator).toHaveAccessibleErrorMessage('First error message. Second error message.'); + await expect(locator).toHaveAccessibleErrorMessage(/first error message./i); + await expect(locator).toHaveAccessibleErrorMessage(/second error message./i); + await expect(locator).not.toHaveAccessibleErrorMessage(/This should not be considered./i); +}); + +test.describe('toHaveAccessibleErrorMessage should handle aria-invalid attribute', () => { + const errorMessageText = 'Error message'; + + async function setupPage(page, ariaInvalidValue: string | null) { + const ariaInvalidAttr = ariaInvalidValue === null ? '' : `aria-invalid="${ariaInvalidValue}"`; + await page.setContent(` +
+ +
${errorMessageText}
+
+ `); + return page.locator('#node'); + } + + test.describe('evaluated in false', () => { + test('no aria-invalid attribute', async ({ page }) => { + const locator = await setupPage(page, null); + await expect(locator).not.toHaveAccessibleErrorMessage(errorMessageText); + }); + test('aria-invalid="false"', async ({ page }) => { + const locator = await setupPage(page, 'false'); + await expect(locator).not.toHaveAccessibleErrorMessage(errorMessageText); + }); + test('aria-invalid="" (empty string)', async ({ page }) => { + const locator = await setupPage(page, ''); + await expect(locator).not.toHaveAccessibleErrorMessage(errorMessageText); + }); + }); + test.describe('evaluated in true', () => { + test('aria-invalid="true"', async ({ page }) => { + const locator = await setupPage(page, 'true'); + await expect(locator).toHaveAccessibleErrorMessage(errorMessageText); + }); + test('aria-invalid="foo" (unrecognized value)', async ({ page }) => { + const locator = await setupPage(page, 'foo'); + await expect(locator).toHaveAccessibleErrorMessage(errorMessageText); + }); + }); +}); + +test.describe('toHaveAccessibleErrorMessage should handle validity state with aria-invalid', () => { + const errorMessageText = 'Error message'; + + test('should show error message when validity is false and aria-invalid is true', async ({ page }) => { + await page.setContent(` +
+ +
${errorMessageText}
+
+ `); + const locator = page.locator('#node'); + await locator.fill('101'); + await expect(locator).toHaveAccessibleErrorMessage(errorMessageText); + }); + + test('should show error message when validity is true and aria-invalid is true', async ({ page }) => { + await page.setContent(` +
+ +
${errorMessageText}
+
+ `); + const locator = page.locator('#node'); + await locator.fill('99'); + await expect(locator).toHaveAccessibleErrorMessage(errorMessageText); + }); + + test('should show error message when validity is false and aria-invalid is false', async ({ page }) => { + await page.setContent(` +
+ +
${errorMessageText}
+
+ `); + const locator = page.locator('#node'); + await locator.fill('101'); + await expect(locator).toHaveAccessibleErrorMessage(errorMessageText); + }); + + test('should not show error message when validity is true and aria-invalid is false', async ({ page }) => { + await page.setContent(` +
+ +
${errorMessageText}
+
+ `); + const locator = page.locator('#node'); + await locator.fill('99'); + await expect(locator).not.toHaveAccessibleErrorMessage(errorMessageText); + }); +}); + + test('toHaveRole', async ({ page }) => { await page.setContent(`
Button!
`); await expect(page.locator('div')).toHaveRole('button'); From 4819747c85faa8cad0f33d770e1e63f6fb894d49 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 27 Dec 2024 11:00:59 +0100 Subject: [PATCH 27/83] chore: keep linting generated files (#34150) --- .eslintignore | 3 --- packages/playwright-core/src/protocol/debug.ts | 2 +- packages/playwright-core/src/protocol/validator.ts | 2 +- .../src/server/injected/recorder/clipPaths.ts | 2 +- packages/protocol/src/channels.d.ts | 1 + utils/generate_channels.js | 6 +++--- utils/generate_clip_paths.js | 1 + 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.eslintignore b/.eslintignore index fdcc76dda0..9d22f618d8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,9 +3,6 @@ test/assets/modernizr.js /packages/*/lib/ *.js /packages/playwright-core/src/generated/* -/packages/playwright-core/src/protocol/debug.ts -/packages/playwright-core/src/protocol/validator.ts -/packages/playwright-core/src/server/injected/recorder/clipPaths.ts /packages/playwright-core/src/third_party/ /packages/playwright-core/types/* /packages/playwright-ct-core/src/generated/* diff --git a/packages/playwright-core/src/protocol/debug.ts b/packages/playwright-core/src/protocol/debug.ts index 4f44f5941e..f29ae6be82 100644 --- a/packages/playwright-core/src/protocol/debug.ts +++ b/packages/playwright-core/src/protocol/debug.ts @@ -187,4 +187,4 @@ export const pausesBeforeInputActions = new Set([ 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck' -]); \ No newline at end of file +]); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index f4db833e02..9b14551fb8 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -2752,4 +2752,4 @@ scheme.JsonPipeSendParams = tObject({ }); scheme.JsonPipeSendResult = tOptional(tObject({})); scheme.JsonPipeCloseParams = tOptional(tObject({})); -scheme.JsonPipeCloseResult = tOptional(tObject({})); \ No newline at end of file +scheme.JsonPipeCloseResult = tOptional(tObject({})); diff --git a/packages/playwright-core/src/server/injected/recorder/clipPaths.ts b/packages/playwright-core/src/server/injected/recorder/clipPaths.ts index a1e016542a..1ac490843d 100644 --- a/packages/playwright-core/src/server/injected/recorder/clipPaths.ts +++ b/packages/playwright-core/src/server/injected/recorder/clipPaths.ts @@ -28,4 +28,4 @@ import type { SvgJson } from './recorder'; // eslint-disable-next-line key-spacing, object-curly-spacing, comma-spacing, quotes const svgJson: SvgJson = {"tagName":"svg","children":[{"tagName":"defs","children":[{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-gripper"},"children":[{"tagName":"path","attrs":{"d":"M5 3h2v2H5zm0 4h2v2H5zm0 4h2v2H5zm4-8h2v2H9zm0 4h2v2H9zm0 4h2v2H9z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-circle-large-filled"},"children":[{"tagName":"path","attrs":{"d":"M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-inspect"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-whole-word"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M0 11H1V13H15V11H16V14H15H1H0V11Z"}},{"tagName":"path","attrs":{"d":"M6.84048 11H5.95963V10.1406H5.93814C5.555 10.7995 4.99104 11.1289 4.24625 11.1289C3.69839 11.1289 3.26871 10.9839 2.95718 10.6938C2.64924 10.4038 2.49527 10.0189 2.49527 9.53906C2.49527 8.51139 3.10041 7.91341 4.3107 7.74512L5.95963 7.51416C5.95963 6.57959 5.58186 6.1123 4.82632 6.1123C4.16389 6.1123 3.56591 6.33789 3.03238 6.78906V5.88672C3.57307 5.54297 4.19612 5.37109 4.90152 5.37109C6.19416 5.37109 6.84048 6.05501 6.84048 7.42285V11ZM5.95963 8.21777L4.63297 8.40039C4.22476 8.45768 3.91682 8.55973 3.70914 8.70654C3.50145 8.84977 3.39761 9.10579 3.39761 9.47461C3.39761 9.74316 3.4925 9.96338 3.68228 10.1353C3.87564 10.3035 4.13166 10.3877 4.45035 10.3877C4.8872 10.3877 5.24706 10.2355 5.52994 9.93115C5.8164 9.62321 5.95963 9.2347 5.95963 8.76562V8.21777Z"}},{"tagName":"path","attrs":{"d":"M9.3475 10.2051H9.32601V11H8.44515V2.85742H9.32601V6.4668H9.3475C9.78076 5.73633 10.4146 5.37109 11.2489 5.37109C11.9543 5.37109 12.5057 5.61816 12.9032 6.1123C13.3042 6.60286 13.5047 7.26172 13.5047 8.08887C13.5047 9.00911 13.2809 9.74674 12.8333 10.3018C12.3857 10.8532 11.7734 11.1289 10.9964 11.1289C10.2695 11.1289 9.71989 10.821 9.3475 10.2051ZM9.32601 7.98682V8.75488C9.32601 9.20964 9.47282 9.59635 9.76644 9.91504C10.0636 10.2301 10.4396 10.3877 10.8944 10.3877C11.4279 10.3877 11.8451 10.1836 12.1458 9.77539C12.4502 9.36719 12.6024 8.79964 12.6024 8.07275C12.6024 7.46045 12.4609 6.98063 12.1781 6.6333C11.8952 6.28597 11.512 6.1123 11.0286 6.1123C10.5166 6.1123 10.1048 6.29134 9.7933 6.64941C9.48177 7.00391 9.32601 7.44971 9.32601 7.98682Z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-eye"},"children":[{"tagName":"path","attrs":{"d":"M7.99993 6.00316C9.47266 6.00316 10.6666 7.19708 10.6666 8.66981C10.6666 10.1426 9.47266 11.3365 7.99993 11.3365C6.52715 11.3365 5.33324 10.1426 5.33324 8.66981C5.33324 7.19708 6.52715 6.00316 7.99993 6.00316ZM7.99993 7.00315C7.07946 7.00315 6.33324 7.74935 6.33324 8.66981C6.33324 9.59028 7.07946 10.3365 7.99993 10.3365C8.9204 10.3365 9.6666 9.59028 9.6666 8.66981C9.6666 7.74935 8.9204 7.00315 7.99993 7.00315ZM7.99993 3.66675C11.0756 3.66675 13.7307 5.76675 14.4673 8.70968C14.5344 8.97755 14.3716 9.24908 14.1037 9.31615C13.8358 9.38315 13.5643 9.22041 13.4973 8.95248C12.8713 6.45205 10.6141 4.66675 7.99993 4.66675C5.38454 4.66675 3.12664 6.45359 2.50182 8.95555C2.43491 9.22341 2.16348 9.38635 1.89557 9.31948C1.62766 9.25255 1.46471 8.98115 1.53162 8.71321C2.26701 5.76856 4.9229 3.66675 7.99993 3.66675Z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-symbol-constant"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M4 6h8v1H4V6zm8 3H4v1h8V9z"}},{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M1 4l1-1h12l1 1v8l-1 1H2l-1-1V4zm1 0v8h12V4H2z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-check"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M14.431 3.323l-8.47 10-.79-.036-3.35-4.77.818-.574 2.978 4.24 8.051-9.506.764.646z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-close"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-pass"},"children":[{"tagName":"path","attrs":{"d":"M6.27 10.87h.71l4.56-4.56-.71-.71-4.2 4.21-1.92-1.92L4 8.6l2.27 2.27z"}},{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6z"}}]},{"tagName":"clipPath","attrs":{"width":"16","height":"16","viewBox":"0 0 16 16","fill":"currentColor","id":"icon-gist"},"children":[{"tagName":"path","attrs":{"fill-rule":"evenodd","clip-rule":"evenodd","d":"M10.57 1.14l3.28 3.3.15.36v9.7l-.5.5h-11l-.5-.5v-13l.5-.5h7.72l.35.14zM10 5h3l-3-3v3zM3 2v12h10V6H9.5L9 5.5V2H3zm2.062 7.533l1.817-1.828L6.17 7 4 9.179v.707l2.171 2.174.707-.707-1.816-1.82zM8.8 7.714l.7-.709 2.189 2.175v.709L9.5 12.062l-.705-.709 1.831-1.82L8.8 7.714z"}}]}]}]}; -export default svgJson; \ No newline at end of file +export default svgJson; diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 100baf59f9..6f9e36f0c3 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -4983,3 +4983,4 @@ export interface JsonPipeEvents { 'message': JsonPipeMessageEvent; 'closed': JsonPipeClosedEvent; } + diff --git a/utils/generate_channels.js b/utils/generate_channels.js index 0bebcbba0c..827250ad8c 100755 --- a/utils/generate_channels.js +++ b/utils/generate_channels.js @@ -362,7 +362,7 @@ function writeFile(filePath, content) { fs.writeFileSync(filePath, content, 'utf8'); } -writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'channels.d.ts'), channels_ts.join('\n')); -writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'debug.ts'), debug_ts.join('\n')); -writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'validator.ts'), validator_ts.join('\n')); +writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'channels.d.ts'), channels_ts.join('\n') + '\n'); +writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'debug.ts'), debug_ts.join('\n') + '\n'); +writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'validator.ts'), validator_ts.join('\n') + '\n'); process.exit(hasChanges ? 1 : 0); diff --git a/utils/generate_clip_paths.js b/utils/generate_clip_paths.js index 83d26a905c..184b71d36a 100644 --- a/utils/generate_clip_paths.js +++ b/utils/generate_clip_paths.js @@ -95,6 +95,7 @@ const iconNames = [ `// eslint-disable-next-line key-spacing, object-curly-spacing, comma-spacing, quotes`, `const svgJson: SvgJson = ${JSON.stringify(svgJson)};`, `export default svgJson;`, + '', ].join('\n'); fs.writeFileSync(outFile, code, 'utf-8'); })(); From 9dbe63636d3d17dd834f1324538e351ea6931cdd Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 30 Dec 2024 18:00:10 +0000 Subject: [PATCH 28/83] fix(routeWebSocket): should work after context reuse (#34165) --- .../src/server/browserContext.ts | 8 +++- .../dispatchers/browserContextDispatcher.ts | 2 +- .../src/server/dispatchers/pageDispatcher.ts | 2 +- .../dispatchers/webSocketRouteDispatcher.ts | 25 +++++------ packages/playwright-core/src/server/page.ts | 8 ++-- tests/library/browsercontext-reuse.spec.ts | 41 ++++++++++++++++++- 6 files changed, 64 insertions(+), 22 deletions(-) diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index fc20c52bb5..8a835d3726 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -314,6 +314,10 @@ export abstract class BrowserContext extends SdkObject { return this.doSetHTTPCredentials(httpCredentials); } + hasBinding(name: string) { + return this._pageBindings.has(name); + } + async exposeBinding(name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource): Promise { if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`); @@ -414,8 +418,8 @@ export abstract class BrowserContext extends SdkObject { this._options.httpCredentials = { username, password: password || '' }; } - async addInitScript(source: string) { - const initScript = new InitScript(source); + async addInitScript(source: string, name?: string) { + const initScript = new InitScript(source, false /* internal */, name); this.initScripts.push(initScript); await this.doAddInitScript(initScript); } diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index c6ffce49f7..3579f4b3bb 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -288,7 +288,7 @@ export class BrowserContextDispatcher extends Dispatcher { this._webSocketInterceptionPatterns = params.patterns; if (params.patterns.length) - await WebSocketRouteDispatcher.installIfNeeded(this, this._context); + await WebSocketRouteDispatcher.installIfNeeded(this._context); } async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 5e3b73a73f..6e8c47929b 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -191,7 +191,7 @@ export class PageDispatcher extends Dispatcher { this._webSocketInterceptionPatterns = params.patterns; if (params.patterns.length) - await WebSocketRouteDispatcher.installIfNeeded(this.parentScope(), this._page); + await WebSocketRouteDispatcher.installIfNeeded(this._page); } async expectScreenshot(params: channels.PageExpectScreenshotParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts b/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts index 87f469b7d0..bcbc89fe03 100644 --- a/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts @@ -18,7 +18,7 @@ import type { BrowserContext } from '../browserContext'; import type { Frame } from '../frames'; import { Page } from '../page'; import type * as channels from '@protocol/channels'; -import { Dispatcher } from './dispatcher'; +import { Dispatcher, existingDispatcher } from './dispatcher'; import { createGuid, urlMatches } from '../../utils'; import { PageDispatcher } from './pageDispatcher'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; @@ -26,9 +26,6 @@ import * as webSocketMockSource from '../../generated/webSocketMockSource'; import type * as ws from '../injected/webSocketMock'; import { eventsHelper } from '../../utils/eventsHelper'; -const kBindingInstalledSymbol = Symbol('webSocketRouteBindingInstalled'); -const kInitScriptInstalledSymbol = Symbol('webSocketRouteInitScriptInstalled'); - export class WebSocketRouteDispatcher extends Dispatcher<{ guid: string }, channels.WebSocketRouteChannel, PageDispatcher | BrowserContextDispatcher> implements channels.WebSocketRouteChannel { _type_WebSocketRoute = true; private _id: string; @@ -57,18 +54,18 @@ export class WebSocketRouteDispatcher extends Dispatcher<{ guid: string }, chann (scope as any)._dispatchEvent('webSocketRoute', { webSocketRoute: this }); } - static async installIfNeeded(contextDispatcher: BrowserContextDispatcher, target: Page | BrowserContext) { + static async installIfNeeded(target: Page | BrowserContext) { + const kBindingName = '__pwWebSocketBinding'; const context = target instanceof Page ? target.context() : target; - if (!(context as any)[kBindingInstalledSymbol]) { - (context as any)[kBindingInstalledSymbol] = true; - - await context.exposeBinding('__pwWebSocketBinding', false, (source, payload: ws.BindingPayload) => { + if (!context.hasBinding(kBindingName)) { + await context.exposeBinding(kBindingName, false, (source, payload: ws.BindingPayload) => { if (payload.type === 'onCreate') { - const pageDispatcher = PageDispatcher.fromNullable(contextDispatcher, source.page); + const contextDispatcher = existingDispatcher(context); + const pageDispatcher = contextDispatcher ? PageDispatcher.fromNullable(contextDispatcher, source.page) : undefined; let scope: PageDispatcher | BrowserContextDispatcher | undefined; if (pageDispatcher && matchesPattern(pageDispatcher, context._options.baseURL, payload.url)) scope = pageDispatcher; - else if (matchesPattern(contextDispatcher, context._options.baseURL, payload.url)) + else if (contextDispatcher && matchesPattern(contextDispatcher, context._options.baseURL, payload.url)) scope = contextDispatcher; if (scope) { new WebSocketRouteDispatcher(scope, payload.id, payload.url, source.frame); @@ -91,15 +88,15 @@ export class WebSocketRouteDispatcher extends Dispatcher<{ guid: string }, chann }); } - if (!(target as any)[kInitScriptInstalledSymbol]) { - (target as any)[kInitScriptInstalledSymbol] = true; + const kInitScriptName = 'webSocketMockSource'; + if (!target.initScripts.find(s => s.name === kInitScriptName)) { await target.addInitScript(` (() => { const module = {}; ${webSocketMockSource.source} (module.exports.inject())(globalThis); })(); - `); + `, kInitScriptName); } } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index fe483b8347..9b85837b65 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -564,8 +564,8 @@ export class Page extends SdkObject { await this._delegate.bringToFront(); } - async addInitScript(source: string) { - const initScript = new InitScript(source); + async addInitScript(source: string, name?: string) { + const initScript = new InitScript(source, false /* internal */, name); this.initScripts.push(initScript); await this._delegate.addInitScript(initScript); } @@ -953,8 +953,9 @@ function addPageBinding(playwrightBinding: string, bindingName: string, needsHan export class InitScript { readonly source: string; readonly internal: boolean; + readonly name?: string; - constructor(source: string, internal?: boolean) { + constructor(source: string, internal?: boolean, name?: string) { const guid = createGuid(); this.source = `(() => { globalThis.__pwInitScripts = globalThis.__pwInitScripts || {}; @@ -965,6 +966,7 @@ export class InitScript { ${source} })();`; this.internal = !!internal; + this.name = name; } } diff --git a/tests/library/browsercontext-reuse.spec.ts b/tests/library/browsercontext-reuse.spec.ts index 30319f0aad..2d65d472b3 100644 --- a/tests/library/browsercontext-reuse.spec.ts +++ b/tests/library/browsercontext-reuse.spec.ts @@ -15,7 +15,7 @@ */ import { browserTest, expect } from '../config/browserTest'; -import type { BrowserContext } from '@playwright/test'; +import type { BrowserContext, Page } from '@playwright/test'; const test = browserTest.extend<{ reusedContext: () => Promise }>({ reusedContext: async ({ browserType, browser }, use) => { @@ -287,3 +287,42 @@ test('should continue issuing events after closing the reused page', async ({ re ]); } }); + +test('should work with routeWebSocket', async ({ reusedContext, server, browser }, testInfo) => { + async function setup(page: Page, suffix: string) { + await page.routeWebSocket(/ws1/, ws => { + ws.onMessage(message => { + ws.send('page-mock-' + suffix); + }); + }); + await page.context().routeWebSocket(/.*/, ws => { + ws.onMessage(message => { + ws.send('context-mock-' + suffix); + }); + }); + await page.goto('about:blank'); + await page.evaluate(({ port }) => { + window.log = []; + (window as any).ws1 = new WebSocket('ws://localhost:' + port + '/ws1'); + (window as any).ws1.addEventListener('message', event => window.log.push(`ws1:${event.data}`)); + (window as any).ws2 = new WebSocket('ws://localhost:' + port + '/ws2'); + (window as any).ws2.addEventListener('message', event => window.log.push(`ws2:${event.data}`)); + }, { port: server.PORT }); + } + + let context = await reusedContext(); + let page = await context.newPage(); + await setup(page, 'before'); + await page.evaluate(() => (window as any).ws1.send('request')); + await expect.poll(() => page.evaluate(() => window.log)).toEqual([`ws1:page-mock-before`]); + await page.evaluate(() => (window as any).ws2.send('request')); + await expect.poll(() => page.evaluate(() => window.log)).toEqual([`ws1:page-mock-before`, `ws2:context-mock-before`]); + + context = await reusedContext(); + page = context.pages()[0]; + await setup(page, 'after'); + await page.evaluate(() => (window as any).ws1.send('request')); + await expect.poll(() => page.evaluate(() => window.log)).toEqual([`ws1:page-mock-after`]); + await page.evaluate(() => (window as any).ws2.send('request')); + await expect.poll(() => page.evaluate(() => window.log)).toEqual([`ws1:page-mock-after`, `ws2:context-mock-after`]); +}); From cab2bc4e2a1e8f6d5c4f958e68bfa240a622cf98 Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Mon, 30 Dec 2024 19:06:00 +0100 Subject: [PATCH 29/83] Combine file name and test name to a single identifier for CSV export of BiDi results (#34172) --- tests/bidi/csvReporter.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/bidi/csvReporter.ts b/tests/bidi/csvReporter.ts index 4986a736c3..1da6b6b697 100644 --- a/tests/bidi/csvReporter.ts +++ b/tests/bidi/csvReporter.ts @@ -41,15 +41,14 @@ class CsvReporter implements Reporter { } onEnd(result: FullResult) { - const rows = [['File Name', 'Test Name', 'Expected Status', 'Status', 'Error Message']]; + const rows = [['Test Name', 'Expected Status', 'Status', 'Error Message']]; for (const project of this._suite.suites) { for (const file of project.suites) { for (const test of file.allTests()) { if (test.ok()) continue; const row = []; - row.push(file.title); - row.push(csvEscape(test.title)); + row.push(csvEscape(`${file.title} :: ${test.title}`)); row.push(test.expectedStatus); row.push(test.outcome()); const result = test.results.find(r => r.error); From cd32d1b08c2d3f26abfaa1ae4222a0d5221e4f80 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 30 Dec 2024 18:45:49 +0000 Subject: [PATCH 30/83] fix(test runner): apply `--last-failed` after sharding (#34166) --- packages/playwright/src/common/config.ts | 1 + packages/playwright/src/runner/lastRun.ts | 2 +- packages/playwright/src/runner/loadUtils.ts | 4 +++ tests/playwright-test/runner.spec.ts | 32 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index a86fbb5157..443cb58319 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -56,6 +56,7 @@ export class FullConfigInternal { cliFailOnFlakyTests?: boolean; cliLastFailed?: boolean; testIdMatcher?: Matcher; + lastFailedTestIdMatcher?: Matcher; defineConfigWasUsed = false; globalSetups: string[] = []; diff --git a/packages/playwright/src/runner/lastRun.ts b/packages/playwright/src/runner/lastRun.ts index 407543041e..2152f977cf 100644 --- a/packages/playwright/src/runner/lastRun.ts +++ b/packages/playwright/src/runner/lastRun.ts @@ -43,7 +43,7 @@ export class LastRunReporter implements ReporterV2 { return; try { const lastRunInfo = JSON.parse(await fs.promises.readFile(this._lastRunFile, 'utf8')) as LastRunInfo; - this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id); + this._config.lastFailedTestIdMatcher = id => lastRunInfo.failedTests.includes(id); } catch { } } diff --git a/packages/playwright/src/runner/loadUtils.ts b/packages/playwright/src/runner/loadUtils.ts index 1315eea6e4..62799747b6 100644 --- a/packages/playwright/src/runner/loadUtils.ts +++ b/packages/playwright/src/runner/loadUtils.ts @@ -194,6 +194,10 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho filterTestsRemoveEmptySuites(rootSuite, test => testsInThisShard.has(test)); } + // Explicitly apply --last-failed filter after sharding. + if (config.lastFailedTestIdMatcher) + filterByTestIds(rootSuite, config.lastFailedTestIdMatcher); + // Now prepend dependency projects without filtration. { // Filtering 'only' and sharding might have reduced the number of top-level projects. diff --git a/tests/playwright-test/runner.spec.ts b/tests/playwright-test/runner.spec.ts index dc88187229..dece006a06 100644 --- a/tests/playwright-test/runner.spec.ts +++ b/tests/playwright-test/runner.spec.ts @@ -841,3 +841,35 @@ test('should run last failed tests', async ({ runInlineTest }) => { expect(result2.passed).toBe(0); expect(result2.failed).toBe(1); }); + +test('should run last failed tests in a shard', async ({ runInlineTest }) => { + const workspace = { + 'a.spec.js': ` + import { test, expect } from '@playwright/test'; + test('pass-a', async () => {}); + test('fail-a', async () => { + expect(1).toBe(2); + }); + `, + 'b.spec.js': ` + import { test, expect } from '@playwright/test'; + test('pass-b', async () => {}); + test('fail-b', async () => { + expect(1).toBe(2); + }); + `, + }; + const result1 = await runInlineTest(workspace, { shard: '2/2' }); + expect(result1.exitCode).toBe(1); + expect(result1.passed).toBe(1); + expect(result1.failed).toBe(1); + expect(result1.output).toContain('b.spec.js:3:11 › pass-b'); + expect(result1.output).toContain('b.spec.js:4:11 › fail-b'); + + const result2 = await runInlineTest(workspace, { shard: '2/2' }, {}, { additionalArgs: ['--last-failed'] }); + expect(result2.exitCode).toBe(1); + expect(result2.passed).toBe(0); + expect(result2.failed).toBe(1); + expect(result2.output).not.toContain('b.spec.js:3:11 › pass-b'); + expect(result2.output).toContain('b.spec.js:4:11 › fail-b'); +}); From 940230d43a272d6e5758dd88f0f6d9600176b73d Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Mon, 30 Dec 2024 23:48:22 +0100 Subject: [PATCH 31/83] Use csvReporter as well when running BiDi tests locally (#34167) --- tests/bidi/playwright.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index 8dbe5b2b9d..5fb370482c 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -42,6 +42,7 @@ const reporters = () => { ['./csvReporter', { outputFile: path.join(outputDir, 'report.csv') }], ] : [ ['html', { open: 'on-failure' }], + ['./csvReporter', { outputFile: path.join(outputDir, 'report.csv') }], ['./expectationReporter', { rebase: false }], ]; return result; From b2cbe7f2ec6abf43f0e441fe2dae566cb7e9fd30 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 31 Dec 2024 11:22:10 +0100 Subject: [PATCH 32/83] chore(roll): roll WebKit to r2121 (#34179) --- 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 3462c09224..6c4af54313 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,7 +27,7 @@ }, { "name": "webkit", - "revision": "2120", + "revision": "2121", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From 7769010e6e146a81504fa921fa3931867dbbc505 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 31 Dec 2024 13:18:25 -0800 Subject: [PATCH 33/83] chore(bidi): mark test expected to timeout as fixme (#34176) --- tests/bidi/csvReporter.ts | 14 ++++++++++---- tests/config/browserTest.ts | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/bidi/csvReporter.ts b/tests/bidi/csvReporter.ts index 1da6b6b697..f227550ef3 100644 --- a/tests/bidi/csvReporter.ts +++ b/tests/bidi/csvReporter.ts @@ -45,15 +45,21 @@ class CsvReporter implements Reporter { for (const project of this._suite.suites) { for (const file of project.suites) { for (const test of file.allTests()) { - if (test.ok()) + // Report fixme tests as failing. + const fixme = test.annotations.find(a => a.type === 'fixme'); + if (test.ok() && !fixme) continue; const row = []; row.push(csvEscape(`${file.title} :: ${test.title}`)); row.push(test.expectedStatus); row.push(test.outcome()); - const result = test.results.find(r => r.error); - const errorMessage = stripAnsi(result?.error?.message.replace(/\s+/g, ' ').trim().substring(0, 1024)); - row.push(csvEscape(errorMessage ?? '')); + if (fixme) { + row.push('fixme' + (fixme.description ? `: ${fixme.description}` : '')); + } else { + const result = test.results.find(r => r.error); + const errorMessage = stripAnsi(result?.error?.message.replace(/\s+/g, ' ').trim().substring(0, 1024)); + row.push(csvEscape(errorMessage ?? '')); + } rows.push(row); } } diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 8477359407..7836b2e38c 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -188,8 +188,7 @@ const test = baseTest.extend }, { scope: 'worker' }], autoSkipBidiTest: [async ({ bidiTestSkipPredicate }, run) => { - if (bidiTestSkipPredicate(test.info())) - test.skip(true); + test.fixme(bidiTestSkipPredicate(test.info()), 'marked as timeout in bidi expectations'); await run(); }, { auto: true, scope: 'test' }], }); From da52befea076312638b800891de7bbb59485f60a Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 1 Jan 2025 21:16:04 -0800 Subject: [PATCH 34/83] feat(chromium-tip-of-tree): roll to r1291 (#34182) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 6c4af54313..03a135dfa7 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1290", + "revision": "1291", "installByDefault": false, - "browserVersion": "133.0.6919.0" + "browserVersion": "133.0.6929.0" }, { "name": "firefox", From 546b7b702c41711baffce9b0c627adc584aabf75 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 1 Jan 2025 21:16:46 -0800 Subject: [PATCH 35/83] feat(webkit): roll to r2122 (#34180) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- packages/playwright-core/src/server/webkit/protocol.d.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 03a135dfa7..9bafa13f3b 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,7 +27,7 @@ }, { "name": "webkit", - "revision": "2121", + "revision": "2122", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index 7c279f9e1e..9abd47bcfd 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -6689,6 +6689,10 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the * Cookie Same-Site policy. */ sameSite: CookieSameSitePolicy; + /** + * Cookie partition key. If null and partitioned property is true, then key must be computed. + */ + partitionKey?: string; } /** * Accessibility Node @@ -7073,6 +7077,10 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the */ export type setCookieParameters = { cookie: Cookie; + /** + * If true, then cookie's partition key should be set. + */ + shouldPartition?: boolean; } export type setCookieReturnValue = { } From acdd666d9530277f3a2561f5591b9a8cf1780ce8 Mon Sep 17 00:00:00 2001 From: David Gahnassia Date: Thu, 2 Jan 2025 07:17:22 +0200 Subject: [PATCH 36/83] docs(test-fixtures): removed redundancy (#34185) --- docs/src/test-fixtures-js.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/test-fixtures-js.md b/docs/src/test-fixtures-js.md index 1d19d0acdb..9bdd4391ad 100644 --- a/docs/src/test-fixtures-js.md +++ b/docs/src/test-fixtures-js.md @@ -695,7 +695,7 @@ test('passes', async ({ database, page, a11y }) => { ## Box fixtures -Usually, custom fixtures are reported as separate steps in in the UI mode, Trace Viewer and various test reports. They also appear in error messages from the test runner. For frequently-used fixtures, this can mean lots of noise. You can stop the fixtures steps from being shown in the UI by "boxing" it. +Usually, custom fixtures are reported as separate steps in the UI mode, Trace Viewer and various test reports. They also appear in error messages from the test runner. For frequently-used fixtures, this can mean lots of noise. You can stop the fixtures steps from being shown in the UI by "boxing" it. ```js import { test as base } from '@playwright/test'; From 175f05cafccfc7c730cf5e15102c5fb87e337724 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 2 Jan 2025 16:04:51 +0100 Subject: [PATCH 37/83] test: increase page-event-crash timeout (#34178) --- tests/library/page-event-crash.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/library/page-event-crash.spec.ts b/tests/library/page-event-crash.spec.ts index 1bf9c396dd..d185fa5a19 100644 --- a/tests/library/page-event-crash.spec.ts +++ b/tests/library/page-event-crash.spec.ts @@ -32,6 +32,10 @@ const test = testBase.extend<{ crash: () => void }, { dummy: string }>({ dummy: ['', { scope: 'worker' }], }); +test.beforeEach(({ platform, browserName }) => { + test.slow(platform === 'linux' && browserName === 'webkit', 'WebKit/Linux tests are consistently slower on some Linux environments. Most likely WebContent process is not getting terminated properly and is causing the slowdown.'); +}); + test('should emit crash event when page crashes', async ({ page, crash }) => { await page.setContent(`
This page should crash
`); crash(); From 04a3574f8054f00c34129a2b1b8acc2a58594956 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 2 Jan 2025 17:48:59 +0100 Subject: [PATCH 38/83] feat(reporter): report `TestStep#attachments` (#34037) --- docs/src/test-reporter-api/class-teststep.md | 10 ++++ packages/html-reporter/src/links.tsx | 5 +- .../html-reporter/src/testCaseView.spec.tsx | 3 + packages/html-reporter/src/testFileView.tsx | 2 +- packages/html-reporter/src/testResultView.tsx | 27 +++++---- packages/html-reporter/src/types.d.ts | 1 + packages/playwright/src/common/ipc.ts | 1 + .../playwright/src/isomorphic/teleReceiver.ts | 17 +++++- packages/playwright/src/reporters/html.ts | 18 ++++-- .../playwright/src/reporters/teleEmitter.ts | 5 +- packages/playwright/src/runner/dispatcher.ts | 8 +++ packages/playwright/src/worker/testInfo.ts | 55 +++++++++++++------ packages/playwright/types/testReporter.d.ts | 27 +++++++++ .../playwright-test/playwright.trace.spec.ts | 6 +- tests/playwright-test/reporter-html.spec.ts | 26 +++++++++ tests/playwright-test/reporter.spec.ts | 31 +++++++++++ .../to-have-screenshot.spec.ts | 28 ++++++++-- tests/playwright-test/ui-mode-trace.spec.ts | 49 ++++++++++++++++- 18 files changed, 265 insertions(+), 54 deletions(-) diff --git a/docs/src/test-reporter-api/class-teststep.md b/docs/src/test-reporter-api/class-teststep.md index 43b8474abe..ef16e4849a 100644 --- a/docs/src/test-reporter-api/class-teststep.md +++ b/docs/src/test-reporter-api/class-teststep.md @@ -50,6 +50,16 @@ Start time of this particular test step. List of steps inside this step. +## property: TestStep.attachments +* since: v1.50 +- type: <[Array]<[Object]>> + - `name` <[string]> Attachment name. + - `contentType` <[string]> Content type of this attachment to properly present in the report, for example `'application/json'` or `'image/png'`. + - `path` ?<[string]> Optional path on the filesystem to the attached file. + - `body` ?<[Buffer]> Optional attachment body used instead of a file. + +The list of files or buffers attached in the step execution through [`method: TestInfo.attach`]. + ## property: TestStep.title * since: v1.10 - type: <[string]> diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index b8db4c0e9e..5f199568b5 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -68,11 +68,12 @@ export const ProjectLink: React.FunctionComponent<{ export const AttachmentLink: React.FunctionComponent<{ attachment: TestAttachment, + result: TestResult, href?: string, linkName?: string, openInNewTab?: boolean, -}> = ({ attachment, href, linkName, openInNewTab }) => { - const isAnchored = useIsAnchored('attachment-' + attachment.name); +}> = ({ attachment, result, href, linkName, openInNewTab }) => { + const isAnchored = useIsAnchored('attachment-' + result.attachments.indexOf(attachment)); return {attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()} {attachment.path &&
{linkName || attachment.name}} diff --git a/packages/html-reporter/src/testCaseView.spec.tsx b/packages/html-reporter/src/testCaseView.spec.tsx index b7a9f9405b..7cc9f8991c 100644 --- a/packages/html-reporter/src/testCaseView.spec.tsx +++ b/packages/html-reporter/src/testCaseView.spec.tsx @@ -37,8 +37,10 @@ const result: TestResult = { duration: 10, location: { file: 'test.spec.ts', line: 82, column: 0 }, steps: [], + attachments: [], count: 1, }], + attachments: [], }], attachments: [], status: 'passed', @@ -139,6 +141,7 @@ const resultWithAttachment: TestResult = { location: { file: 'test.spec.ts', line: 62, column: 0 }, count: 1, steps: [], + attachments: [1], }], attachments: [{ name: 'first attachment', diff --git a/packages/html-reporter/src/testFileView.tsx b/packages/html-reporter/src/testFileView.tsx index f8fad1d646..00ea004136 100644 --- a/packages/html-reporter/src/testFileView.tsx +++ b/packages/html-reporter/src/testFileView.tsx @@ -75,7 +75,7 @@ function imageDiffBadge(test: TestCaseSummary): JSX.Element | undefined { for (const result of test.results) { for (const attachment of result.attachments) { if (attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/)) - return {image()}; + return {image()}; } } } diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index 410677cb02..9dcdf29092 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -32,7 +32,7 @@ interface ImageDiffWithAnchors extends ImageDiff { anchors: string[]; } -function groupImageDiffs(screenshots: Set): ImageDiffWithAnchors[] { +function groupImageDiffs(screenshots: Set, result: TestResult): ImageDiffWithAnchors[] { const snapshotNameToImageDiff = new Map(); for (const attachment of screenshots) { const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); @@ -45,7 +45,7 @@ function groupImageDiffs(screenshots: Set): ImageDiffWithAnchors imageDiff = { name: snapshotName, anchors: [`attachment-${name}`] }; snapshotNameToImageDiff.set(snapshotName, imageDiff); } - imageDiff.anchors.push(`attachment-${attachment.name}`); + imageDiff.anchors.push(`attachment-${result.attachments.indexOf(attachment)}`); if (category === 'actual') imageDiff.actual = { attachment }; if (category === 'expected') @@ -72,15 +72,15 @@ export const TestResultView: React.FC<{ result: TestResult, }> = ({ test, result }) => { const { screenshots, videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors } = React.useMemo(() => { - const attachments = result?.attachments || []; + const attachments = result.attachments; const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); - const screenshotAnchors = [...screenshots].map(a => `attachment-${a.name}`); + const screenshotAnchors = [...screenshots].map(a => `attachment-${attachments.indexOf(a)}`); const videos = attachments.filter(a => a.contentType.startsWith('video/')); const traces = attachments.filter(a => a.name === 'trace'); const otherAttachments = new Set(attachments); [...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); - const otherAttachmentAnchors = [...otherAttachments].map(a => `attachment-${a.name}`); - const diffs = groupImageDiffs(screenshots); + const otherAttachmentAnchors = [...otherAttachments].map(a => `attachment-${attachments.indexOf(a)}`); + const diffs = groupImageDiffs(screenshots, result); const errors = classifyErrors(result.errors, diffs); return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors }; }, [result]); @@ -107,11 +107,11 @@ export const TestResultView: React.FC<{ {!!screenshots.length && {screenshots.map((a, i) => { - return + return - + ; })} } @@ -121,7 +121,7 @@ export const TestResultView: React.FC<{ - {traces.map((a, i) => )} + {traces.map((a, i) => )}
} } @@ -130,14 +130,14 @@ export const TestResultView: React.FC<{ - +
)} } {!!otherAttachments.size && {[...otherAttachments].map((a, i) => - - + + )} } @@ -174,10 +174,9 @@ const StepTreeItem: React.FC<{ step: TestStep; depth: number, }> = ({ test, step, result, depth }) => { - const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1]; return {msToString(step.duration)} - {attachmentName && { evt.stopPropagation(); }}>{icons.attachment()}} + {step.attachments.length > 0 && { evt.stopPropagation(); }}>{icons.attachment()}} {statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')} {step.title} {step.count > 1 && <> ✕ {step.count}} diff --git a/packages/html-reporter/src/types.d.ts b/packages/html-reporter/src/types.d.ts index 733e88e8b9..7a99184739 100644 --- a/packages/html-reporter/src/types.d.ts +++ b/packages/html-reporter/src/types.d.ts @@ -108,5 +108,6 @@ export type TestStep = { snippet?: string; error?: string; steps: TestStep[]; + attachments: number[]; count: number; }; diff --git a/packages/playwright/src/common/ipc.ts b/packages/playwright/src/common/ipc.ts index 909df3dc8f..76ee996216 100644 --- a/packages/playwright/src/common/ipc.ts +++ b/packages/playwright/src/common/ipc.ts @@ -75,6 +75,7 @@ export type AttachmentPayload = { path?: string; body?: string; contentType: string; + stepId?: string; }; export type TestInfoErrorImpl = TestInfoError & { diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index f96547d427..1d41b793cd 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -108,6 +108,7 @@ export type JsonTestStepEnd = { id: string; duration: number; error?: reporterTypes.TestError; + attachments?: number[]; // index of JsonTestResultEnd.attachments }; export type JsonFullResult = { @@ -249,7 +250,7 @@ export class TeleReporterReceiver { const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : undefined; const location = this._absoluteLocation(payload.location); - const step = new TeleTestStep(payload, parentStep, location); + const step = new TeleTestStep(payload, parentStep, location, result); if (parentStep) parentStep.steps.push(step); else @@ -262,6 +263,7 @@ export class TeleReporterReceiver { const test = this._tests.get(testId)!; const result = test.results.find(r => r._id === resultId)!; const step = result._stepMap.get(payload.id)!; + step._endPayload = payload; step.duration = payload.duration; step.error = payload.error; this._reporter.onStepEnd?.(test, result, step); @@ -512,15 +514,20 @@ class TeleTestStep implements reporterTypes.TestStep { parent: reporterTypes.TestStep | undefined; duration: number = -1; steps: reporterTypes.TestStep[] = []; + error: reporterTypes.TestError | undefined; + + private _result: TeleTestResult; + _endPayload?: JsonTestStepEnd; private _startTime: number = 0; - constructor(payload: JsonTestStepStart, parentStep: reporterTypes.TestStep | undefined, location: reporterTypes.Location | undefined) { + constructor(payload: JsonTestStepStart, parentStep: reporterTypes.TestStep | undefined, location: reporterTypes.Location | undefined, result: TeleTestResult) { this.title = payload.title; this.category = payload.category; this.location = location; this.parent = parentStep; this._startTime = payload.startTime; + this._result = result; } titlePath() { @@ -535,6 +542,10 @@ class TeleTestStep implements reporterTypes.TestStep { set startTime(value: Date) { this._startTime = +value; } + + get attachments() { + return this._endPayload?.attachments?.map(index => this._result.attachments[index]) ?? []; + } } export class TeleTestResult implements reporterTypes.TestResult { @@ -550,7 +561,7 @@ export class TeleTestResult implements reporterTypes.TestResult { errors: reporterTypes.TestResult['errors'] = []; error: reporterTypes.TestResult['error']; - _stepMap: Map = new Map(); + _stepMap = new Map(); _id: string; private _startTime: number = 0; diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 75d345e319..62158eef6d 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -505,7 +505,7 @@ class HtmlBuilder { duration: result.duration, startTime: result.startTime.toISOString(), retry: result.retry, - steps: dedupeSteps(result.steps).map(s => this._createTestStep(s)), + steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)), errors: formatResultFailure(test, result, '', true).map(error => error.message), status: result.status, attachments: this._serializeAttachments([ @@ -515,20 +515,26 @@ class HtmlBuilder { }; } - private _createTestStep(dedupedStep: DedupedStep): TestStep { + private _createTestStep(dedupedStep: DedupedStep, result: api.TestResult): TestStep { const { step, duration, count } = dedupedStep; - const result: TestStep = { + const testStep: TestStep = { title: step.title, startTime: step.startTime.toISOString(), duration, - steps: dedupeSteps(step.steps).map(s => this._createTestStep(s)), + steps: dedupeSteps(step.steps).map(s => this._createTestStep(s, result)), + attachments: step.attachments.map(s => { + const index = result.attachments.indexOf(s); + if (index === -1) + throw new Error('Unexpected, attachment not found'); + return index; + }), location: this._relativeLocation(step.location), error: step.error?.message, count }; if (step.location) - this._stepsInFile.set(step.location.file, result); - return result; + this._stepsInFile.set(step.location.file, testStep); + return testStep; } private _relativeLocation(location: api.Location | undefined): api.Location | undefined { diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index f56178114d..0ec92ae9ac 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -100,7 +100,7 @@ export class TeleReporterEmitter implements ReporterV2 { params: { testId: test.id, resultId: (result as any)[this._idSymbol], - step: this._serializeStepEnd(step) + step: this._serializeStepEnd(step, result) } }); } @@ -251,11 +251,12 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _serializeStepEnd(step: reporterTypes.TestStep): teleReceiver.JsonTestStepEnd { + private _serializeStepEnd(step: reporterTypes.TestStep, result: reporterTypes.TestResult): teleReceiver.JsonTestStepEnd { return { id: (step as any)[this._idSymbol], duration: step.duration, error: step.error, + attachments: step.attachments.map(a => result.attachments.indexOf(a)), }; } diff --git a/packages/playwright/src/runner/dispatcher.ts b/packages/playwright/src/runner/dispatcher.ts index 98e0ec1546..534fe7eb4a 100644 --- a/packages/playwright/src/runner/dispatcher.ts +++ b/packages/playwright/src/runner/dispatcher.ts @@ -320,6 +320,7 @@ class JobDispatcher { startTime: new Date(params.wallTime), duration: -1, steps: [], + attachments: [], location: params.location, }; steps.set(params.stepId, step); @@ -361,6 +362,13 @@ class JobDispatcher { body: params.body !== undefined ? Buffer.from(params.body, 'base64') : undefined }; data.result.attachments.push(attachment); + if (params.stepId) { + const step = data.steps.get(params.stepId); + if (step) + step.attachments.push(attachment); + else + this._reporter.onStdErr?.('Internal error: step id not found: ' + params.stepId); + } } private _failTestWithErrors(test: TestCase, errors: TestError[]) { diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 8b965e0a14..6577e19d0d 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -17,6 +17,7 @@ import fs from 'fs'; import path from 'path'; import { captureRawStack, monotonicTime, zones, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils'; +import type { ExpectZone } from 'playwright-core/lib/utils'; import type { TestInfo, TestStatus, FullProject } from '../../types/test'; import type { AttachmentPayload, StepBeginPayload, StepEndPayload, TestInfoErrorImpl, WorkerInitParams } from '../common/ipc'; import type { TestCase } from '../common/test'; @@ -26,12 +27,12 @@ import type { Annotation, FullConfigInternal, FullProjectInternal } from '../com import type { FullConfig, Location } from '../../types/testReporter'; import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util'; import { TestTracing } from './testTracing'; -import type { Attachment } from './testTracing'; import type { StackFrame } from '@protocol/channels'; import { testInfoError } from './util'; export interface TestStepInternal { - complete(result: { error?: Error | unknown, attachments?: Attachment[], suggestedRebaseline?: string }): void; + complete(result: { error?: Error | unknown, suggestedRebaseline?: string }): void; + attachmentIndices: number[]; stepId: string; title: string; category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string; @@ -69,6 +70,7 @@ export class TestInfoImpl implements TestInfo { readonly _projectInternal: FullProjectInternal; readonly _configInternal: FullConfigInternal; private readonly _steps: TestStepInternal[] = []; + private readonly _stepMap = new Map(); _onDidFinishTestFunction: (() => Promise) | undefined; _hasNonRetriableError = false; _hasUnhandledError = false; @@ -193,7 +195,7 @@ export class TestInfoImpl implements TestInfo { this._attachmentsPush = this.attachments.push.bind(this.attachments); this.attachments.push = (...attachments: TestInfo['attachments']) => { for (const a of attachments) - this._attach(a.name, a); + this._attach(a, this._expectStepId() ?? this._parentStep()?.stepId); return this.attachments.length; }; @@ -238,7 +240,16 @@ export class TestInfoImpl implements TestInfo { } } - _addStep(data: Omit, parentStep?: TestStepInternal): TestStepInternal { + private _parentStep() { + return zones.zoneData('stepZone') + ?? this._findLastStageStep(this._steps); // If no parent step on stack, assume the current stage as parent. + } + + private _expectStepId() { + return zones.zoneData('expectZone')?.stepId; + } + + _addStep(data: Omit, parentStep?: TestStepInternal): TestStepInternal { const stepId = `${data.category}@${++this._lastStepId}`; if (data.isStage) { @@ -246,11 +257,7 @@ export class TestInfoImpl implements TestInfo { parentStep = this._findLastStageStep(this._steps); } else { if (!parentStep) - parentStep = zones.zoneData('stepZone'); - if (!parentStep) { - // If no parent step on stack, assume the current stage as parent. - parentStep = this._findLastStageStep(this._steps); - } + parentStep = this._parentStep(); } const filteredStack = filteredStackTrace(captureRawStack()); @@ -261,10 +268,12 @@ export class TestInfoImpl implements TestInfo { } data.location = data.location || filteredStack[0]; + const attachmentIndices: number[] = []; const step: TestStepInternal = { stepId, ...data, steps: [], + attachmentIndices, complete: result => { if (step.endWallTime) return; @@ -301,11 +310,13 @@ export class TestInfoImpl implements TestInfo { }; this._onStepEnd(payload); const errorForTrace = step.error ? { name: '', message: step.error.message || '', stack: step.error.stack } : undefined; - this._tracing.appendAfterActionForStep(stepId, errorForTrace, result.attachments); + const attachments = attachmentIndices.map(i => this.attachments[i]); + this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments); } }; const parentStepList = parentStep ? parentStep.steps : this._steps; parentStepList.push(step); + this._stepMap.set(stepId, step); const payload: StepBeginPayload = { testId: this.testId, stepId, @@ -400,23 +411,33 @@ export class TestInfoImpl implements TestInfo { // ------------ TestInfo methods ------------ async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) { - this._attach(name, await normalizeAndSaveAttachment(this.outputPath(), name, options)); - } - - private _attach(name: string, attachment: TestInfo['attachments'][0]) { const step = this._addStep({ title: `attach "${name}"`, category: 'attach', }); - this._attachmentsPush(attachment); + this._attach(await normalizeAndSaveAttachment(this.outputPath(), name, options), step.stepId); + step.complete({}); + } + + private _attach(attachment: TestInfo['attachments'][0], stepId: string | undefined) { + const index = this._attachmentsPush(attachment) - 1; + if (stepId) { + this._stepMap.get(stepId)!.attachmentIndices.push(index); + } else { + // trace viewer has no means of representing attachments outside of a step, so we create an artificial action + const callId = `attach@${++this._lastStepId}`; + this._tracing.appendBeforeActionForStep(callId, this._findLastStageStep(this._steps)?.stepId, `attach "${attachment.name}"`, undefined, []); + this._tracing.appendAfterActionForStep(callId, undefined, [attachment]); + } + this._onAttach({ testId: this.testId, name: attachment.name, contentType: attachment.contentType, path: attachment.path, - body: attachment.body?.toString('base64') + body: attachment.body?.toString('base64'), + stepId, }); - step.complete({ attachments: [attachment] }); } outputPath(...pathSegments: string[]){ diff --git a/packages/playwright/types/testReporter.d.ts b/packages/playwright/types/testReporter.d.ts index 04cf03287f..3f3a43984e 100644 --- a/packages/playwright/types/testReporter.d.ts +++ b/packages/playwright/types/testReporter.d.ts @@ -691,6 +691,33 @@ export interface TestStep { */ titlePath(): Array; + /** + * The list of files or buffers attached in the step execution through + * [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach). + */ + attachments: Array<{ + /** + * Attachment name. + */ + name: string; + + /** + * Content type of this attachment to properly present in the report, for example `'application/json'` or + * `'image/png'`. + */ + contentType: string; + + /** + * Optional path on the filesystem to the attached file. + */ + path?: string; + + /** + * Optional attachment body used instead of a file. + */ + body?: Buffer; + }>; + /** * Step category to differentiate steps with different origin and verbosity. Built-in categories are: * - `hook` for fixtures and hooks initialization and teardown diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 5c5d6c304a..9f06e8f3b4 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -540,7 +540,7 @@ test('should include attachments by default', async ({ runInlineTest, server }, contentType: 'text/plain', sha1: expect.any(String), }]); - expect([...trace.resources.keys()].filter(f => f.startsWith('resources/'))).toHaveLength(1); + expect([...trace.resources.keys()]).toContain(`resources/${trace.actions[1].attachments[0].sha1}`); }); test('should opt out of attachments', async ({ runInlineTest, server }, testInfo) => { @@ -566,7 +566,7 @@ test('should opt out of attachments', async ({ runInlineTest, server }, testInfo 'After Hooks', ]); expect(trace.actions[1].attachments).toEqual(undefined); - expect([...trace.resources.keys()].filter(f => f.startsWith('resources/'))).toHaveLength(0); + expect([...trace.resources.keys()].filter(f => f.startsWith('resources/') && !f.startsWith('resources/src@'))).toHaveLength(0); }); test('should record with custom page fixture', async ({ runInlineTest }, testInfo) => { @@ -761,7 +761,7 @@ test('should not throw when screenshot on failure fails', async ({ runInlineTest expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-has-download-page', 'trace.zip')); - const attachedScreenshots = trace.actionTree.filter(s => s.trim() === `attach "screenshot"`); + const attachedScreenshots = trace.actions.flatMap(a => a.attachments); // One screenshot for the page, no screenshot for the download page since it should have failed. expect(attachedScreenshots.length).toBe(1); }); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 556d12e8a2..7c7c60836f 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -941,6 +941,32 @@ for (const useIntermediateMergeReport of [true, false] as const) { await expect(attachment).toBeInViewport(); }); + test('steps with internal attachments have links', async ({ runInlineTest, page, showReport }) => { + const result = await runInlineTest({ + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('passing', async ({ page }, testInfo) => { + for (let i = 0; i < 100; i++) + await testInfo.attach('spacer', { body: 'content' }); + + await test.step('step', async () => { + testInfo.attachments.push({ name: 'attachment', body: 'content', contentType: 'text/plain' }); + }) + + }); + `, + }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); + expect(result.exitCode).toBe(0); + + await showReport(); + await page.getByRole('link', { name: 'passing' }).click(); + + const attachment = page.getByText('attachment', { exact: true }); + await expect(attachment).not.toBeInViewport(); + await page.getByLabel('step').getByTitle('link to attachment').click(); + await expect(attachment).toBeInViewport(); + }); + test('should highlight textual diff', async ({ runInlineTest, showReport, page }) => { const result = await runInlineTest({ 'helper.ts': ` diff --git a/tests/playwright-test/reporter.spec.ts b/tests/playwright-test/reporter.spec.ts index f036e3e494..d91702620a 100644 --- a/tests/playwright-test/reporter.spec.ts +++ b/tests/playwright-test/reporter.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { Reporter, TestCase, TestResult, TestStep } from '../../packages/playwright-test/reporter'; import { test, expect } from './playwright-test-fixtures'; const smallReporterJS = ` @@ -703,3 +704,33 @@ onEnd onExit `); }); + +test('step attachments are referentially equal to result attachments', async ({ runInlineTest }) => { + class TestReporter implements Reporter { + onStepEnd(test: TestCase, result: TestResult, step: TestStep) { + console.log('%%%', JSON.stringify({ + title: step.title, + attachments: step.attachments.map(a => result.attachments.indexOf(a)), + })); + } + } + const result = await runInlineTest({ + 'reporter.ts': `module.exports = ${TestReporter.toString()}`, + 'playwright.config.ts': `module.exports = { reporter: './reporter' };`, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({}, testInfo) => { + await test.step('step', async () => { + testInfo.attachments.push({ name: 'attachment', body: Buffer.from('content') }); + }); + }); + `, + }, { 'reporter': '', 'workers': 1 }); + + const steps = result.outputLines.map(line => JSON.parse(line)); + expect(steps).toEqual([ + { title: 'Before Hooks', attachments: [] }, + { title: 'step', attachments: [0] }, + { title: 'After Hooks', attachments: [] }, + ]); +}); diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 83642bd19e..3afa7a8d90 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -263,11 +263,7 @@ test('should report toHaveScreenshot step with expectation name in title', async `end browserContext.newPage`, `end fixture: page`, `end Before Hooks`, - `end attach "foo-expected.png"`, - `end attach "foo-actual.png"`, `end expect.toHaveScreenshot(foo.png)`, - `end attach "is-a-test-1-expected.png"`, - `end attach "is-a-test-1-actual.png"`, `end expect.toHaveScreenshot(is-a-test-1.png)`, `end fixture: page`, `end fixture: context`, @@ -681,6 +677,30 @@ test('should write missing expectations locally twice and attach them', async ({ ]); }); +test('should attach missing expectations to right step', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'reporter.ts': ` + class Reporter { + onStepEnd(test, result, step) { + if (step.attachments.length > 0) + console.log(\`%%\${step.title}: \${step.attachments.map(a => a.name).join(", ")}\`); + } + } + module.exports = Reporter; + `, + ...playwrightConfig({ reporter: [['dot'], ['./reporter']] }), + 'a.spec.js': ` + const { test, expect } = require('@playwright/test'); + test('is a test', async ({ page }) => { + await expect(page).toHaveScreenshot('snapshot.png'); + }); + `, + }, { reporter: '' }); + + expect(result.exitCode).toBe(1); + expect(result.outputLines).toEqual(['expect.toHaveScreenshot(snapshot.png): snapshot-expected.png, snapshot-actual.png']); +}); + test('shouldn\'t write missing expectations locally for negated matcher', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ ...playwrightConfig({ diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index 06cff62399..23a321338b 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -94,8 +94,6 @@ test('should merge screenshot assertions', async ({ runUITest }, testInfo) => { /Before Hooks[\d.]+m?s/, /page.setContent[\d.]+m?s/, /expect.toHaveScreenshot[\d.]+m?s/, - /attach "trace-test-1-expected.png/, - /attach "trace-test-1-actual.png/, /After Hooks[\d.]+m?s/, /Worker Cleanup[\d.]+m?s/, ]); @@ -425,3 +423,50 @@ test('should show custom fixture titles in actions tree', async ({ runUITest }) /After Hooks[\d.]+m?s/, ]); }); + +test('attachments tab shows all but top-level .push attachments', async ({ runUITest }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('attachment test', async ({}) => { + await test.step('step', async () => { + test.info().attachments.push({ + name: 'foo-push', + body: Buffer.from('foo-content'), + contentType: 'text/plain' + }); + + await test.info().attach('foo-attach', { body: 'foo-content' }) + }); + + test.info().attachments.push({ + name: 'bar-push', + body: Buffer.from('bar-content'), + contentType: 'text/plain' + }); + await test.info().attach('bar-attach', { body: 'bar-content' }) + }); + `, + }); + + await page.getByRole('treeitem', { name: 'attachment test' }).dblclick(); + const actionsTree = page.getByTestId('actions-tree'); + await actionsTree.getByRole('treeitem', { name: 'step' }).click(); + await page.keyboard.press('ArrowRight'); + await expect(actionsTree, 'attach() and top-level attachments.push calls are shown as actions').toMatchAriaSnapshot(` + - tree: + - treeitem /step/: + - group: + - treeitem /attach \\"foo-attach\\"/ + - treeitem /attach \\"bar-push\\"/ + - treeitem /attach \\"bar-attach\\"/ + `); + await page.getByRole('tab', { name: 'Attachments' }).click(); + await expect(page.getByRole('tabpanel', { name: 'Attachments' })).toMatchAriaSnapshot(` + - tabpanel: + - button /foo-push/ + - button /foo-attach/ + - button /bar-push/ + - button /bar-attach/ + `); +}); From 6bdd2694ee6b0d34bfc8fb4151223c6f3e31a9e5 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 3 Jan 2025 11:34:34 +0100 Subject: [PATCH 39/83] feat(webserver): customize shutdown with new `gracefulShutdown` option (#34130) Signed-off-by: Simon Knott Co-authored-by: Dmitry Gozman --- docs/src/test-api/class-testconfig.md | 3 + docs/src/test-webserver-js.md | 1 + .../src/utils/processLauncher.ts | 2 +- .../playwright/src/plugins/webServerPlugin.ts | 30 +++++++-- packages/playwright/types/test.d.ts | 12 ++++ tests/playwright-test/web-server.spec.ts | 64 +++++++++++++++++++ 6 files changed, 106 insertions(+), 6 deletions(-) diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 37b9ca5f27..90425fcf4c 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -629,6 +629,9 @@ export default defineConfig({ - `stdout` ?<["pipe"|"ignore"]> If `"pipe"`, it will pipe the stdout of the command to the process stdout. If `"ignore"`, it will ignore the stdout of the command. Default to `"ignore"`. - `stderr` ?<["pipe"|"ignore"]> Whether to pipe the stderr of the command to the process stderr or ignore it. Defaults to `"pipe"`. - `timeout` ?<[int]> How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. + - `gracefulShutdown` ?<[Object]> How to shut down the process. If unspecified, the process group is forcefully `SIGKILL`ed. If set to `{ signal: 'SIGINT', timeout: 500 }`, the process group is sent a `SIGINT` signal, followed by `SIGKILL` if it doesn't exit within 500ms. You can also use `SIGTERM` instead. A `0` timeout means no `SIGKILL` will be sent. Windows doesn't support `SIGINT` and `SIGTERM` signals, so this option is ignored. + - `signal` <["SIGINT"|"SIGTERM"]> + - `timeout` <[int]> - `url` ?<[string]> The url on your http server that is expected to return a 2xx, 3xx, 400, 401, 402, or 403 status code when the server is ready to accept connections. Redirects (3xx status codes) are being followed and the new location is checked. Either `port` or `url` should be specified. Launch a development web server (or multiple) during the tests. diff --git a/docs/src/test-webserver-js.md b/docs/src/test-webserver-js.md index bf01a5cd27..f4c86197c0 100644 --- a/docs/src/test-webserver-js.md +++ b/docs/src/test-webserver-js.md @@ -37,6 +37,7 @@ export default defineConfig({ | `stdout` | If `"pipe"`, it will pipe the stdout of the command to the process stdout. If `"ignore"`, it will ignore the stdout of the command. Default to `"ignore"`. | | `stderr` | Whether to pipe the stderr of the command to the process stderr or ignore it. Defaults to `"pipe"`. | | `timeout` | How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. | +| `gracefulShutdown` | How to shut down the process. If unspecified, the process group is forcefully `SIGKILL`ed. If set to `{ signal: 'SIGINT', timeout: 500 }`, the process group is sent a `SIGINT` signal, followed by `SIGKILL` if it doesn't exit within 500ms. You can also use `SIGTERM` instead. A `0` timeout means no `SIGKILL` will be sent. Windows doesn't support `SIGINT` and `SIGTERM` signals, so this option is ignored. | ## Adding a server timeout diff --git a/packages/playwright-core/src/utils/processLauncher.ts b/packages/playwright-core/src/utils/processLauncher.ts index 4e6c1030b2..1310f95277 100644 --- a/packages/playwright-core/src/utils/processLauncher.ts +++ b/packages/playwright-core/src/utils/processLauncher.ts @@ -180,7 +180,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise {}; const waitForCleanup = new Promise(f => fulfillCleanup = f); - spawnedProcess.once('exit', (exitCode, signal) => { + spawnedProcess.once('close', (exitCode, signal) => { options.log(`[pid=${spawnedProcess.pid}] `); processClosed = true; gracefullyCloseSet.delete(gracefullyClose); diff --git a/packages/playwright/src/plugins/webServerPlugin.ts b/packages/playwright/src/plugins/webServerPlugin.ts index e2474f35f2..002ad235bd 100644 --- a/packages/playwright/src/plugins/webServerPlugin.ts +++ b/packages/playwright/src/plugins/webServerPlugin.ts @@ -30,6 +30,7 @@ export type WebServerPluginOptions = { url?: string; ignoreHTTPSErrors?: boolean; timeout?: number; + gracefulShutdown?: { signal: 'SIGINT' | 'SIGTERM', timeout?: number }; reuseExistingServer?: boolean; cwd?: string; env?: { [key: string]: string; }; @@ -92,7 +93,7 @@ export class WebServerPlugin implements TestRunnerPlugin { } debugWebServer(`Starting WebServer process ${this._options.command}...`); - const { launchedProcess, kill } = await launchProcess({ + const { launchedProcess, gracefullyClose } = await launchProcess({ command: this._options.command, env: { ...DEFAULT_ENVIRONMENT_VARIABLES, @@ -102,14 +103,33 @@ export class WebServerPlugin implements TestRunnerPlugin { cwd: this._options.cwd, stdio: 'stdin', shell: true, - // Reject to indicate that we cannot close the web server gracefully - // and should fallback to non-graceful shutdown. - attemptToGracefullyClose: () => Promise.reject(), + attemptToGracefullyClose: async () => { + if (process.platform === 'win32') + throw new Error('Graceful shutdown is not supported on Windows'); + if (!this._options.gracefulShutdown) + throw new Error('skip graceful shutdown'); + + const { signal, timeout = 0 } = this._options.gracefulShutdown; + + // proper usage of SIGINT is to send it to the entire process group, see https://www.cons.org/cracauer/sigint.html + // there's no such convention for SIGTERM, so we decide what we want. signaling the process group for consistency. + process.kill(-launchedProcess.pid!, signal); + + return new Promise((resolve, reject) => { + const timer = timeout !== 0 + ? setTimeout(() => reject(new Error(`process didn't close gracefully within timeout`)), timeout) + : undefined; + launchedProcess.once('close', (...args) => { + clearTimeout(timer); + resolve(); + }); + }); + }, log: () => {}, onExit: code => processExitedReject(new Error(code ? `Process from config.webServer was not able to start. Exit code: ${code}` : 'Process from config.webServer exited early.')), tempDirectories: [], }); - this._killProcess = kill; + this._killProcess = gracefullyClose; debugWebServer(`Process started`); diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 7c9cae5415..cff8c8ca60 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9657,6 +9657,18 @@ interface TestConfigWebServer { */ timeout?: number; + /** + * How to shut down the process. If unspecified, the process group is forcefully `SIGKILL`ed. If set to `{ signal: + * 'SIGINT', timeout: 500 }`, the process group is sent a `SIGINT` signal, followed by `SIGKILL` if it doesn't exit + * within 500ms. You can also use `SIGTERM` instead. A `0` timeout means no `SIGKILL` will be sent. Windows doesn't + * support `SIGINT` and `SIGTERM` signals, so this option is ignored. + */ + gracefulShutdown?: { + signal: "SIGINT"|"SIGTERM"; + + timeout: number; + }; + /** * The url on your http server that is expected to return a 2xx, 3xx, 400, 401, 402, or 403 status code when the * server is ready to accept connections. Redirects (3xx status codes) are being followed and the new location is diff --git a/tests/playwright-test/web-server.spec.ts b/tests/playwright-test/web-server.spec.ts index 04e3c1d328..6caed12a42 100644 --- a/tests/playwright-test/web-server.spec.ts +++ b/tests/playwright-test/web-server.spec.ts @@ -17,6 +17,7 @@ import type http from 'http'; import path from 'path'; import { test, expect, parseTestRunnerOutput } from './playwright-test-fixtures'; +import type { RunResult } from './playwright-test-fixtures'; import { createHttpServer } from '../../packages/playwright-core/lib/utils/network'; const SIMPLE_SERVER_PATH = path.join(__dirname, 'assets', 'simple-server.js'); @@ -744,3 +745,66 @@ test('should forward stdout when set to "pipe" before server is ready', async ({ expect(result.output).toContain('[WebServer] output from server'); expect(result.output).not.toContain('Timed out waiting 3000ms'); }); + +test.describe('gracefulShutdown option', () => { + test.skip(process.platform === 'win32', 'No sending SIGINT on Windows'); + + const files = (additionalOptions = {}) => { + const port = test.info().workerIndex * 2 + 10510; + return { + 'child.js': ` + process.on('SIGINT', () => { console.log('%%childprocess received SIGINT'); setTimeout(() => process.exit(), 10) }) + process.on('SIGTERM', () => { console.log('%%childprocess received SIGTERM'); setTimeout(() => process.exit(), 10) }) + setTimeout(() => {}, 100000) // prevent child from exiting + `, + 'web-server.js': ` + require("node:child_process").fork('./child.js', { silent: false }) + + process.on('SIGINT', () => { + console.log('%%webserver received SIGINT but stubbornly refuses to wind down') + }) + process.on('SIGTERM', () => { + console.log('%%webserver received SIGTERM but stubbornly refuses to wind down') + }) + + const server = require("node:http").createServer((req, res) => { res.end("ok"); }) + server.listen(process.argv[2]); + `, + 'test.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({}) => {}); + `, + 'playwright.config.ts': ` + module.exports = { + webServer: { + command: 'echo some-precondition && node web-server.js ${port}', + port: ${port}, + stdout: 'pipe', + timeout: 3000, + ...${JSON.stringify(additionalOptions)} + }, + }; + `, + }; + }; + + function parseOutputLines(result: RunResult): string[] { + const prefix = '[WebServer] %%'; + return result.output.split('\n').filter(line => line.startsWith(prefix)).map(line => line.substring(prefix.length)); + } + + test('sends SIGKILL by default', async ({ runInlineTest }) => { + const result = await runInlineTest(files(), { workers: 1 }); + expect(parseOutputLines(result)).toEqual([]); + }); + + test('can be configured to send SIGTERM', async ({ runInlineTest }) => { + const result = await runInlineTest(files({ gracefulShutdown: { signal: 'SIGTERM', timeout: 500 } }), { workers: 1 }); + expect(parseOutputLines(result).sort()).toEqual(['childprocess received SIGTERM', 'webserver received SIGTERM but stubbornly refuses to wind down']); + }); + + test('can be configured to send SIGINT', async ({ runInlineTest }) => { + const result = await runInlineTest(files({ gracefulShutdown: { signal: 'SIGINT', timeout: 500 } }), { workers: 1 }); + expect(parseOutputLines(result).sort()).toEqual(['childprocess received SIGINT', 'webserver received SIGINT but stubbornly refuses to wind down']); + }); +}); From dca95ba609a445b71f84501f8ee487c1b114e219 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 3 Jan 2025 10:39:32 -0800 Subject: [PATCH 40/83] fix(bidi): set initial frame url from creation event (#34198) --- packages/playwright-core/src/server/bidi/bidiBrowser.ts | 4 ++++ tests/bidi/expectations/bidi-chromium-library.txt | 2 +- tests/bidi/expectations/bidi-firefox-nightly-library.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts index 955f6274a3..96c48ea2a8 100644 --- a/packages/playwright-core/src/server/bidi/bidiBrowser.ts +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -152,6 +152,9 @@ export class BidiBrowser extends Browser { continue; page._session.addFrameBrowsingContext(event.context); page._page._frameManager.frameAttached(event.context, parentFrameId); + const frame = page._page._frameManager.frame(event.context); + if (frame) + frame._url = event.url; return; } return; @@ -164,6 +167,7 @@ export class BidiBrowser extends Browser { const session = this._connection.createMainFrameBrowsingContextSession(event.context); const opener = event.originalOpener && this._bidiPages.get(event.originalOpener); const page = new BidiPage(context, session, opener || null); + page._page.mainFrame()._url = event.url; this._bidiPages.set(event.context, page); } diff --git a/tests/bidi/expectations/bidi-chromium-library.txt b/tests/bidi/expectations/bidi-chromium-library.txt index 13dbf66eb5..ecdb4e425c 100644 --- a/tests/bidi/expectations/bidi-chromium-library.txt +++ b/tests/bidi/expectations/bidi-chromium-library.txt @@ -310,7 +310,7 @@ library/browsercontext-network-event.spec.ts › BrowserContext.Events.RequestFi library/browsercontext-network-event.spec.ts › BrowserContext.Events.Response [pass] library/browsercontext-network-event.spec.ts › should fire events in proper order [pass] library/browsercontext-network-event.spec.ts › should not fire events for favicon or favicon redirects [unknown] -library/browsercontext-page-event.spec.ts › should fire page lifecycle events [fail] +library/browsercontext-page-event.spec.ts › should fire page lifecycle events [pass] library/browsercontext-page-event.spec.ts › should have about:blank for empty url with domcontentloaded [fail] library/browsercontext-page-event.spec.ts › should have about:blank url with domcontentloaded [fail] library/browsercontext-page-event.spec.ts › should have an opener [pass] diff --git a/tests/bidi/expectations/bidi-firefox-nightly-library.txt b/tests/bidi/expectations/bidi-firefox-nightly-library.txt index fd0ce6cea6..f0920dbee4 100644 --- a/tests/bidi/expectations/bidi-firefox-nightly-library.txt +++ b/tests/bidi/expectations/bidi-firefox-nightly-library.txt @@ -312,7 +312,7 @@ library/browsercontext-network-event.spec.ts › BrowserContext.Events.Response library/browsercontext-network-event.spec.ts › should fire events in proper order [pass] library/browsercontext-network-event.spec.ts › should not fire events for favicon or favicon redirects [unknown] library/browsercontext-network-event.spec.ts › should reject response.finished if context closes [timeout] -library/browsercontext-page-event.spec.ts › should fire page lifecycle events [fail] +library/browsercontext-page-event.spec.ts › should fire page lifecycle events [pass] library/browsercontext-page-event.spec.ts › should have about:blank for empty url with domcontentloaded [timeout] library/browsercontext-page-event.spec.ts › should have about:blank url with domcontentloaded [pass] library/browsercontext-page-event.spec.ts › should have an opener [pass] From 8b45ea6f2f1c176d63909a2c4199008177f687ae Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 3 Jan 2025 12:16:01 -0800 Subject: [PATCH 41/83] chore: properly initialize Touch arguments in TouchEvent (#34200) --- .../src/server/injected/injectedScript.ts | 13 +++- .../locator-dispatchevent-touch.spec.ts | 59 +++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tests/library/locator-dispatchevent-touch.spec.ts diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 21fcca69a4..c3d6e296e5 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -996,13 +996,20 @@ export class InjectedScript { return { stop }; } - dispatchEvent(node: Node, type: string, eventInit: Object) { + dispatchEvent(node: Node, type: string, eventInitObj: Object) { let event; - eventInit = { bubbles: true, cancelable: true, composed: true, ...eventInit }; + const eventInit: any = { bubbles: true, cancelable: true, composed: true, ...eventInitObj }; switch (eventType.get(type)) { case 'mouse': event = new MouseEvent(type, eventInit); break; case 'keyboard': event = new KeyboardEvent(type, eventInit); break; - case 'touch': event = new TouchEvent(type, eventInit); break; + case 'touch': { + eventInit.target ??= node; + eventInit.touches = eventInit.touches?.map((t: any) => t instanceof Touch ? t : new Touch({ ...t, target: t.target ?? node })); + eventInit.targetTouches = eventInit.targetTouches?.map((t: any) => t instanceof Touch ? t : new Touch({ ...t, target: t.target ?? node })); + eventInit.changedTouches = eventInit.changedTouches?.map((t: any) => t instanceof Touch ? t : new Touch({ ...t, target: t.target ?? node })); + event = new TouchEvent(type, eventInit); + break; + } case 'pointer': event = new PointerEvent(type, eventInit); break; case 'focus': event = new FocusEvent(type, eventInit); break; case 'drag': event = new DragEvent(type, eventInit); break; diff --git a/tests/library/locator-dispatchevent-touch.spec.ts b/tests/library/locator-dispatchevent-touch.spec.ts new file mode 100644 index 0000000000..c811c847f0 --- /dev/null +++ b/tests/library/locator-dispatchevent-touch.spec.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { contextTest as it, expect } from '../config/browserTest'; + +it.use({ hasTouch: true }); + +it('should support touch points in touch event arguments', async ({ page, server, browserName }) => { + it.fixme(browserName === 'webkit', 'WebKit does not have Touch constructor'); + await page.goto(server.EMPTY_PAGE); + await page.setContent(` +
+
inner
+
`); + const outer = page.getByTestId('outer'); + await outer.evaluate(el => { + const events = []; + (window as any).events = events; + el.addEventListener('touchstart', (e: TouchEvent) => events.push('touchstart: ' + [...e.touches].map(t => `${t.constructor.name}(id: ${t.identifier}, clientX: ${t.clientX}, clientY: ${t.clientY})`))); + el.addEventListener('touchmove', (e: TouchEvent) => events.push('touchmove: ' + [...e.touches].map(t => `${t.constructor.name}(id: ${t.identifier}, clientX: ${t.clientX}, clientY: ${t.clientY})`))); + el.addEventListener('touchend', (e: TouchEvent) => events.push('touchend: ' + [...e.touches].map(t => `${t.constructor.name}(id: ${t.identifier}, clientX: ${t.clientX}, clientY: ${t.clientY})`))); + }); + + const touches = [{ identifier: 0, clientX: 61, clientY: 60 }, { identifier: 1, clientX: 59, clientY: 60 }]; + const inner = page.getByTestId('inner'); + await inner.dispatchEvent('touchstart', { + touches, + changedTouches: touches, + targetTouches: touches, + }); + await inner.dispatchEvent('touchmove', { + touches, + changedTouches: touches, + targetTouches: touches, + }); + await inner.dispatchEvent('touchend', { + touches: [], + changedTouches: touches, + targetTouches: [], + }); + expect(await page.evaluate(() => (window as any).events)).toEqual([ + 'touchstart: Touch(id: 0, clientX: 61, clientY: 60),Touch(id: 1, clientX: 59, clientY: 60)', + 'touchmove: Touch(id: 0, clientX: 61, clientY: 60),Touch(id: 1, clientX: 59, clientY: 60)', + 'touchend: ', + ]); +}); From 5a22475ea81380bbb432fe6a135e0047ff36ad25 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 3 Jan 2025 12:37:28 -0800 Subject: [PATCH 42/83] chore(bidi): fix signals tests (#34209) --- packages/playwright-core/src/browserServerImpl.ts | 4 ++-- packages/playwright-core/src/inProcessFactory.ts | 2 ++ tests/bidi/expectations/bidi-chromium-library.txt | 10 ---------- .../bidi/expectations/bidi-firefox-nightly-library.txt | 10 ---------- tests/config/browserTest.ts | 4 ++-- tests/config/remoteServer.ts | 9 ++++++++- 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index dfe960c5ea..d59c95bfb1 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -29,9 +29,9 @@ import { rewriteErrorMessage } from './utils/stackTrace'; import { SocksProxy } from './common/socksProxy'; export class BrowserServerLauncherImpl implements BrowserServerLauncher { - private _browserName: 'chromium' | 'firefox' | 'webkit'; + private _browserName: 'chromium' | 'firefox' | 'webkit' | 'bidiFirefox' | 'bidiChromium'; - constructor(browserName: 'chromium' | 'firefox' | 'webkit') { + constructor(browserName: 'chromium' | 'firefox' | 'webkit' | 'bidiFirefox' | 'bidiChromium') { this._browserName = browserName; } diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 1397c81958..a757294da8 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -41,6 +41,8 @@ export function createInProcessPlaywright(): PlaywrightAPI { playwrightAPI.firefox._serverLauncher = new BrowserServerLauncherImpl('firefox'); playwrightAPI.webkit._serverLauncher = new BrowserServerLauncherImpl('webkit'); playwrightAPI._android._serverLauncher = new AndroidServerLauncherImpl(); + playwrightAPI._bidiChromium._serverLauncher = new BrowserServerLauncherImpl('bidiChromium'); + playwrightAPI._bidiFirefox._serverLauncher = new BrowserServerLauncherImpl('bidiFirefox'); // Switch to async dispatch after we got Playwright object. dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message)); diff --git a/tests/bidi/expectations/bidi-chromium-library.txt b/tests/bidi/expectations/bidi-chromium-library.txt index ecdb4e425c..e49e4cedb2 100644 --- a/tests/bidi/expectations/bidi-chromium-library.txt +++ b/tests/bidi/expectations/bidi-chromium-library.txt @@ -1674,16 +1674,6 @@ library/selectors-register.spec.ts › should work in main and isolated world [p library/selectors-register.spec.ts › should work when registered on global [pass] library/selectors-register.spec.ts › should work with path [pass] library/shared-worker.spec.ts › should survive shared worker restart [timeout] -library/signals.spec.ts › should close the browser when the node process closes [timeout] -library/signals.spec.ts › should remove temp dir on process.exit [timeout] -library/signals.spec.ts › signals › should close the browser on SIGHUP [timeout] -library/signals.spec.ts › signals › should close the browser on SIGINT [timeout] -library/signals.spec.ts › signals › should close the browser on SIGTERM [timeout] -library/signals.spec.ts › signals › should kill the browser on SIGINT + SIGTERM [timeout] -library/signals.spec.ts › signals › should kill the browser on SIGTERM + SIGINT [timeout] -library/signals.spec.ts › signals › should kill the browser on double SIGINT and remove temp dir [timeout] -library/signals.spec.ts › signals › should not prevent default SIGTERM handling after browser close [timeout] -library/signals.spec.ts › signals › should report browser close signal 2 [timeout] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo check [pass] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo click [pass] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dblclick [pass] diff --git a/tests/bidi/expectations/bidi-firefox-nightly-library.txt b/tests/bidi/expectations/bidi-firefox-nightly-library.txt index f0920dbee4..c4d8b933fa 100644 --- a/tests/bidi/expectations/bidi-firefox-nightly-library.txt +++ b/tests/bidi/expectations/bidi-firefox-nightly-library.txt @@ -1726,16 +1726,6 @@ library/selectors-register.spec.ts › should work in main and isolated world [p library/selectors-register.spec.ts › should work when registered on global [pass] library/selectors-register.spec.ts › should work with path [pass] library/shared-worker.spec.ts › should survive shared worker restart [pass] -library/signals.spec.ts › should close the browser when the node process closes [timeout] -library/signals.spec.ts › should remove temp dir on process.exit [timeout] -library/signals.spec.ts › signals › should close the browser on SIGHUP [timeout] -library/signals.spec.ts › signals › should close the browser on SIGINT [timeout] -library/signals.spec.ts › signals › should close the browser on SIGTERM [timeout] -library/signals.spec.ts › signals › should kill the browser on SIGINT + SIGTERM [timeout] -library/signals.spec.ts › signals › should kill the browser on SIGTERM + SIGINT [timeout] -library/signals.spec.ts › signals › should kill the browser on double SIGINT and remove temp dir [timeout] -library/signals.spec.ts › signals › should not prevent default SIGTERM handling after browser close [timeout] -library/signals.spec.ts › signals › should report browser close signal 2 [timeout] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo check [pass] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo click [pass] library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dblclick [pass] diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 7836b2e38c..9eba4f30d1 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -137,14 +137,14 @@ const test = baseTest.extend await persistentContext.close(); }, - startRemoteServer: async ({ childProcess, browserType }, run) => { + startRemoteServer: async ({ childProcess, browserType, channel }, run) => { let server: PlaywrightServer | undefined; const fn = async (kind: 'launchServer' | 'run-server', options?: RemoteServerOptions) => { if (server) throw new Error('can only start one remote server'); if (kind === 'launchServer') { const remoteServer = new RemoteServer(); - await remoteServer._start(childProcess, browserType, options); + await remoteServer._start(childProcess, browserType, channel, options); server = remoteServer; } else { const runServer = new RunServer(); diff --git a/tests/config/remoteServer.ts b/tests/config/remoteServer.ts index 6d9710fd44..94c476ed80 100644 --- a/tests/config/remoteServer.ts +++ b/tests/config/remoteServer.ts @@ -80,7 +80,7 @@ export class RemoteServer implements PlaywrightServer { _browser: Browser | undefined; _wsEndpoint!: string; - async _start(childProcess: CommonFixtures['childProcess'], browserType: BrowserType, remoteServerOptions: RemoteServerOptions = {}) { + async _start(childProcess: CommonFixtures['childProcess'], browserType: BrowserType, channel: string, remoteServerOptions: RemoteServerOptions = {}) { this._browserType = browserType; const browserOptions = (browserType as any)._defaultLaunchOptions; // Copy options to prevent a large JSON string when launching subprocess. @@ -97,9 +97,16 @@ export class RemoteServer implements PlaywrightServer { }; const options = { browserTypeName: browserType.name(), + channel, launchOptions, ...remoteServerOptions, }; + if ('bidi' === browserType.name()) { + if (channel.toLocaleLowerCase().includes('firefox')) + options.browserTypeName = '_bidiFirefox'; + else + options.browserTypeName = '_bidiChromium'; + } this._process = childProcess({ command: ['node', path.join(__dirname, 'remote-server-impl.js'), JSON.stringify(options)], env: { ...process.env, PWTEST_UNDER_TEST: '1' }, From eeca68ba9714fef056fd60203240126607337a26 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Sun, 5 Jan 2025 18:19:28 +0000 Subject: [PATCH 43/83] test: unflake some cookie tests in msedge (#34217) --- tests/library/defaultbrowsercontext-1.spec.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/library/defaultbrowsercontext-1.spec.ts b/tests/library/defaultbrowsercontext-1.spec.ts index 3aa7357d2f..1516be3487 100644 --- a/tests/library/defaultbrowsercontext-1.spec.ts +++ b/tests/library/defaultbrowsercontext-1.spec.ts @@ -19,7 +19,13 @@ import { playwrightTest as it, expect } from '../config/browserTest'; import { verifyViewport } from '../config/utils'; import fs from 'fs'; -it('context.cookies() should work @smoke', async ({ server, launchPersistent, defaultSameSiteCookieValue }) => { +function maybeFilterCookies(channel: string | undefined, cookies: any[]) { + if (channel?.startsWith('msedge')) + return cookies.filter(c => !c.domain.endsWith('microsoft.com')); + return cookies; +} + +it('context.cookies() should work @smoke', async ({ server, launchPersistent, defaultSameSiteCookieValue, channel }) => { const { page } = await launchPersistent(); await page.goto(server.EMPTY_PAGE); const documentCookie = await page.evaluate(() => { @@ -27,7 +33,7 @@ it('context.cookies() should work @smoke', async ({ server, launchPersistent, de return document.cookie; }); expect(documentCookie).toBe('username=John Doe'); - expect(await page.context().cookies()).toEqual([{ + expect(maybeFilterCookies(channel, await page.context().cookies())).toEqual([{ name: 'username', value: 'John Doe', domain: 'localhost', @@ -39,7 +45,7 @@ it('context.cookies() should work @smoke', async ({ server, launchPersistent, de }]); }); -it('context.addCookies() should work', async ({ server, launchPersistent, browserName, isWindows }) => { +it('context.addCookies() should work', async ({ server, launchPersistent, browserName, isWindows, channel }) => { const { page } = await launchPersistent(); await page.goto(server.EMPTY_PAGE); await page.context().addCookies([{ @@ -49,7 +55,7 @@ it('context.addCookies() should work', async ({ server, launchPersistent, browse sameSite: 'Lax', }]); expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe'); - expect(await page.context().cookies()).toEqual([{ + expect(maybeFilterCookies(channel, await page.context().cookies())).toEqual([{ name: 'username', value: 'John Doe', domain: 'localhost', @@ -61,7 +67,7 @@ it('context.addCookies() should work', async ({ server, launchPersistent, browse }]); }); -it('context.clearCookies() should work', async ({ server, launchPersistent }) => { +it('context.clearCookies() should work', async ({ server, launchPersistent, channel }) => { const { page } = await launchPersistent(); await page.goto(server.EMPTY_PAGE); await page.context().addCookies([{ @@ -76,7 +82,7 @@ it('context.clearCookies() should work', async ({ server, launchPersistent }) => expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2'); await page.context().clearCookies(); await page.reload(); - expect(await page.context().cookies([])).toEqual([]); + expect(maybeFilterCookies(channel, await page.context().cookies([]))).toEqual([]); expect(await page.evaluate('document.cookie')).toBe(''); }); From 4e8c83055fa1d50d5900cd6ae3d98fcc12c949af Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 6 Jan 2025 11:03:35 -0800 Subject: [PATCH 44/83] chore: split output clients by capabilities and base dir (#34135) --- packages/playwright/src/reporters/base.ts | 209 ++++++++++++------ packages/playwright/src/reporters/dot.ts | 16 +- packages/playwright/src/reporters/github.ts | 19 +- packages/playwright/src/reporters/html.ts | 8 +- .../src/reporters/internalReporter.ts | 4 +- packages/playwright/src/reporters/json.ts | 4 +- packages/playwright/src/reporters/junit.ts | 4 +- packages/playwright/src/reporters/line.ts | 14 +- packages/playwright/src/reporters/list.ts | 50 ++--- packages/playwright/src/reporters/markdown.ts | 7 +- packages/playwright/src/runner/reporters.ts | 9 +- packages/playwright/src/runner/runner.ts | 7 +- packages/playwright/src/runner/testServer.ts | 3 +- packages/playwright/src/runner/watchMode.ts | 10 +- tests/playwright-test/reporter-blob.spec.ts | 5 +- 15 files changed, 231 insertions(+), 138 deletions(-) diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 38b69d9d65..ec11413391 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -24,6 +24,8 @@ import { resolveReporterOutputPath } from '../util'; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); +type Colors = typeof realColors; + type ErrorDetails = { message: string; location?: Location; @@ -40,7 +42,57 @@ type TestSummary = { fatalErrors: TestError[]; }; -export const { isTTY, ttyWidth, colors } = (() => { +export type Screen = { + resolveFiles: 'cwd' | 'rootDir'; + colors: Colors; + isTTY: boolean; + ttyWidth: number; +}; + +export const noColors: Colors = { + bold: (t: string) => t, + cyan: (t: string) => t, + dim: (t: string) => t, + gray: (t: string) => t, + green: (t: string) => t, + red: (t: string) => t, + yellow: (t: string) => t, + black: (t: string) => t, + blue: (t: string) => t, + magenta: (t: string) => t, + white: (t: string) => t, + grey: (t: string) => t, + bgBlack: (t: string) => t, + bgRed: (t: string) => t, + bgGreen: (t: string) => t, + bgYellow: (t: string) => t, + bgBlue: (t: string) => t, + bgMagenta: (t: string) => t, + bgCyan: (t: string) => t, + bgWhite: (t: string) => t, + strip: (t: string) => t, + stripColors: (t: string) => t, + reset: (t: string) => t, + italic: (t: string) => t, + underline: (t: string) => t, + inverse: (t: string) => t, + hidden: (t: string) => t, + strikethrough: (t: string) => t, + rainbow: (t: string) => t, + zebra: (t: string) => t, + america: (t: string) => t, + trap: (t: string) => t, + random: (t: string) => t, + zalgo: (t: string) => t, + + enabled: false, + enable: () => {}, + disable: () => {}, + setTheme: () => {}, +}; + +// Output goes to terminal. +export const terminalScreen: Screen = (() => { let isTTY = !!process.stdout.isTTY; let ttyWidth = process.stdout.columns || 0; if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') { @@ -63,20 +115,33 @@ export const { isTTY, ttyWidth, colors } = (() => { else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR) useColors = true; - const colors = useColors ? realColors : { - bold: (t: string) => t, - cyan: (t: string) => t, - dim: (t: string) => t, - gray: (t: string) => t, - green: (t: string) => t, - red: (t: string) => t, - yellow: (t: string) => t, - enabled: false, + const colors = useColors ? realColors : noColors; + return { + resolveFiles: 'cwd', + isTTY, + ttyWidth, + colors }; - return { isTTY, ttyWidth, colors }; })(); -export class BaseReporter implements ReporterV2 { +// Output does not go to terminal, but colors are controlled with terminal env vars. +export const nonTerminalScreen: Screen = { + colors: terminalScreen.colors, + isTTY: false, + ttyWidth: 0, + resolveFiles: 'rootDir', +}; + +// Internal output for post-processing, should always contain real colors. +export const internalScreen: Screen = { + colors: realColors, + isTTY: false, + ttyWidth: 0, + resolveFiles: 'rootDir', +}; + +export class TerminalReporter implements ReporterV2 { + screen: Screen = terminalScreen; config!: FullConfig; suite!: Suite; totalTestCount = 0; @@ -122,7 +187,7 @@ export class BaseReporter implements ReporterV2 { if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount; const projectName = test.titlePath()[1]; - const relativePath = relativeTestPath(this.config, test); + const relativePath = relativeTestPath(this.screen, this.config, test); const fileAndProject = (projectName ? `[${projectName}] › ` : '') + relativePath; const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: new Set() }; entry.duration += result.duration; @@ -139,11 +204,11 @@ export class BaseReporter implements ReporterV2 { } protected fitToScreen(line: string, prefix?: string): string { - if (!ttyWidth) { + if (!this.screen.ttyWidth) { // Guard against the case where we cannot determine available width. return line; } - return fitToWidth(line, ttyWidth, prefix); + return fitToWidth(line, this.screen.ttyWidth, prefix); } protected generateStartingMessage() { @@ -151,7 +216,7 @@ export class BaseReporter implements ReporterV2 { const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : ''; if (!this.totalTestCount) return ''; - return '\n' + colors.dim('Running ') + this.totalTestCount + colors.dim(` test${this.totalTestCount !== 1 ? 's' : ''} using `) + jobs + colors.dim(` worker${jobs !== 1 ? 's' : ''}${shardDetails}`); + return '\n' + this.screen.colors.dim('Running ') + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? 's' : ''} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? 's' : ''}${shardDetails}`); } protected getSlowTests(): [string, number][] { @@ -168,28 +233,28 @@ export class BaseReporter implements ReporterV2 { protected generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }: TestSummary) { const tokens: string[] = []; if (unexpected.length) { - tokens.push(colors.red(` ${unexpected.length} failed`)); + tokens.push(this.screen.colors.red(` ${unexpected.length} failed`)); for (const test of unexpected) - tokens.push(colors.red(formatTestHeader(this.config, test, { indent: ' ' }))); + tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: ' ' }))); } if (interrupted.length) { - tokens.push(colors.yellow(` ${interrupted.length} interrupted`)); + tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`)); for (const test of interrupted) - tokens.push(colors.yellow(formatTestHeader(this.config, test, { indent: ' ' }))); + tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: ' ' }))); } if (flaky.length) { - tokens.push(colors.yellow(` ${flaky.length} flaky`)); + tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`)); for (const test of flaky) - tokens.push(colors.yellow(formatTestHeader(this.config, test, { indent: ' ' }))); + tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: ' ' }))); } if (skipped) - tokens.push(colors.yellow(` ${skipped} skipped`)); + tokens.push(this.screen.colors.yellow(` ${skipped} skipped`)); if (didNotRun) - tokens.push(colors.yellow(` ${didNotRun} did not run`)); + tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`)); if (expected) - tokens.push(colors.green(` ${expected} passed`) + colors.dim(` (${milliseconds(this.result.duration)})`)); + tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${milliseconds(this.result.duration)})`)); if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0) - tokens.push(colors.red(` ${fatalErrors.length === 1 ? '1 error was not a part of any test' : fatalErrors.length + ' errors were not a part of any test'}, see above for details`)); + tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? '1 error was not a part of any test' : fatalErrors.length + ' errors were not a part of any test'}, see above for details`)); return tokens.join('\n'); } @@ -248,17 +313,17 @@ export class BaseReporter implements ReporterV2 { private _printFailures(failures: TestCase[]) { console.log(''); failures.forEach((test, index) => { - console.log(formatFailure(this.config, test, index + 1)); + console.log(this.formatFailure(test, index + 1)); }); } private _printSlowTests() { const slowTests = this.getSlowTests(); slowTests.forEach(([file, duration]) => { - console.log(colors.yellow(' Slow test file: ') + file + colors.yellow(` (${milliseconds(duration)})`)); + console.log(this.screen.colors.yellow(' Slow test file: ') + file + this.screen.colors.yellow(` (${milliseconds(duration)})`)); }); if (slowTests.length) - console.log(colors.yellow(' Consider running tests from slow files in parallel, see https://playwright.dev/docs/test-parallel.')); + console.log(this.screen.colors.yellow(' Consider running tests from slow files in parallel, see https://playwright.dev/docs/test-parallel.')); } private _printSummary(summary: string) { @@ -269,21 +334,37 @@ export class BaseReporter implements ReporterV2 { willRetry(test: TestCase): boolean { return test.outcome() === 'unexpected' && test.results.length <= test.retries; } + + formatTestTitle(test: TestCase, step?: TestStep, omitLocation: boolean = false): string { + return formatTestTitle(this.screen, this.config, test, step, omitLocation); + } + + formatTestHeader(test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string { + return formatTestHeader(this.screen, this.config, test, options); + } + + formatFailure(test: TestCase, index?: number): string { + return formatFailure(this.screen, this.config, test, index); + } + + formatError(error: TestError): ErrorDetails { + return formatError(this.screen, error); + } } -export function formatFailure(config: FullConfig, test: TestCase, index?: number): string { +export function formatFailure(screen: Screen, config: FullConfig, test: TestCase, index?: number): string { const lines: string[] = []; - const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' }); - lines.push(colors.red(header)); + const header = formatTestHeader(screen, config, test, { indent: ' ', index, mode: 'error' }); + lines.push(screen.colors.red(header)); for (const result of test.results) { const resultLines: string[] = []; - const errors = formatResultFailure(test, result, ' ', colors.enabled); + const errors = formatResultFailure(screen, test, result, ' '); if (!errors.length) continue; const retryLines = []; if (result.retry) { retryLines.push(''); - retryLines.push(colors.gray(separator(` Retry #${result.retry}`))); + retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`))); } resultLines.push(...retryLines); resultLines.push(...errors.map(error => '\n' + error.message)); @@ -293,16 +374,16 @@ export function formatFailure(config: FullConfig, test: TestCase, index?: number if (!attachment.path && !hasPrintableContent) continue; resultLines.push(''); - resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`))); + resultLines.push(screen.colors.cyan(separator(screen, ` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`))); if (attachment.path) { const relativePath = path.relative(process.cwd(), attachment.path); - resultLines.push(colors.cyan(` ${relativePath}`)); + resultLines.push(screen.colors.cyan(` ${relativePath}`)); // Make this extensible if (attachment.name === 'trace') { const packageManagerCommand = getPackageManagerExecCommand(); - resultLines.push(colors.cyan(` Usage:`)); + resultLines.push(screen.colors.cyan(` Usage:`)); resultLines.push(''); - resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`)); + resultLines.push(screen.colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`)); resultLines.push(''); } } else { @@ -311,10 +392,10 @@ export function formatFailure(config: FullConfig, test: TestCase, index?: number if (text.length > 300) text = text.slice(0, 300) + '...'; for (const line of text.split('\n')) - resultLines.push(colors.cyan(` ${line}`)); + resultLines.push(screen.colors.cyan(` ${line}`)); } } - resultLines.push(colors.cyan(separator(' '))); + resultLines.push(screen.colors.cyan(separator(screen, ' '))); } lines.push(...resultLines); } @@ -322,11 +403,11 @@ export function formatFailure(config: FullConfig, test: TestCase, index?: number return lines.join('\n'); } -export function formatRetry(result: TestResult) { +export function formatRetry(screen: Screen, result: TestResult) { const retryLines = []; if (result.retry) { retryLines.push(''); - retryLines.push(colors.gray(separator(` Retry #${result.retry}`))); + retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`))); } return retryLines; } @@ -337,22 +418,22 @@ function quotePathIfNeeded(path: string): string { return path; } -export function formatResultFailure(test: TestCase, result: TestResult, initialIndent: string, highlightCode: boolean): ErrorDetails[] { +export function formatResultFailure(screen: Screen, test: TestCase, result: TestResult, initialIndent: string): ErrorDetails[] { const errorDetails: ErrorDetails[] = []; if (result.status === 'passed' && test.expectedStatus === 'failed') { errorDetails.push({ - message: indent(colors.red(`Expected to fail, but passed.`), initialIndent), + message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent), }); } if (result.status === 'interrupted') { errorDetails.push({ - message: indent(colors.red(`Test was interrupted.`), initialIndent), + message: indent(screen.colors.red(`Test was interrupted.`), initialIndent), }); } for (const error of result.errors) { - const formattedError = formatError(error, highlightCode); + const formattedError = formatError(screen, error); errorDetails.push({ message: indent(formattedError.message, initialIndent), location: formattedError.location, @@ -361,12 +442,14 @@ export function formatResultFailure(test: TestCase, result: TestResult, initialI return errorDetails; } -export function relativeFilePath(config: FullConfig, file: string): string { - return path.relative(config.rootDir, file) || path.basename(file); +export function relativeFilePath(screen: Screen, config: FullConfig, file: string): string { + if (screen.resolveFiles === 'cwd') + return path.relative(process.cwd(), file); + return path.relative(config.rootDir, file); } -function relativeTestPath(config: FullConfig, test: TestCase): string { - return relativeFilePath(config, test.location.file); +function relativeTestPath(screen: Screen, config: FullConfig, test: TestCase): string { + return relativeFilePath(screen, config, test.location.file); } export function stepSuffix(step: TestStep | undefined) { @@ -374,22 +457,22 @@ export function stepSuffix(step: TestStep | undefined) { return stepTitles.map(t => t.split('\n')[0]).map(t => ' › ' + t).join(''); } -export function formatTestTitle(config: FullConfig, test: TestCase, step?: TestStep, omitLocation: boolean = false): string { +function formatTestTitle(screen: Screen, config: FullConfig, test: TestCase, step?: TestStep, omitLocation: boolean = false): string { // root, project, file, ...describes, test const [, projectName, , ...titles] = test.titlePath(); let location; if (omitLocation) - location = `${relativeTestPath(config, test)}`; + location = `${relativeTestPath(screen, config, test)}`; else - location = `${relativeTestPath(config, test)}:${test.location.line}:${test.location.column}`; + location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`; const projectTitle = projectName ? `[${projectName}] › ` : ''; const testTitle = `${projectTitle}${location} › ${titles.join(' › ')}`; const extraTags = test.tags.filter(t => !testTitle.includes(t)); return `${testTitle}${stepSuffix(step)}${extraTags.length ? ' ' + extraTags.join(' ') : ''}`; } -export function formatTestHeader(config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string { - const title = formatTestTitle(config, test); +function formatTestHeader(screen: Screen, config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string { + const title = formatTestTitle(screen, config, test); const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`; let fullHeader = header; @@ -412,10 +495,10 @@ export function formatTestHeader(config: FullConfig, test: TestCase, options: { } fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : ''); } - return separator(fullHeader); + return separator(screen, fullHeader); } -export function formatError(error: TestError, highlightCode: boolean): ErrorDetails { +export function formatError(screen: Screen, error: TestError): ErrorDetails { const message = error.message || error.value || ''; const stack = error.stack; if (!stack && !error.location) @@ -430,21 +513,21 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta if (error.snippet) { let snippet = error.snippet; - if (!highlightCode) + if (!screen.colors.enabled) snippet = stripAnsiEscapes(snippet); tokens.push(''); tokens.push(snippet); } if (parsedStack && parsedStack.stackLines.length) - tokens.push(colors.dim(parsedStack.stackLines.join('\n'))); + tokens.push(screen.colors.dim(parsedStack.stackLines.join('\n'))); let location = error.location; if (parsedStack && !location) location = parsedStack.location; if (error.cause) - tokens.push(colors.dim('[cause]: ') + formatError(error.cause, highlightCode).message); + tokens.push(screen.colors.dim('[cause]: ') + formatError(screen, error.cause).message); return { location, @@ -452,11 +535,11 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta }; } -export function separator(text: string = ''): string { +export function separator(screen: Screen, text: string = ''): string { if (text) text += ' '; - const columns = Math.min(100, ttyWidth || 100); - return text + colors.dim('─'.repeat(Math.max(0, columns - text.length))); + const columns = Math.min(100, screen.ttyWidth || 100); + return text + screen.colors.dim('─'.repeat(Math.max(0, columns - text.length))); } function indent(lines: string, tab: string) { diff --git a/packages/playwright/src/reporters/dot.ts b/packages/playwright/src/reporters/dot.ts index 169af3b1e7..7f635f8214 100644 --- a/packages/playwright/src/reporters/dot.ts +++ b/packages/playwright/src/reporters/dot.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { colors, BaseReporter, formatError } from './base'; +import { TerminalReporter } from './base'; import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../types/testReporter'; -class DotReporter extends BaseReporter { +class DotReporter extends TerminalReporter { private _counter = 0; override onBegin(suite: Suite) { @@ -45,23 +45,23 @@ class DotReporter extends BaseReporter { } ++this._counter; if (result.status === 'skipped') { - process.stdout.write(colors.yellow('°')); + process.stdout.write(this.screen.colors.yellow('°')); return; } if (this.willRetry(test)) { - process.stdout.write(colors.gray('×')); + process.stdout.write(this.screen.colors.gray('×')); return; } switch (test.outcome()) { - case 'expected': process.stdout.write(colors.green('·')); break; - case 'unexpected': process.stdout.write(colors.red(result.status === 'timedOut' ? 'T' : 'F')); break; - case 'flaky': process.stdout.write(colors.yellow('±')); break; + case 'expected': process.stdout.write(this.screen.colors.green('·')); break; + case 'unexpected': process.stdout.write(this.screen.colors.red(result.status === 'timedOut' ? 'T' : 'F')); break; + case 'flaky': process.stdout.write(this.screen.colors.yellow('±')); break; } } override onError(error: TestError): void { super.onError(error); - console.log('\n' + formatError(error, colors.enabled).message); + console.log('\n' + this.formatError(error).message); this._counter = 0; } diff --git a/packages/playwright/src/reporters/github.ts b/packages/playwright/src/reporters/github.ts index c178cce64d..e7ec7198fb 100644 --- a/packages/playwright/src/reporters/github.ts +++ b/packages/playwright/src/reporters/github.ts @@ -16,7 +16,7 @@ import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; import path from 'path'; -import { BaseReporter, colors, formatError, formatResultFailure, formatRetry, formatTestHeader, formatTestTitle, stripAnsiEscapes } from './base'; +import { TerminalReporter, formatResultFailure, formatRetry, noColors, stripAnsiEscapes } from './base'; import type { TestCase, FullResult, TestError } from '../../types/testReporter'; type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error'; @@ -56,9 +56,14 @@ class GitHubLogger { } } -export class GitHubReporter extends BaseReporter { +export class GitHubReporter extends TerminalReporter { githubLogger = new GitHubLogger(); + constructor(options: { omitFailures?: boolean } = {}) { + super(options); + this.screen = { ...this.screen, colors: noColors }; + } + printsToStdio() { return false; } @@ -69,7 +74,7 @@ export class GitHubReporter extends BaseReporter { } override onError(error: TestError) { - const errorMessage = formatError(error, false).message; + const errorMessage = this.formatError(error).message; this.githubLogger.error(errorMessage); } @@ -100,10 +105,10 @@ export class GitHubReporter extends BaseReporter { private _printFailureAnnotations(failures: TestCase[]) { failures.forEach((test, index) => { - const title = formatTestTitle(this.config, test); - const header = formatTestHeader(this.config, test, { indent: ' ', index: index + 1, mode: 'error' }); + const title = this.formatTestTitle(test); + const header = this.formatTestHeader(test, { indent: ' ', index: index + 1, mode: 'error' }); for (const result of test.results) { - const errors = formatResultFailure(test, result, ' ', colors.enabled); + const errors = formatResultFailure(this.screen, test, result, ' '); for (const error of errors) { const options: GitHubLogOptions = { file: workspaceRelativePath(error.location?.file || test.location.file), @@ -113,7 +118,7 @@ export class GitHubReporter extends BaseReporter { options.line = error.location.line; options.col = error.location.column; } - const message = [header, ...formatRetry(result), error.message].join('\n'); + const message = [header, ...formatRetry(this.screen, result), error.message].join('\n'); this.githubLogger.error(message, options); } } diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 62158eef6d..e14be98f63 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { open } from 'playwright-core/lib/utilsBundle'; +import { colors, open } from 'playwright-core/lib/utilsBundle'; import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import fs from 'fs'; import path from 'path'; @@ -23,7 +23,7 @@ import { Transform } from 'stream'; import { codeFrameColumns } from '../transform/babelBundle'; import type * as api from '../../types/testReporter'; import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils'; -import { colors, formatError, formatResultFailure, stripAnsiEscapes } from './base'; +import { formatError, formatResultFailure, internalScreen, stripAnsiEscapes } from './base'; import { resolveReporterOutputPath } from '../util'; import type { Metadata } from '../../types/test'; import type { ZipFile } from 'playwright-core/lib/zipBundle'; @@ -297,7 +297,7 @@ class HtmlBuilder { files: [...data.values()].map(e => e.testFileSummary), projectNames: projectSuites.map(r => r.project()!.name), stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) }, - errors: topLevelErrors.map(error => formatError(error, true).message), + errors: topLevelErrors.map(error => formatError(internalScreen, error).message), }; htmlReport.files.sort((f1, f2) => { const w1 = f1.stats.unexpected * 1000 + f1.stats.flaky; @@ -506,7 +506,7 @@ class HtmlBuilder { startTime: result.startTime.toISOString(), retry: result.retry, steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)), - errors: formatResultFailure(test, result, '', true).map(error => error.message), + errors: formatResultFailure(internalScreen, test, result, '').map(error => error.message), status: result.status, attachments: this._serializeAttachments([ ...result.attachments, diff --git a/packages/playwright/src/reporters/internalReporter.ts b/packages/playwright/src/reporters/internalReporter.ts index 3526f89e0d..da5e667ffd 100644 --- a/packages/playwright/src/reporters/internalReporter.ts +++ b/packages/playwright/src/reporters/internalReporter.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import { codeFrameColumns } from '../transform/babelBundle'; import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter'; import { Suite } from '../common/test'; -import { colors, prepareErrorStack, relativeFilePath } from './base'; +import { internalScreen, prepareErrorStack, relativeFilePath } from './base'; import type { ReporterV2 } from './reporterV2'; import { monotonicTime } from 'playwright-core/lib/utils'; import { Multiplexer } from './multiplexer'; @@ -125,7 +125,7 @@ function addLocationAndSnippetToError(config: FullConfig, error: TestError, file const codeFrame = codeFrameColumns(source, { start: location }, { highlightCode: true }); // Convert /var/folders to /private/var/folders on Mac. if (!file || fs.realpathSync(file) !== location.file) { - tokens.push(colors.gray(` at `) + `${relativeFilePath(config, location.file)}:${location.line}`); + tokens.push(internalScreen.colors.gray(` at `) + `${relativeFilePath(internalScreen, config, location.file)}:${location.line}`); tokens.push(''); } tokens.push(codeFrame); diff --git a/packages/playwright/src/reporters/json.ts b/packages/playwright/src/reporters/json.ts index e2b7ddf872..3c827aea78 100644 --- a/packages/playwright/src/reporters/json.ts +++ b/packages/playwright/src/reporters/json.ts @@ -17,7 +17,7 @@ import fs from 'fs'; import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter'; -import { formatError, prepareErrorStack, resolveOutputFile } from './base'; +import { formatError, nonTerminalScreen, prepareErrorStack, resolveOutputFile } from './base'; import { MultiMap, toPosixPath } from 'playwright-core/lib/utils'; import { getProjectId } from '../common/config'; import type { ReporterV2 } from './reporterV2'; @@ -222,7 +222,7 @@ class JSONReporter implements ReporterV2 { } private _serializeError(error: TestError): JSONReportError { - return formatError(error, true); + return formatError(nonTerminalScreen, error); } private _serializeTestStep(step: TestStep): JSONReportTestStep { diff --git a/packages/playwright/src/reporters/junit.ts b/packages/playwright/src/reporters/junit.ts index f193f4f79d..9140fb19d8 100644 --- a/packages/playwright/src/reporters/junit.ts +++ b/packages/playwright/src/reporters/junit.ts @@ -17,7 +17,7 @@ import fs from 'fs'; import path from 'path'; import type { FullConfig, FullResult, Suite, TestCase } from '../../types/testReporter'; -import { formatFailure, resolveOutputFile, stripAnsiEscapes } from './base'; +import { formatFailure, nonTerminalScreen, resolveOutputFile, stripAnsiEscapes } from './base'; import { getAsBooleanFromENV } from 'playwright-core/lib/utils'; import type { ReporterV2 } from './reporterV2'; @@ -188,7 +188,7 @@ class JUnitReporter implements ReporterV2 { message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`, type: 'FAILURE', }, - text: stripAnsiEscapes(formatFailure(this.config, test)) + text: stripAnsiEscapes(formatFailure(nonTerminalScreen, this.config, test)) }); } diff --git a/packages/playwright/src/reporters/line.ts b/packages/playwright/src/reporters/line.ts index de5fc61703..96e393c1cf 100644 --- a/packages/playwright/src/reporters/line.ts +++ b/packages/playwright/src/reporters/line.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { colors, BaseReporter, formatError, formatFailure, formatTestTitle } from './base'; +import { TerminalReporter } from './base'; import type { TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter'; -class LineReporter extends BaseReporter { +class LineReporter extends TerminalReporter { private _current = 0; private _failures = 0; private _lastTest: TestCase | undefined; @@ -50,7 +50,7 @@ class LineReporter extends BaseReporter { stream.write(`\u001B[1A\u001B[2K`); if (test && this._lastTest !== test) { // Write new header for the output. - const title = colors.dim(formatTestTitle(this.config, test)); + const title = this.screen.colors.dim(this.formatTestTitle(test)); stream.write(this.fitToScreen(title) + `\n`); this._lastTest = test; } @@ -82,7 +82,7 @@ class LineReporter extends BaseReporter { if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) { if (!process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write(`\u001B[1A\u001B[2K`); - console.log(formatFailure(this.config, test, ++this._failures)); + console.log(this.formatFailure(test, ++this._failures)); console.log(); } } @@ -90,8 +90,8 @@ class LineReporter extends BaseReporter { private _updateLine(test: TestCase, result: TestResult, step?: TestStep) { const retriesPrefix = this.totalTestCount < this._current ? ` (retries)` : ``; const prefix = `[${this._current}/${this.totalTestCount}]${retriesPrefix} `; - const currentRetrySuffix = result.retry ? colors.yellow(` (retry #${result.retry})`) : ''; - const title = formatTestTitle(this.config, test, step) + currentRetrySuffix; + const currentRetrySuffix = result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : ''; + const title = this.formatTestTitle(test, step) + currentRetrySuffix; if (process.env.PW_TEST_DEBUG_REPORTERS) process.stdout.write(`${prefix + title}\n`); else @@ -101,7 +101,7 @@ class LineReporter extends BaseReporter { override onError(error: TestError): void { super.onError(error); - const message = formatError(error, colors.enabled).message + '\n'; + const message = this.formatError(error).message + '\n'; if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) process.stdout.write(`\u001B[1A\u001B[2K`); process.stdout.write(message); diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index 8704bfe104..4f885946e5 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -15,7 +15,7 @@ */ import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; -import { colors, BaseReporter, formatError, formatTestTitle, isTTY, stepSuffix, stripAnsiEscapes, ttyWidth } from './base'; +import { TerminalReporter, stepSuffix, stripAnsiEscapes } from './base'; import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; import { getAsBooleanFromENV } from 'playwright-core/lib/utils'; @@ -24,7 +24,7 @@ const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && proces const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'ok' : '✓'; const NEGATIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'x' : '✘'; -class ListReporter extends BaseReporter { +class ListReporter extends TerminalReporter { private _lastRow = 0; private _lastColumn = 0; private _testRows = new Map(); @@ -52,12 +52,12 @@ class ListReporter extends BaseReporter { const index = String(this._resultIndex.size + 1); this._resultIndex.set(result, index); - if (!isTTY) + if (!this.screen.isTTY) return; this._maybeWriteNewLine(); this._testRows.set(test, this._lastRow); const prefix = this._testPrefix(index, ''); - const line = colors.dim(formatTestTitle(this.config, test)) + this._retrySuffix(result); + const line = this.screen.colors.dim(this.formatTestTitle(test)) + this._retrySuffix(result); this._appendLine(line, prefix); } @@ -87,17 +87,17 @@ class ListReporter extends BaseReporter { return; const testIndex = this._resultIndex.get(result) || ''; - if (!isTTY) + if (!this.screen.isTTY) return; if (this._printSteps) { this._maybeWriteNewLine(); this._stepRows.set(step, this._lastRow); const prefix = this._testPrefix(this.getStepIndex(testIndex, result, step), ''); - const line = test.title + colors.dim(stepSuffix(step)); + const line = test.title + this.screen.colors.dim(stepSuffix(step)); this._appendLine(line, prefix); } else { - this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); + this._updateLine(this._testRows.get(test)!, this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); } } @@ -107,20 +107,20 @@ class ListReporter extends BaseReporter { const testIndex = this._resultIndex.get(result) || ''; if (!this._printSteps) { - if (isTTY) - this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); + if (this.screen.isTTY) + this._updateLine(this._testRows.get(test)!, this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); return; } const index = this.getStepIndex(testIndex, result, step); - const title = isTTY ? test.title + colors.dim(stepSuffix(step)) : formatTestTitle(this.config, test, step); + const title = this.screen.isTTY ? test.title + this.screen.colors.dim(stepSuffix(step)) : this.formatTestTitle(test, step); const prefix = this._testPrefix(index, ''); let text = ''; if (step.error) - text = colors.red(title); + text = this.screen.colors.red(title); else text = title; - text += colors.dim(` (${milliseconds(step.duration)})`); + text += this.screen.colors.dim(` (${milliseconds(step.duration)})`); this._updateOrAppendLine(this._stepRows.get(step)!, text, prefix); } @@ -134,7 +134,7 @@ class ListReporter extends BaseReporter { private _updateLineCountAndNewLineFlagForOutput(text: string) { this._needNewLine = text[text.length - 1] !== '\n'; - if (!ttyWidth) + if (!this.screen.ttyWidth) return; for (const ch of text) { if (ch === '\n') { @@ -143,7 +143,7 @@ class ListReporter extends BaseReporter { continue; } ++this._lastColumn; - if (this._lastColumn > ttyWidth) { + if (this._lastColumn > this.screen.ttyWidth) { this._lastColumn = 0; ++this._lastRow; } @@ -161,7 +161,7 @@ class ListReporter extends BaseReporter { override onTestEnd(test: TestCase, result: TestResult) { super.onTestEnd(test, result); - const title = formatTestTitle(this.config, test); + const title = this.formatTestTitle(test); let prefix = ''; let text = ''; @@ -174,26 +174,26 @@ class ListReporter extends BaseReporter { } if (result.status === 'skipped') { - prefix = this._testPrefix(index, colors.green('-')); + prefix = this._testPrefix(index, this.screen.colors.green('-')); // Do not show duration for skipped. - text = colors.cyan(title) + this._retrySuffix(result); + text = this.screen.colors.cyan(title) + this._retrySuffix(result); } else { const statusMark = result.status === 'passed' ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK; if (result.status === test.expectedStatus) { - prefix = this._testPrefix(index, colors.green(statusMark)); + prefix = this._testPrefix(index, this.screen.colors.green(statusMark)); text = title; } else { - prefix = this._testPrefix(index, colors.red(statusMark)); - text = colors.red(title); + prefix = this._testPrefix(index, this.screen.colors.red(statusMark)); + text = this.screen.colors.red(title); } - text += this._retrySuffix(result) + colors.dim(` (${milliseconds(result.duration)})`); + text += this._retrySuffix(result) + this.screen.colors.dim(` (${milliseconds(result.duration)})`); } this._updateOrAppendLine(this._testRows.get(test)!, text, prefix); } private _updateOrAppendLine(row: number, text: string, prefix: string) { - if (isTTY) { + if (this.screen.isTTY) { this._updateLine(row, text, prefix); } else { this._maybeWriteNewLine(); @@ -234,17 +234,17 @@ class ListReporter extends BaseReporter { private _testPrefix(index: string, statusMark: string) { const statusMarkLength = stripAnsiEscapes(statusMark).length; - return ' ' + statusMark + ' '.repeat(3 - statusMarkLength) + colors.dim(index + ' '); + return ' ' + statusMark + ' '.repeat(3 - statusMarkLength) + this.screen.colors.dim(index + ' '); } private _retrySuffix(result: TestResult) { - return (result.retry ? colors.yellow(` (retry #${result.retry})`) : ''); + return (result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : ''); } override onError(error: TestError): void { super.onError(error); this._maybeWriteNewLine(); - const message = formatError(error, colors.enabled).message + '\n'; + const message = this.formatError(error).message + '\n'; this._updateLineCountAndNewLineFlagForOutput(message); process.stdout.write(message); } diff --git a/packages/playwright/src/reporters/markdown.ts b/packages/playwright/src/reporters/markdown.ts index fe8c83cdd4..2b6bcbf063 100644 --- a/packages/playwright/src/reporters/markdown.ts +++ b/packages/playwright/src/reporters/markdown.ts @@ -18,15 +18,14 @@ import fs from 'fs'; import path from 'path'; import type { FullResult, TestCase } from '../../types/testReporter'; import { resolveReporterOutputPath } from '../util'; -import { BaseReporter, formatTestTitle } from './base'; +import { TerminalReporter } from './base'; type MarkdownReporterOptions = { configDir: string, outputFile?: string; }; - -class MarkdownReporter extends BaseReporter { +class MarkdownReporter extends TerminalReporter { private _options: MarkdownReporterOptions; constructor(options: MarkdownReporterOptions) { @@ -75,7 +74,7 @@ class MarkdownReporter extends BaseReporter { private _printTestList(prefix: string, tests: TestCase[], lines: string[], suffix?: string) { for (const test of tests) - lines.push(`${prefix} ${formatTestTitle(this.config, test)}${suffix || ''}`); + lines.push(`${prefix} ${this.formatTestTitle(test)}${suffix || ''}`); lines.push(``); } } diff --git a/packages/playwright/src/runner/reporters.ts b/packages/playwright/src/runner/reporters.ts index 2bab152f08..b25cb84154 100644 --- a/packages/playwright/src/runner/reporters.ts +++ b/packages/playwright/src/runner/reporters.ts @@ -16,7 +16,8 @@ import path from 'path'; import type { FullConfig, TestError } from '../../types/testReporter'; -import { colors, formatError } from '../reporters/base'; +import { formatError, terminalScreen } from '../reporters/base'; +import type { Screen } from '../reporters/base'; import DotReporter from '../reporters/dot'; import EmptyReporter from '../reporters/empty'; import GitHubReporter from '../reporters/github'; @@ -88,14 +89,14 @@ interface ErrorCollectingReporter extends ReporterV2 { errors(): TestError[]; } -export function createErrorCollectingReporter(writeToConsole?: boolean): ErrorCollectingReporter { +export function createErrorCollectingReporter(screen: Screen, writeToConsole?: boolean): ErrorCollectingReporter { const errors: TestError[] = []; return { version: () => 'v2', onError(error: TestError) { errors.push(error); if (writeToConsole) - process.stdout.write(formatError(error, colors.enabled).message + '\n'); + process.stdout.write(formatError(screen, error).message + '\n'); }, errors: () => errors, }; @@ -160,6 +161,6 @@ class ListModeReporter implements ReporterV2 { onError(error: TestError) { // eslint-disable-next-line no-console - console.error('\n' + formatError(error, false).message); + console.error('\n' + formatError(terminalScreen, error).message); } } diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index 5a015ec755..a1e73657c9 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -24,6 +24,7 @@ import type { FullConfigInternal } from '../common/config'; import { affectedTestFiles } from '../transform/compilationCache'; import { InternalReporter } from '../reporters/internalReporter'; import { LastRunReporter } from './lastRun'; +import { terminalScreen } from '../reporters/base'; type ProjectConfigWithFiles = { name: string; @@ -98,7 +99,7 @@ export class Runner { } async findRelatedTestFiles(files: string[]): Promise { - const errorReporter = createErrorCollectingReporter(); + const errorReporter = createErrorCollectingReporter(terminalScreen); const reporter = new InternalReporter([errorReporter]); const status = await runTasks(new TestRun(this._config, reporter), [ ...createPluginSetupTasks(this._config), @@ -110,7 +111,7 @@ export class Runner { } async runDevServer() { - const reporter = new InternalReporter([createErrorCollectingReporter(true)]); + const reporter = new InternalReporter([createErrorCollectingReporter(terminalScreen, true)]); const status = await runTasks(new TestRun(this._config, reporter), [ ...createPluginSetupTasks(this._config), createLoadTask('in-process', { failOnLoadErrors: true, filterOnly: false }), @@ -121,7 +122,7 @@ export class Runner { } async clearCache() { - const reporter = new InternalReporter([createErrorCollectingReporter(true)]); + const reporter = new InternalReporter([createErrorCollectingReporter(terminalScreen, true)]); const status = await runTasks(new TestRun(this._config, reporter), [ ...createPluginSetupTasks(this._config), createClearCacheTask(this._config), diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 08fa4b9353..e3a61329e2 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -38,6 +38,7 @@ import { serializeError } from '../util'; import { baseFullConfig } from '../isomorphic/teleReceiver'; import { InternalReporter } from '../reporters/internalReporter'; import type { ReporterV2 } from '../reporters/reporterV2'; +import { internalScreen } from '../reporters/base'; const originalStdoutWrite = process.stdout.write; const originalStderrWrite = process.stderr.write; @@ -359,7 +360,7 @@ export class TestServerDispatcher implements TestServerInterface { } async findRelatedTestFiles(params: Parameters[0]): ReturnType { - const errorReporter = createErrorCollectingReporter(); + const errorReporter = createErrorCollectingReporter(internalScreen); const reporter = new InternalReporter([errorReporter]); const config = await this._loadConfigOrReportError(reporter); if (!config) diff --git a/packages/playwright/src/runner/watchMode.ts b/packages/playwright/src/runner/watchMode.ts index 310cdeb546..5c8ddf34a0 100644 --- a/packages/playwright/src/runner/watchMode.ts +++ b/packages/playwright/src/runner/watchMode.ts @@ -21,7 +21,7 @@ import type { ConfigLocation } from '../common/config'; import type { FullResult } from '../../types/testReporter'; import { colors } from 'playwright-core/lib/utilsBundle'; import { enquirer } from '../utilsBundle'; -import { separator } from '../reporters/base'; +import { separator, terminalScreen } from '../reporters/base'; import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer'; import { TestServerDispatcher } from './testServer'; import { EventEmitter } from 'stream'; @@ -332,7 +332,7 @@ function readCommand() { return 'exit'; if (name === 'h') { - process.stdout.write(`${separator()} + process.stdout.write(`${separator(terminalScreen)} Run tests ${colors.bold('enter')} ${colors.dim('run tests')} ${colors.bold('f')} ${colors.dim('run failed tests')} @@ -380,7 +380,7 @@ function printConfiguration(options: WatchModeOptions, title?: string) { tokens.push(colors.dim(`(${title})`)); tokens.push(colors.dim(`#${seq++}`)); const lines: string[] = []; - const sep = separator(); + const sep = separator(terminalScreen); lines.push('\x1Bc' + sep); lines.push(`${tokens.join(' ')}`); lines.push(`${colors.dim('Show & reuse browser:')} ${colors.bold(showBrowserServer ? 'on' : 'off')}`); @@ -388,7 +388,7 @@ function printConfiguration(options: WatchModeOptions, title?: string) { } function printBufferPrompt(dirtyTestFiles: Set, rootDir: string) { - const sep = separator(); + const sep = separator(terminalScreen); process.stdout.write('\x1Bc'); process.stdout.write(`${sep}\n`); @@ -404,7 +404,7 @@ function printBufferPrompt(dirtyTestFiles: Set, rootDir: string) { } function printPrompt() { - const sep = separator(); + const sep = separator(terminalScreen); process.stdout.write(` ${sep} ${colors.dim('Waiting for file changes. Press')} ${colors.bold('enter')} ${colors.dim('to run tests')}, ${colors.bold('q')} ${colors.dim('to quit or')} ${colors.bold('h')} ${colors.dim('for more options.')} diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 5ddb271b70..e16fbcc460 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -882,7 +882,10 @@ test('multiple output reports based on config', async ({ runInlineTest, mergeRep const reportFiles = await fs.promises.readdir(reportDir); reportFiles.sort(); expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip']); - const { exitCode, output } = await mergeReports(reportDir, undefined, { additionalArgs: ['--config', test.info().outputPath('merged/playwright.config.ts')] }); + const { exitCode, output } = await mergeReports(reportDir, undefined, { + cwd: test.info().outputPath('merged'), + additionalArgs: ['--config', 'playwright.config.ts'], + }); expect(exitCode).toBe(0); // Check that line reporter was called. From a9e6b5110885f85d34bb4af041a9f23a8d428369 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 6 Jan 2025 16:37:11 -0800 Subject: [PATCH 45/83] chore(bidi): implement query selector all ($$) method (#34226) --- .../src/server/bidi/bidiExecutionContext.ts | 20 ++++++++++++- .../bidi/expectations/bidi-chromium-page.txt | 20 ++++++------- .../bidi-firefox-nightly-page.txt | 30 +++++++++---------- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts index f53b160ccf..de1b50bd1a 100644 --- a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -103,7 +103,25 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate { } async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise> { - throw new Error('Method not implemented.'); + const handle = this.createHandle(context, { objectId }); + try { + const names = await handle.evaluate(object => { + const names = []; + const descriptors = Object.getOwnPropertyDescriptors(object); + for (const name in descriptors) { + if (descriptors[name]?.enumerable) + names.push(name); + } + return names; + }); + const values = await Promise.all(names.map(name => handle.evaluateHandle((object, name) => object[name], name))); + const map = new Map(); + for (let i = 0; i < names.length; i++) + map.set(names[i], values[i]); + return map; + } finally { + handle.dispose(); + } } createHandle(context: js.ExecutionContext, jsRemoteObject: js.RemoteObject): js.JSHandle { diff --git a/tests/bidi/expectations/bidi-chromium-page.txt b/tests/bidi/expectations/bidi-chromium-page.txt index da2bee1fef..0664b52591 100644 --- a/tests/bidi/expectations/bidi-chromium-page.txt +++ b/tests/bidi/expectations/bidi-chromium-page.txt @@ -64,12 +64,12 @@ page/elementhandle-press.spec.ts › should reset selection when not focused [pa page/elementhandle-press.spec.ts › should work [pass] page/elementhandle-press.spec.ts › should work with number input [pass] page/elementhandle-query-selector.spec.ts › should query existing element [pass] -page/elementhandle-query-selector.spec.ts › should query existing elements [fail] -page/elementhandle-query-selector.spec.ts › should return empty array for non-existing elements [fail] +page/elementhandle-query-selector.spec.ts › should query existing elements [pass] +page/elementhandle-query-selector.spec.ts › should return empty array for non-existing elements [pass] page/elementhandle-query-selector.spec.ts › should return null for non-existing element [pass] page/elementhandle-query-selector.spec.ts › should work for adopted elements [fail] -page/elementhandle-query-selector.spec.ts › xpath should query existing element [fail] -page/elementhandle-query-selector.spec.ts › xpath should return null for non-existing element [fail] +page/elementhandle-query-selector.spec.ts › xpath should query existing element [pass] +page/elementhandle-query-selector.spec.ts › xpath should return null for non-existing element [pass] page/elementhandle-screenshot.spec.ts › element screenshot › path option should create subdirectories [fail] page/elementhandle-screenshot.spec.ts › element screenshot › should capture full element when larger than viewport [fail] page/elementhandle-screenshot.spec.ts › element screenshot › should capture full element when larger than viewport in parallel [fail] @@ -1690,7 +1690,7 @@ page/page-wait-for-url.spec.ts › should work with commit and about:blank [time page/page-wait-for-url.spec.ts › should work with history.pushState() [pass] page/page-wait-for-url.spec.ts › should work with history.replaceState() [pass] page/page-wait-for-url.spec.ts › should work with url match for same document navigations [pass] -page/queryselector.spec.ts › $$ should work with bogus Array.from [fail] +page/queryselector.spec.ts › $$ should work with bogus Array.from [pass] page/queryselector.spec.ts › should auto-detect css selector [pass] page/queryselector.spec.ts › should auto-detect text selector [pass] page/queryselector.spec.ts › should auto-detect xpath selector [pass] @@ -1699,14 +1699,14 @@ page/queryselector.spec.ts › should auto-detect xpath selector with starting p page/queryselector.spec.ts › should query existing element with css selector @smoke [pass] page/queryselector.spec.ts › should query existing element with text selector [pass] page/queryselector.spec.ts › should query existing element with xpath selector [pass] -page/queryselector.spec.ts › should query existing elements [fail] -page/queryselector.spec.ts › should return empty array if nothing is found [fail] +page/queryselector.spec.ts › should query existing elements [pass] +page/queryselector.spec.ts › should return empty array if nothing is found [pass] page/queryselector.spec.ts › should return null for non-existing element [pass] page/queryselector.spec.ts › should support >> syntax [pass] page/queryselector.spec.ts › should throw for non-string selector [pass] -page/queryselector.spec.ts › xpath should query existing element [fail] -page/queryselector.spec.ts › xpath should return empty array for non-existing element [fail] -page/queryselector.spec.ts › xpath should return multiple elements [fail] +page/queryselector.spec.ts › xpath should query existing element [pass] +page/queryselector.spec.ts › xpath should return empty array for non-existing element [pass] +page/queryselector.spec.ts › xpath should return multiple elements [pass] page/retarget.spec.ts › check retargeting [pass] page/retarget.spec.ts › direct actions retargeting [pass] page/retarget.spec.ts › editable retargeting [pass] diff --git a/tests/bidi/expectations/bidi-firefox-nightly-page.txt b/tests/bidi/expectations/bidi-firefox-nightly-page.txt index f7e4292227..e9edb983e3 100644 --- a/tests/bidi/expectations/bidi-firefox-nightly-page.txt +++ b/tests/bidi/expectations/bidi-firefox-nightly-page.txt @@ -64,11 +64,11 @@ page/elementhandle-press.spec.ts › should reset selection when not focused [pa page/elementhandle-press.spec.ts › should work [pass] page/elementhandle-press.spec.ts › should work with number input [pass] page/elementhandle-query-selector.spec.ts › should query existing element [pass] -page/elementhandle-query-selector.spec.ts › should query existing elements [fail] -page/elementhandle-query-selector.spec.ts › should return empty array for non-existing elements [fail] +page/elementhandle-query-selector.spec.ts › should query existing elements [pass] +page/elementhandle-query-selector.spec.ts › should return empty array for non-existing elements [pass] page/elementhandle-query-selector.spec.ts › should return null for non-existing element [pass] -page/elementhandle-query-selector.spec.ts › should work for adopted elements [timeout] -page/elementhandle-query-selector.spec.ts › xpath should query existing element [fail] +page/elementhandle-query-selector.spec.ts › should work for adopted elements [pass] +page/elementhandle-query-selector.spec.ts › xpath should query existing element [pass] page/elementhandle-query-selector.spec.ts › xpath should return null for non-existing element [fail] page/elementhandle-screenshot.spec.ts › element screenshot › path option should create subdirectories [pass] page/elementhandle-screenshot.spec.ts › element screenshot › should capture full element when larger than viewport [fail] @@ -1702,23 +1702,23 @@ page/page-wait-for-url.spec.ts › should work with commit and about:blank [pass page/page-wait-for-url.spec.ts › should work with history.pushState() [fail] page/page-wait-for-url.spec.ts › should work with history.replaceState() [fail] page/page-wait-for-url.spec.ts › should work with url match for same document navigations [timeout] -page/queryselector.spec.ts › $$ should work with bogus Array.from [fail] -page/queryselector.spec.ts › should auto-detect css selector [fail] +page/queryselector.spec.ts › $$ should work with bogus Array.from [pass] +page/queryselector.spec.ts › should auto-detect css selector [pass] page/queryselector.spec.ts › should auto-detect text selector [pass] page/queryselector.spec.ts › should auto-detect xpath selector [pass] page/queryselector.spec.ts › should auto-detect xpath selector starting with .. [pass] page/queryselector.spec.ts › should auto-detect xpath selector with starting parenthesis [pass] -page/queryselector.spec.ts › should query existing element with css selector @smoke [fail] +page/queryselector.spec.ts › should query existing element with css selector @smoke [pass] page/queryselector.spec.ts › should query existing element with text selector [pass] page/queryselector.spec.ts › should query existing element with xpath selector [pass] -page/queryselector.spec.ts › should query existing elements [fail] -page/queryselector.spec.ts › should return empty array if nothing is found [fail] +page/queryselector.spec.ts › should query existing elements [pass] +page/queryselector.spec.ts › should return empty array if nothing is found [pass] page/queryselector.spec.ts › should return null for non-existing element [pass] page/queryselector.spec.ts › should support >> syntax [pass] page/queryselector.spec.ts › should throw for non-string selector [pass] -page/queryselector.spec.ts › xpath should query existing element [fail] -page/queryselector.spec.ts › xpath should return empty array for non-existing element [fail] -page/queryselector.spec.ts › xpath should return multiple elements [fail] +page/queryselector.spec.ts › xpath should query existing element [pass] +page/queryselector.spec.ts › xpath should return empty array for non-existing element [pass] +page/queryselector.spec.ts › xpath should return multiple elements [pass] page/retarget.spec.ts › check retargeting [fail] page/retarget.spec.ts › direct actions retargeting [pass] page/retarget.spec.ts › editable retargeting [pass] @@ -1769,13 +1769,13 @@ page/selectors-frame.spec.ts › click should survive iframe navigation [pass] page/selectors-frame.spec.ts › click should survive navigation [pass] page/selectors-frame.spec.ts › should capture after the enter-frame [pass] page/selectors-frame.spec.ts › should click in lazy iframe [pass] -page/selectors-frame.spec.ts › should fail if element removed while waiting on element handle [fail] +page/selectors-frame.spec.ts › should fail if element removed while waiting on element handle [pass] page/selectors-frame.spec.ts › should non work for non-frame [pass] page/selectors-frame.spec.ts › should not allow capturing before enter-frame [pass] page/selectors-frame.spec.ts › should not allow dangling enter-frame [pass] page/selectors-frame.spec.ts › should not allow leading enter-frame [pass] -page/selectors-frame.spec.ts › should work for $ and $$ [fail] -page/selectors-frame.spec.ts › should work for $ and $$ (handle) [fail] +page/selectors-frame.spec.ts › should work for $ and $$ [pass] +page/selectors-frame.spec.ts › should work for $ and $$ (handle) [pass] page/selectors-frame.spec.ts › should work for $$eval [pass] page/selectors-frame.spec.ts › should work for $$eval (handle) [pass] page/selectors-frame.spec.ts › should work for $eval [pass] From ec79f28ffebd8e8047bd186d03a151929966ba2f Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 6 Jan 2025 17:36:49 -0800 Subject: [PATCH 46/83] chore(bidi): keep custom expectations only for timing out tests (#34228) --- .../expectations/bidi-chromium-library.txt | 1682 +------------- .../bidi/expectations/bidi-chromium-page.txt | 1895 ---------------- .../bidi-firefox-nightly-library.txt | 1822 +--------------- .../bidi-firefox-nightly-page.txt | 1930 ----------------- 4 files changed, 2 insertions(+), 7327 deletions(-) diff --git a/tests/bidi/expectations/bidi-chromium-library.txt b/tests/bidi/expectations/bidi-chromium-library.txt index e49e4cedb2..aad83691f6 100644 --- a/tests/bidi/expectations/bidi-chromium-library.txt +++ b/tests/bidi/expectations/bidi-chromium-library.txt @@ -1,450 +1,19 @@ library/beforeunload.spec.ts › should access page after beforeunload [timeout] -library/beforeunload.spec.ts › should be able to navigate away from page with beforeunload [pass] -library/beforeunload.spec.ts › should close browser with beforeunload page [pass] -library/beforeunload.spec.ts › should close browsercontext with beforeunload page [pass] -library/beforeunload.spec.ts › should close page with beforeunload listener [pass] -library/beforeunload.spec.ts › should not stall on evaluate when dismissing beforeunload [pass] library/beforeunload.spec.ts › should run beforeunload if asked for @smoke [timeout] -library/browser.spec.ts › should create new page @smoke [pass] -library/browser.spec.ts › should dispatch page.on(close) upon browser.close and reject evaluate [fail] -library/browser.spec.ts › should return browserType [pass] -library/browser.spec.ts › should throw upon second create new page [pass] -library/browser.spec.ts › version should work [fail] -library/browsercontext-add-cookies.spec.ts › should add cookies with empty value [fail] -library/browsercontext-add-cookies.spec.ts › should allow unnamed cookies [fail] -library/browsercontext-add-cookies.spec.ts › should be able to set unsecure cookie for HTTP website [pass] -library/browsercontext-add-cookies.spec.ts › should default to setting secure cookie for HTTPS websites [pass] -library/browsercontext-add-cookies.spec.ts › should have |expires| set to |-1| for session cookies [pass] -library/browsercontext-add-cookies.spec.ts › should isolate cookies between launches [pass] -library/browsercontext-add-cookies.spec.ts › should isolate cookies in browser contexts [pass] -library/browsercontext-add-cookies.spec.ts › should isolate persistent cookies [pass] -library/browsercontext-add-cookies.spec.ts › should isolate send cookie header [pass] -library/browsercontext-add-cookies.spec.ts › should isolate session cookies [pass] -library/browsercontext-add-cookies.spec.ts › should not block third party SameSite=None cookies [fail] -library/browsercontext-add-cookies.spec.ts › should not set a cookie on a data URL page [pass] -library/browsercontext-add-cookies.spec.ts › should not set a cookie with blank page URL [pass] -library/browsercontext-add-cookies.spec.ts › should roundtrip cookie [fail] -library/browsercontext-add-cookies.spec.ts › should send cookie header [pass] -library/browsercontext-add-cookies.spec.ts › should set a cookie on a different domain [pass] -library/browsercontext-add-cookies.spec.ts › should set a cookie with a path [pass] -library/browsercontext-add-cookies.spec.ts › should set cookie with reasonable defaults [fail] -library/browsercontext-add-cookies.spec.ts › should set cookies for a frame [pass] -library/browsercontext-add-cookies.spec.ts › should set multiple cookies [pass] -library/browsercontext-add-cookies.spec.ts › should set secure cookies on secure WebSocket [fail] -library/browsercontext-add-cookies.spec.ts › should work @smoke [pass] -library/browsercontext-add-cookies.spec.ts › should work with expires=-1 [fail] -library/browsercontext-add-cookies.spec.ts › should(not) block third party cookies [pass] -library/browsercontext-add-init-script.spec.ts › should work with browser context scripts @smoke [pass] -library/browsercontext-add-init-script.spec.ts › should work with browser context scripts for already created pages [pass] -library/browsercontext-add-init-script.spec.ts › should work with browser context scripts with a path [pass] -library/browsercontext-add-init-script.spec.ts › should work without navigation in popup [fail] -library/browsercontext-add-init-script.spec.ts › should work without navigation, after all bindings [fail] -library/browsercontext-base-url.spec.ts › should be able to match a URL relative to its given URL with urlMatcher [fail] -library/browsercontext-base-url.spec.ts › should construct a new URL when a baseURL in browser.newContext is passed to page.goto @smoke [pass] -library/browsercontext-base-url.spec.ts › should construct a new URL when a baseURL in browser.newPage is passed to page.goto [pass] -library/browsercontext-base-url.spec.ts › should construct a new URL when a baseURL in browserType.launchPersistentContext is passed to page.goto [fail] -library/browsercontext-base-url.spec.ts › should construct the URLs correctly when a baseURL with a trailing slash in browser.newPage is passed to page.goto [pass] -library/browsercontext-base-url.spec.ts › should construct the URLs correctly when a baseURL without a trailing slash in browser.newPage is passed to page.goto [pass] -library/browsercontext-base-url.spec.ts › should not construct a new URL when valid URLs are passed [pass] -library/browsercontext-base-url.spec.ts › should not construct a new URL with baseURL when a glob was used [pass] -library/browsercontext-basic.spec.ts › close() should abort waitForEvent [pass] -library/browsercontext-basic.spec.ts › close() should be callable twice [pass] -library/browsercontext-basic.spec.ts › close() should work for empty context [pass] -library/browsercontext-basic.spec.ts › default user agent [pass] -library/browsercontext-basic.spec.ts › setContent should work after disabling javascript [pass] -library/browsercontext-basic.spec.ts › should be able to click across browser contexts [pass] -library/browsercontext-basic.spec.ts › should be able to navigate after disabling javascript [pass] -library/browsercontext-basic.spec.ts › should close all belonging pages once closing context [pass] -library/browsercontext-basic.spec.ts › should create new context @smoke [pass] -library/browsercontext-basic.spec.ts › should disable javascript [fail] -library/browsercontext-basic.spec.ts › should emulate media in cross-process iframe [fail] -library/browsercontext-basic.spec.ts › should emulate media in popup [fail] -library/browsercontext-basic.spec.ts › should emulate navigator.onLine [fail] -library/browsercontext-basic.spec.ts › should isolate localStorage and cookies @smoke [pass] -library/browsercontext-basic.spec.ts › should make a copy of default viewport [pass] -library/browsercontext-basic.spec.ts › should not allow deviceScaleFactor with null viewport [pass] -library/browsercontext-basic.spec.ts › should not allow isMobile with null viewport [pass] -library/browsercontext-basic.spec.ts › should not hang on promises after disabling javascript [pass] -library/browsercontext-basic.spec.ts › should not report frameless pages on error [pass] -library/browsercontext-basic.spec.ts › should pass self to close event [pass] -library/browsercontext-basic.spec.ts › should propagate default viewport to the page [pass] -library/browsercontext-basic.spec.ts › should respect deviceScaleFactor [pass] -library/browsercontext-basic.spec.ts › should return all of the pages [pass] -library/browsercontext-basic.spec.ts › should work with offline option [fail] -library/browsercontext-basic.spec.ts › window.open should use parent tab context [pass] -library/browsercontext-clearcookies.spec.ts › should clear cookies [pass] -library/browsercontext-clearcookies.spec.ts › should isolate cookies when clearing [pass] -library/browsercontext-clearcookies.spec.ts › should remove cookies by domain [fail] -library/browsercontext-clearcookies.spec.ts › should remove cookies by name [fail] -library/browsercontext-clearcookies.spec.ts › should remove cookies by name and domain [fail] -library/browsercontext-clearcookies.spec.ts › should remove cookies by name regex [fail] -library/browsercontext-clearcookies.spec.ts › should remove cookies by path [fail] -library/browsercontext-cookies.spec.ts › should add cookies with an expiration [pass] -library/browsercontext-cookies.spec.ts › should be able to send third party cookies via an iframe [fail] -library/browsercontext-cookies.spec.ts › should get a cookie @smoke [fail] -library/browsercontext-cookies.spec.ts › should get a non-session cookie [fail] -library/browsercontext-cookies.spec.ts › should get cookies from multiple urls [pass] -library/browsercontext-cookies.spec.ts › should get multiple cookies [fail] -library/browsercontext-cookies.spec.ts › should parse cookie with large Max-Age correctly [fail] -library/browsercontext-cookies.spec.ts › should properly report "Lax" sameSite cookie [pass] -library/browsercontext-cookies.spec.ts › should properly report "Strict" sameSite cookie [pass] -library/browsercontext-cookies.spec.ts › should properly report httpOnly cookie [pass] -library/browsercontext-cookies.spec.ts › should return cookies with empty value [pass] -library/browsercontext-cookies.spec.ts › should return no cookies in pristine browser context [pass] -library/browsercontext-cookies.spec.ts › should return secure cookies based on HTTP(S) protocol [pass] -library/browsercontext-cookies.spec.ts › should support requestStorageAccess [fail] -library/browsercontext-cookies.spec.ts › should work with subdomain cookie [pass] -library/browsercontext-credentials.spec.ts › should fail with correct credentials and mismatching hostname [fail] -library/browsercontext-credentials.spec.ts › should fail with correct credentials and mismatching port [fail] -library/browsercontext-credentials.spec.ts › should fail with correct credentials and mismatching scheme [fail] -library/browsercontext-credentials.spec.ts › should fail with wrong credentials [fail] -library/browsercontext-credentials.spec.ts › should fail without credentials [pass] -library/browsercontext-credentials.spec.ts › should return resource body [fail] -library/browsercontext-credentials.spec.ts › should work with correct credentials @smoke [fail] -library/browsercontext-credentials.spec.ts › should work with correct credentials and matching origin [fail] -library/browsercontext-credentials.spec.ts › should work with correct credentials and matching origin case insensitive [fail] -library/browsercontext-credentials.spec.ts › should work with setHTTPCredentials [fail] -library/browsercontext-csp.spec.ts › should bypass CSP header [fail] -library/browsercontext-csp.spec.ts › should bypass CSP in iframes as well [fail] -library/browsercontext-csp.spec.ts › should bypass CSP meta tag @smoke [fail] -library/browsercontext-csp.spec.ts › should bypass after cross-process navigation [fail] -library/browsercontext-device.spec.ts › device › should emulate viewport and screen size [fail] -library/browsercontext-device.spec.ts › device › should emulate viewport without screen size [fail] -library/browsercontext-device.spec.ts › device › should reset scroll top after a navigation [pass] -library/browsercontext-device.spec.ts › device › should scroll to a precise position with mobile scale [pass] -library/browsercontext-device.spec.ts › device › should scroll to click [pass] -library/browsercontext-device.spec.ts › device › should scroll twice when emulated [pass] -library/browsercontext-device.spec.ts › device › should support clicking [pass] -library/browsercontext-device.spec.ts › device › should work @smoke [fail] -library/browsercontext-dsf.spec.ts › should fetch hidpi assets [pass] -library/browsercontext-dsf.spec.ts › should fetch lodpi assets @smoke [pass] -library/browsercontext-events.spec.ts › console event should work @smoke [pass] library/browsercontext-events.spec.ts › console event should work in immediately closed popup [timeout] -library/browsercontext-events.spec.ts › console event should work in popup [pass] library/browsercontext-events.spec.ts › console event should work in popup 2 [timeout] -library/browsercontext-events.spec.ts › dialog event should work @smoke [pass] library/browsercontext-events.spec.ts › dialog event should work in immediately closed popup [timeout] library/browsercontext-events.spec.ts › dialog event should work in popup [timeout] -library/browsercontext-events.spec.ts › dialog event should work in popup 2 [pass] library/browsercontext-events.spec.ts › dialog event should work with inline script tag [timeout] library/browsercontext-events.spec.ts › weberror event should work [timeout] -library/browsercontext-expose-function.spec.ts › expose binding should work [fail] -library/browsercontext-expose-function.spec.ts › exposeBindingHandle should work [fail] -library/browsercontext-expose-function.spec.ts › should be callable from-inside addInitScript [fail] -library/browsercontext-expose-function.spec.ts › should throw for duplicate registrations [pass] -library/browsercontext-expose-function.spec.ts › should work [fail] -library/browsercontext-expose-function.spec.ts › should work with CSP [fail] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › br decompression › should not fail if response content-length header is missing (br) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › br decompression › should not fail with an empty response with content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › br decompression › should not fail with an empty response without content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › br decompression › should not fail with chunked responses (without Content-Length header) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › br decompression › should support decompression [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › deflate decompression › should not fail if response content-length header is missing (deflate) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › deflate decompression › should not fail with an empty response with content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › deflate decompression › should not fail with an empty response without content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › deflate decompression › should not fail with chunked responses (without Content-Length header) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › deflate decompression › should support decompression [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › gzip decompression › should not fail if response content-length header is missing (gzip) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › gzip decompression › should not fail with an empty response with content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › gzip decompression › should not fail with an empty response without content-length header (Z_BUF_ERROR) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › gzip decompression › should not fail with chunked responses (without Content-Length header) [pass] -library/browsercontext-fetch-algorithms.spec.ts › algorithms › gzip decompression › should support decompression [pass] -library/browsercontext-fetch-happy-eyeballs.spec.ts › get should work [pass] -library/browsercontext-fetch-happy-eyeballs.spec.ts › get should work on request fixture [pass] -library/browsercontext-fetch-happy-eyeballs.spec.ts › https post should work with ignoreHTTPSErrors option [pass] -library/browsercontext-fetch-happy-eyeballs.spec.ts › should work with ip6 and port as the host [pass] -library/browsercontext-fetch.spec.ts › context request should export same storage state as context [pass] -library/browsercontext-fetch.spec.ts › delete should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › delete should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › delete should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › delete should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › delete should support post data [pass] -library/browsercontext-fetch.spec.ts › deleteshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › fetch should not throw on long set-cookie value [pass] -library/browsercontext-fetch.spec.ts › fetch should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › fetch should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › fetch should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › fetch should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › fetch should work [pass] -library/browsercontext-fetch.spec.ts › fetchshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › get should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › get should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › get should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › get should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › get should support post data [pass] -library/browsercontext-fetch.spec.ts › get should work @smoke [pass] -library/browsercontext-fetch.spec.ts › getshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › head should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › head should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › head should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › head should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › head should support post data [pass] -library/browsercontext-fetch.spec.ts › headshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › patch should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › patch should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › patch should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › patch should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › patch should support post data [pass] -library/browsercontext-fetch.spec.ts › patchshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › post should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › post should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › post should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › post should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › post should support post data [pass] -library/browsercontext-fetch.spec.ts › postshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › put should support failOnStatusCode [pass] -library/browsercontext-fetch.spec.ts › put should support params passed as URLSearchParams [pass] -library/browsercontext-fetch.spec.ts › put should support params passed as object [pass] -library/browsercontext-fetch.spec.ts › put should support params passed as string [pass] -library/browsercontext-fetch.spec.ts › put should support post data [pass] -library/browsercontext-fetch.spec.ts › putshould support ignoreHTTPSErrors option [pass] -library/browsercontext-fetch.spec.ts › should abort requests when browser context closes [pass] -library/browsercontext-fetch.spec.ts › should accept bool and numeric params [pass] -library/browsercontext-fetch.spec.ts › should add cookies from Set-Cookie header [pass] -library/browsercontext-fetch.spec.ts › should add default headers [pass] -library/browsercontext-fetch.spec.ts › should add default headers to redirects [pass] -library/browsercontext-fetch.spec.ts › should add session cookies to request [pass] -library/browsercontext-fetch.spec.ts › should allow to override default headers [pass] -library/browsercontext-fetch.spec.ts › should dispose [pass] -library/browsercontext-fetch.spec.ts › should dispose when context closes [pass] -library/browsercontext-fetch.spec.ts › should encode to application/json by default [pass] -library/browsercontext-fetch.spec.ts › should follow redirects [pass] -library/browsercontext-fetch.spec.ts › should follow redirects correctly when Location header contains UTF-8 characters [pass] -library/browsercontext-fetch.spec.ts › should handle cookies on redirects [pass] -library/browsercontext-fetch.spec.ts › should inherit ignoreHTTPSErrors from context [pass] -library/browsercontext-fetch.spec.ts › should not add context cookie if cookie header passed as a parameter [pass] -library/browsercontext-fetch.spec.ts › should not hang on a brotli encoded Range request [pass] -library/browsercontext-fetch.spec.ts › should not lose body while handling Set-Cookie header [pass] -library/browsercontext-fetch.spec.ts › should not work after context dispose [pass] -library/browsercontext-fetch.spec.ts › should not work after dispose [pass] -library/browsercontext-fetch.spec.ts › should override request parameters [pass] -library/browsercontext-fetch.spec.ts › should preserve cookie order from Set-Cookie header [pass] -library/browsercontext-fetch.spec.ts › should propagate custom headers with redirects [pass] -library/browsercontext-fetch.spec.ts › should propagate extra http headers with redirects [fail] -library/browsercontext-fetch.spec.ts › should remove cookie with expires far in the past [pass] -library/browsercontext-fetch.spec.ts › should remove cookie with negative max-age [pass] -library/browsercontext-fetch.spec.ts › should resolve url relative to baseURL [pass] -library/browsercontext-fetch.spec.ts › should respect timeout after redirects [pass] -library/browsercontext-fetch.spec.ts › should retry on ECONNRESET [pass] -library/browsercontext-fetch.spec.ts › should return error with wrong credentials [pass] -library/browsercontext-fetch.spec.ts › should return raw headers [pass] -library/browsercontext-fetch.spec.ts › should send content-length [pass] -library/browsercontext-fetch.spec.ts › should send secure cookie over http for localhost [pass] -library/browsercontext-fetch.spec.ts › should serialize data to json regardless of content-type [pass] -library/browsercontext-fetch.spec.ts › should set domain=localhost cookie [pass] -library/browsercontext-fetch.spec.ts › should support HTTPCredentials.send for browser.newPage [fail] -library/browsercontext-fetch.spec.ts › should support HTTPCredentials.send for newContext [pass] -library/browsercontext-fetch.spec.ts › should support SameSite cookie attribute over https [pass] -library/browsercontext-fetch.spec.ts › should support a timeout of 0 [pass] -library/browsercontext-fetch.spec.ts › should support application/x-www-form-urlencoded [pass] -library/browsercontext-fetch.spec.ts › should support brotli compression [pass] -library/browsercontext-fetch.spec.ts › should support cookie with empty value [pass] -library/browsercontext-fetch.spec.ts › should support deflate compression [pass] -library/browsercontext-fetch.spec.ts › should support gzip compression [pass] -library/browsercontext-fetch.spec.ts › should support https [pass] -library/browsercontext-fetch.spec.ts › should support multipart/form-data [pass] -library/browsercontext-fetch.spec.ts › should support multipart/form-data and keep the order [pass] -library/browsercontext-fetch.spec.ts › should support multipart/form-data with ReadStream values [pass] -library/browsercontext-fetch.spec.ts › should support repeating names in multipart/form-data [pass] -library/browsercontext-fetch.spec.ts › should support set-cookie with SameSite and without Secure attribute over HTTP [fail] -library/browsercontext-fetch.spec.ts › should support timeout option [pass] -library/browsercontext-fetch.spec.ts › should throw informative error on corrupted brotli body [pass] -library/browsercontext-fetch.spec.ts › should throw informative error on corrupted deflate body [pass] -library/browsercontext-fetch.spec.ts › should throw informative error on corrupted gzip body [pass] -library/browsercontext-fetch.spec.ts › should throw nice error on unsupported data type [pass] -library/browsercontext-fetch.spec.ts › should throw on invalid header value [pass] -library/browsercontext-fetch.spec.ts › should throw on network error [pass] -library/browsercontext-fetch.spec.ts › should throw on network error after redirect [pass] -library/browsercontext-fetch.spec.ts › should throw on network error when sending body [pass] -library/browsercontext-fetch.spec.ts › should throw on network error when sending body after redirect [pass] -library/browsercontext-fetch.spec.ts › should throw on non-http(s) protocol [pass] -library/browsercontext-fetch.spec.ts › should update host header on redirect [pass] -library/browsercontext-fetch.spec.ts › should work with connectOverCDP [unknown] -library/browsercontext-fetch.spec.ts › should work with http credentials [pass] -library/browsercontext-fetch.spec.ts › should work with setHTTPCredentials [pass] -library/browsercontext-har.spec.ts › by default should abort requests not found in har [fail] -library/browsercontext-har.spec.ts › context.unrouteAll should stop context.routeFromHAR [fail] -library/browsercontext-har.spec.ts › fallback:continue should continue requests on bad har [fail] -library/browsercontext-har.spec.ts › fallback:continue should continue when not found in har [fail] -library/browsercontext-har.spec.ts › newPage should fulfill from har, matching the method and following redirects [fail] -library/browsercontext-har.spec.ts › page.unrouteAll should stop page.routeFromHAR [fail] -library/browsercontext-har.spec.ts › should apply overrides before routing from har [fail] -library/browsercontext-har.spec.ts › should change document URL after redirected navigation [fail] -library/browsercontext-har.spec.ts › should change document URL after redirected navigation on click [fail] -library/browsercontext-har.spec.ts › should context.routeFromHAR, matching the method and following redirects [fail] -library/browsercontext-har.spec.ts › should disambiguate by header [fail] -library/browsercontext-har.spec.ts › should fulfill from har with content in a file [fail] -library/browsercontext-har.spec.ts › should goBack to redirected navigation [fail] -library/browsercontext-har.spec.ts › should goForward to redirected navigation [fail] -library/browsercontext-har.spec.ts › should ignore aborted requests [fail] -library/browsercontext-har.spec.ts › should ignore boundary when matching multipart/form-data body [fail] -library/browsercontext-har.spec.ts › should only context.routeFromHAR requests matching url filter [fail] -library/browsercontext-har.spec.ts › should only handle requests matching url filter [fail] -library/browsercontext-har.spec.ts › should only page.routeFromHAR requests matching url filter [fail] -library/browsercontext-har.spec.ts › should page.routeFromHAR, matching the method and following redirects [fail] -library/browsercontext-har.spec.ts › should produce extracted zip [fail] -library/browsercontext-har.spec.ts › should record overridden requests to har [fail] -library/browsercontext-har.spec.ts › should reload redirected navigation [fail] -library/browsercontext-har.spec.ts › should round-trip extracted har.zip [fail] -library/browsercontext-har.spec.ts › should round-trip har with postData [fail] -library/browsercontext-har.spec.ts › should round-trip har.zip [fail] -library/browsercontext-har.spec.ts › should support regex filter [fail] -library/browsercontext-har.spec.ts › should update extracted har.zip for page [fail] -library/browsercontext-har.spec.ts › should update har.zip for context [fail] -library/browsercontext-har.spec.ts › should update har.zip for page [fail] -library/browsercontext-har.spec.ts › should update har.zip for page with different options [fail] -library/browsercontext-locale.spec.ts › should affect Intl.DateTimeFormat().resolvedOptions().locale [fail] -library/browsercontext-locale.spec.ts › should affect accept-language header @smoke [fail] -library/browsercontext-locale.spec.ts › should affect navigator.language [fail] -library/browsercontext-locale.spec.ts › should affect navigator.language in popups [fail] -library/browsercontext-locale.spec.ts › should be isolated between contexts [fail] -library/browsercontext-locale.spec.ts › should format date [fail] -library/browsercontext-locale.spec.ts › should format number [fail] -library/browsercontext-locale.spec.ts › should format number in popups [fail] library/browsercontext-locale.spec.ts › should format number in workers [timeout] -library/browsercontext-locale.spec.ts › should not change default locale in another context [fail] -library/browsercontext-locale.spec.ts › should work for multiple pages sharing same process [pass] -library/browsercontext-network-event.spec.ts › BrowserContext.Events.Request [pass] -library/browsercontext-network-event.spec.ts › BrowserContext.Events.RequestFailed [fail] -library/browsercontext-network-event.spec.ts › BrowserContext.Events.RequestFinished [pass] -library/browsercontext-network-event.spec.ts › BrowserContext.Events.Response [pass] -library/browsercontext-network-event.spec.ts › should fire events in proper order [pass] -library/browsercontext-network-event.spec.ts › should not fire events for favicon or favicon redirects [unknown] -library/browsercontext-page-event.spec.ts › should fire page lifecycle events [pass] -library/browsercontext-page-event.spec.ts › should have about:blank for empty url with domcontentloaded [fail] -library/browsercontext-page-event.spec.ts › should have about:blank url with domcontentloaded [fail] -library/browsercontext-page-event.spec.ts › should have an opener [pass] -library/browsercontext-page-event.spec.ts › should have url [pass] -library/browsercontext-page-event.spec.ts › should have url after domcontentloaded [pass] -library/browsercontext-page-event.spec.ts › should not crash while redirecting of original request was missed [pass] library/browsercontext-page-event.spec.ts › should not hang on ctrl-click during provisional load [timeout] -library/browsercontext-page-event.spec.ts › should report initialized pages [fail] -library/browsercontext-page-event.spec.ts › should report when a new page is created and closed [fail] -library/browsercontext-page-event.spec.ts › should work with Ctrl-clicking [pass] -library/browsercontext-page-event.spec.ts › should work with Shift-clicking [pass] -library/browsercontext-pages.spec.ts › frame.focus should work multiple times [pass] -library/browsercontext-pages.spec.ts › page.context should return the correct instance [pass] -library/browsercontext-pages.spec.ts › should click the button with deviceScaleFactor set [pass] -library/browsercontext-pages.spec.ts › should click the button with offset with page scale [pass] -library/browsercontext-pages.spec.ts › should click with disabled javascript [pass] -library/browsercontext-pages.spec.ts › should keep selection in multiple pages [pass] -library/browsercontext-pages.spec.ts › should not be visible in context.pages [pass] -library/browsercontext-pages.spec.ts › should not hang with touch-enabled viewports [pass] -library/browsercontext-pages.spec.ts › should not leak listeners during navigation of 20 pages [fail] -library/browsercontext-pages.spec.ts › should return bounding box with page scale [pass] -library/browsercontext-proxy.spec.ts › does launch without a port [pass] -library/browsercontext-proxy.spec.ts › should authenticate [fail] -library/browsercontext-proxy.spec.ts › should authenticate with empty password [fail] -library/browsercontext-proxy.spec.ts › should exclude patterns [fail] -library/browsercontext-proxy.spec.ts › should isolate proxy credentials between contexts [fail] -library/browsercontext-proxy.spec.ts › should isolate proxy credentials between contexts on navigation [fail] library/browsercontext-proxy.spec.ts › should proxy local network requests › by default › link-local [timeout] -library/browsercontext-proxy.spec.ts › should proxy local network requests › by default › localhost [fail] -library/browsercontext-proxy.spec.ts › should proxy local network requests › by default › loopback address [fail] library/browsercontext-proxy.spec.ts › should proxy local network requests › with other bypasses › link-local [timeout] -library/browsercontext-proxy.spec.ts › should proxy local network requests › with other bypasses › localhost [fail] -library/browsercontext-proxy.spec.ts › should proxy local network requests › with other bypasses › loopback address [fail] -library/browsercontext-proxy.spec.ts › should set cookie for top-level domain [pass] -library/browsercontext-proxy.spec.ts › should throw for bad server value [pass] -library/browsercontext-proxy.spec.ts › should throw for socks4 authentication [pass] -library/browsercontext-proxy.spec.ts › should throw for socks5 authentication [pass] -library/browsercontext-proxy.spec.ts › should use ipv6 proxy [fail] -library/browsercontext-proxy.spec.ts › should use proxy [fail] library/browsercontext-proxy.spec.ts › should use proxy for https urls [timeout] -library/browsercontext-proxy.spec.ts › should use proxy for second page [fail] -library/browsercontext-proxy.spec.ts › should use proxy twice [fail] -library/browsercontext-proxy.spec.ts › should use socks proxy [fail] -library/browsercontext-proxy.spec.ts › should use socks proxy in second page [fail] -library/browsercontext-proxy.spec.ts › should work when passing the proxy only on the context level [fail] -library/browsercontext-proxy.spec.ts › should work with IP:PORT notion [fail] -library/browsercontext-reuse.spec.ts › should continue issuing events after closing the reused page [fail] -library/browsercontext-reuse.spec.ts › should ignore binding from beforeunload [pass] -library/browsercontext-reuse.spec.ts › should not cache resources [fail] -library/browsercontext-reuse.spec.ts › should re-add binding after reset [fail] -library/browsercontext-reuse.spec.ts › should reset mouse position [pass] -library/browsercontext-reuse.spec.ts › should reset serviceworker [fail] -library/browsercontext-reuse.spec.ts › should reset serviceworker that hangs in importScripts [fail] -library/browsercontext-reuse.spec.ts › should reset tracing [pass] -library/browsercontext-reuse.spec.ts › should work with clock emulation [pass] -library/browsercontext-route.spec.ts › should chain fallback [fail] -library/browsercontext-route.spec.ts › should chain fallback into page [fail] -library/browsercontext-route.spec.ts › should chain fallback w/ dynamic URL [fail] -library/browsercontext-route.spec.ts › should fall back async [fail] -library/browsercontext-route.spec.ts › should fall back to context.route [fail] -library/browsercontext-route.spec.ts › should ignore secure Set-Cookie header for insecure requests [fail] -library/browsercontext-route.spec.ts › should intercept [fail] -library/browsercontext-route.spec.ts › should not chain abort [pass] -library/browsercontext-route.spec.ts › should not chain fulfill [fail] -library/browsercontext-route.spec.ts › should overwrite post body with empty string [fail] -library/browsercontext-route.spec.ts › should support Set-Cookie header [fail] -library/browsercontext-route.spec.ts › should support async handler w/ times [fail] -library/browsercontext-route.spec.ts › should support the times parameter with route matching [fail] -library/browsercontext-route.spec.ts › should unroute [fail] -library/browsercontext-route.spec.ts › should use Set-Cookie header in future requests [fail] -library/browsercontext-route.spec.ts › should work if handler with times parameter was removed from another handler [fail] -library/browsercontext-route.spec.ts › should work with ignoreHTTPSErrors [fail] -library/browsercontext-route.spec.ts › should yield to page.route [fail] library/browsercontext-service-worker-policy.spec.ts › block › blocks service worker registration [timeout] -library/browsercontext-service-worker-policy.spec.ts › block › should not throw error on about:blank [pass] -library/browsercontext-service-worker-policy.spec.ts › should allow service workers by default [pass] -library/browsercontext-set-extra-http-headers.spec.ts › should override extra headers from browser context [fail] -library/browsercontext-set-extra-http-headers.spec.ts › should throw for non-string header values [pass] -library/browsercontext-storage-state.spec.ts › should capture cookies [fail] -library/browsercontext-storage-state.spec.ts › should capture local storage [fail] -library/browsercontext-storage-state.spec.ts › should handle malformed file [pass] -library/browsercontext-storage-state.spec.ts › should handle missing file [pass] -library/browsercontext-storage-state.spec.ts › should not emit events about internal page [fail] -library/browsercontext-storage-state.spec.ts › should not restore localStorage twice [fail] -library/browsercontext-storage-state.spec.ts › should round-trip through the file [fail] -library/browsercontext-storage-state.spec.ts › should serialize storageState with lone surrogates [pass] -library/browsercontext-storage-state.spec.ts › should set local storage [fail] -library/browsercontext-storage-state.spec.ts › should work when service worker is intefering [pass] -library/browsercontext-strict.spec.ts › should not fail page.textContent in non-strict mode [pass] -library/browsercontext-strict.spec.ts › strict context mode › should fail page.click in strict mode [fail] -library/browsercontext-strict.spec.ts › strict context mode › should fail page.textContent in strict mode [fail] -library/browsercontext-strict.spec.ts › strict context mode › should opt out of strict mode [pass] -library/browsercontext-timezone-id.spec.ts › should affect Intl.DateTimeFormat().resolvedOptions().timeZone [fail] -library/browsercontext-timezone-id.spec.ts › should not change default timezone in another context [fail] -library/browsercontext-timezone-id.spec.ts › should throw for invalid timezone IDs when creating pages [fail] -library/browsercontext-timezone-id.spec.ts › should work @smoke [fail] -library/browsercontext-timezone-id.spec.ts › should work for multiple pages sharing same process [pass] library/browsercontext-user-agent.spec.ts › custom user agent for download [timeout] -library/browsercontext-user-agent.spec.ts › should emulate device user-agent [fail] -library/browsercontext-user-agent.spec.ts › should make a copy of default options [fail] -library/browsercontext-user-agent.spec.ts › should work [fail] -library/browsercontext-user-agent.spec.ts › should work for navigator.userAgentData and sec-ch-ua headers [unknown] -library/browsercontext-user-agent.spec.ts › should work for subframes [fail] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › default mobile viewports to 980 width [fail] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › mouse should work with mobile viewports and cross process navigations [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › respect meta viewport tag [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should be detectable [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should detect touch when applying viewport with touches [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should emulate the hover media feature [fail] library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should fire orientationchange event [timeout] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should scroll when emulating a mobile viewport [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should support landscape emulation [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should support mobile emulation [pass] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should support touch emulation [fail] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › should support window.orientation emulation [fail] -library/browsercontext-viewport-mobile.spec.ts › mobile viewport › view scale should reset after navigation [fail] -library/browsercontext-viewport.spec.ts › WebKit Windows headed should have a minimal viewport [unknown] -library/browsercontext-viewport.spec.ts › should be able to get correct orientation angle on non-mobile devices [pass] -library/browsercontext-viewport.spec.ts › should drag with high dpi [pass] -library/browsercontext-viewport.spec.ts › should emulate availWidth and availHeight [fail] -library/browsercontext-viewport.spec.ts › should emulate device height [fail] -library/browsercontext-viewport.spec.ts › should emulate device width [fail] -library/browsercontext-viewport.spec.ts › should get the proper default viewport size [pass] -library/browsercontext-viewport.spec.ts › should not have touch by default [pass] -library/browsercontext-viewport.spec.ts › should report null viewportSize when given null viewport [pass] -library/browsercontext-viewport.spec.ts › should return correct outerWidth and outerHeight [pass] -library/browsercontext-viewport.spec.ts › should set both screen and viewport options [fail] -library/browsercontext-viewport.spec.ts › should set the proper viewport size [pass] -library/browsercontext-viewport.spec.ts › should set window.screen.orientation.type for mobile devices [fail] -library/browsercontext-viewport.spec.ts › should support touch with null viewport [fail] -library/browsercontext-viewport.spec.ts › should throw on tap if hasTouch is not enabled [pass] -library/browsertype-basic.spec.ts › browserType.executablePath should work [unknown] -library/browsertype-basic.spec.ts › browserType.name should work [fail] -library/browsertype-basic.spec.ts › should throw when trying to connect with not-chromium [pass] library/browsertype-connect.spec.ts › launchServer only › should be able to reconnect to a browser 12 times without warnings [timeout] library/browsertype-connect.spec.ts › launchServer only › should properly disconnect when connection closes from the server side [timeout] library/browsertype-connect.spec.ts › launchServer only › should work with cluster [timeout] @@ -466,9 +35,6 @@ library/browsertype-connect.spec.ts › launchServer › should fulfill with glo library/browsertype-connect.spec.ts › launchServer › should handle exceptions during connect [timeout] library/browsertype-connect.spec.ts › launchServer › should ignore page.pause when headed [timeout] library/browsertype-connect.spec.ts › launchServer › should not throw on close after disconnect [timeout] -library/browsertype-connect.spec.ts › launchServer › should print HTTP error [pass] -library/browsertype-connect.spec.ts › launchServer › should print custom ws close error [pass] -library/browsertype-connect.spec.ts › launchServer › should print ws error [pass] library/browsertype-connect.spec.ts › launchServer › should properly disconnect when connection closes from the client side [timeout] library/browsertype-connect.spec.ts › launchServer › should record trace with sources [timeout] library/browsertype-connect.spec.ts › launchServer › should reject navigation when browser closes [timeout] @@ -479,501 +45,34 @@ library/browsertype-connect.spec.ts › launchServer › should respect selector library/browsertype-connect.spec.ts › launchServer › should save download [timeout] library/browsertype-connect.spec.ts › launchServer › should save har [timeout] library/browsertype-connect.spec.ts › launchServer › should saveAs videos from remote browser [timeout] -library/browsertype-connect.spec.ts › launchServer › should send default User-Agent and X-Playwright-Browser headers with connect request [fail] -library/browsertype-connect.spec.ts › launchServer › should send extra headers with connect request [pass] library/browsertype-connect.spec.ts › launchServer › should set the browser connected state [timeout] library/browsertype-connect.spec.ts › launchServer › should support slowmo option [timeout] library/browsertype-connect.spec.ts › launchServer › should terminate network waiters [timeout] library/browsertype-connect.spec.ts › launchServer › should throw when calling waitForNavigation after disconnect [timeout] library/browsertype-connect.spec.ts › launchServer › should throw when used after isConnected returns false [timeout] -library/browsertype-connect.spec.ts › launchServer › should timeout in connect while connecting [pass] -library/browsertype-connect.spec.ts › launchServer › should timeout in socket while connecting [pass] library/browsertype-connect.spec.ts › launchServer › should upload large file [timeout] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should check proxy pattern on the client [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should forward non-forwarded requests [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should lead to the error page for forwarded requests when the connection is refused [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should proxy based on the pattern [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should proxy ipv6 localhost requests @smoke [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should proxy local.playwright requests [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should proxy localhost requests @smoke [unknown] -library/browsertype-connect.spec.ts › launchServer › socks proxy › should proxy localhost requests from fetch api [unknown] -library/browsertype-connect.spec.ts › run-server › disconnected event should be emitted when browser is closed or server is closed [fail] -library/browsertype-connect.spec.ts › run-server › disconnected event should have browser as argument [fail] -library/browsertype-connect.spec.ts › run-server › setInputFiles should preserve lastModified timestamp [fail] -library/browsertype-connect.spec.ts › run-server › should be able to connect 20 times to a single server without warnings [fail] -library/browsertype-connect.spec.ts › run-server › should be able to connect two browsers at the same time [fail] -library/browsertype-connect.spec.ts › run-server › should be able to connect when the wsEndpoint is passed as an option [fail] -library/browsertype-connect.spec.ts › run-server › should be able to reconnect to a browser [fail] -library/browsertype-connect.spec.ts › run-server › should be able to visit ipv6 [fail] -library/browsertype-connect.spec.ts › run-server › should be able to visit ipv6 through localhost [fail] -library/browsertype-connect.spec.ts › run-server › should connect over http [fail] -library/browsertype-connect.spec.ts › run-server › should connect over wss [fail] -library/browsertype-connect.spec.ts › run-server › should emit close events on pages and contexts [fail] -library/browsertype-connect.spec.ts › run-server › should error when saving download after deletion [fail] -library/browsertype-connect.spec.ts › run-server › should filter launch options [fail] -library/browsertype-connect.spec.ts › run-server › should fulfill with global fetch result [fail] -library/browsertype-connect.spec.ts › run-server › should handle exceptions during connect [pass] -library/browsertype-connect.spec.ts › run-server › should ignore page.pause when headed [fail] -library/browsertype-connect.spec.ts › run-server › should not throw on close after disconnect [fail] -library/browsertype-connect.spec.ts › run-server › should print HTTP error [pass] -library/browsertype-connect.spec.ts › run-server › should print custom ws close error [pass] -library/browsertype-connect.spec.ts › run-server › should print ws error [pass] -library/browsertype-connect.spec.ts › run-server › should properly disconnect when connection closes from the client side [fail] -library/browsertype-connect.spec.ts › run-server › should record trace with sources [fail] -library/browsertype-connect.spec.ts › run-server › should reject navigation when browser closes [fail] -library/browsertype-connect.spec.ts › run-server › should reject waitForEvent before browser.close finishes [fail] -library/browsertype-connect.spec.ts › run-server › should reject waitForEvent before browser.onDisconnect fires [fail] -library/browsertype-connect.spec.ts › run-server › should reject waitForSelector when browser closes [fail] -library/browsertype-connect.spec.ts › run-server › should respect selectors [fail] -library/browsertype-connect.spec.ts › run-server › should save download [fail] -library/browsertype-connect.spec.ts › run-server › should save har [fail] -library/browsertype-connect.spec.ts › run-server › should saveAs videos from remote browser [fail] -library/browsertype-connect.spec.ts › run-server › should send default User-Agent and X-Playwright-Browser headers with connect request [fail] -library/browsertype-connect.spec.ts › run-server › should send extra headers with connect request [pass] -library/browsertype-connect.spec.ts › run-server › should set the browser connected state [fail] -library/browsertype-connect.spec.ts › run-server › should support slowmo option [fail] -library/browsertype-connect.spec.ts › run-server › should terminate network waiters [fail] -library/browsertype-connect.spec.ts › run-server › should throw when calling waitForNavigation after disconnect [fail] -library/browsertype-connect.spec.ts › run-server › should throw when used after isConnected returns false [fail] -library/browsertype-connect.spec.ts › run-server › should timeout in connect while connecting [pass] -library/browsertype-connect.spec.ts › run-server › should timeout in socket while connecting [pass] -library/browsertype-connect.spec.ts › run-server › should upload large file [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should check proxy pattern on the client [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should forward non-forwarded requests [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should lead to the error page for forwarded requests when the connection is refused [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should proxy based on the pattern [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should proxy ipv6 localhost requests @smoke [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should proxy local.playwright requests [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should proxy localhost requests @smoke [fail] -library/browsertype-connect.spec.ts › run-server › socks proxy › should proxy localhost requests from fetch api [fail] -library/browsertype-connect.spec.ts › should refuse connecting when versions do not match [pass] -library/browsertype-launch-selenium.spec.ts › selenium grid 3.141.59 hub + node chromium [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 3.141.59 standalone chromium [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 3.141.59 standalone chromium through run-driver [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 3.141.59 standalone non-chromium [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 4.8.3 hub + node chromium [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 4.8.3 standalone chromium [unknown] -library/browsertype-launch-selenium.spec.ts › selenium grid 4.8.3 standalone chromium broken driver [unknown] -library/browsertype-launch-server.spec.ts › launch server › should default to random wsPath [fail] -library/browsertype-launch-server.spec.ts › launch server › should fire "close" event during kill [fail] -library/browsertype-launch-server.spec.ts › launch server › should fire close event [fail] -library/browsertype-launch-server.spec.ts › launch server › should log protocol [fail] -library/browsertype-launch-server.spec.ts › launch server › should provide an error when ws endpoint is incorrect [fail] -library/browsertype-launch-server.spec.ts › launch server › should return child_process instance [fail] -library/browsertype-launch-server.spec.ts › launch server › should work [fail] -library/browsertype-launch-server.spec.ts › launch server › should work when wsPath is missing leading slash [fail] -library/browsertype-launch-server.spec.ts › launch server › should work with host [fail] -library/browsertype-launch-server.spec.ts › launch server › should work with port [fail] -library/browsertype-launch-server.spec.ts › launch server › should work with wsPath [fail] -library/browsertype-launch.spec.ts › should accept objects as options [pass] -library/browsertype-launch.spec.ts › should allow await using [pass] -library/browsertype-launch.spec.ts › should be callable twice [pass] -library/browsertype-launch.spec.ts › should fire close event for all contexts [pass] -library/browsertype-launch.spec.ts › should handle exception [pass] -library/browsertype-launch.spec.ts › should handle timeout [pass] -library/browsertype-launch.spec.ts › should reject all promises when browser is closed [fail] -library/browsertype-launch.spec.ts › should reject if executable path is invalid [pass] -library/browsertype-launch.spec.ts › should reject if launched browser fails immediately [fail] -library/browsertype-launch.spec.ts › should report launch log [pass] -library/browsertype-launch.spec.ts › should throw if page argument is passed [pass] -library/browsertype-launch.spec.ts › should throw if port option is passed [pass] -library/browsertype-launch.spec.ts › should throw if port option is passed for persistent context [pass] -library/browsertype-launch.spec.ts › should throw if userDataDir is passed as an argument [pass] -library/browsertype-launch.spec.ts › should throw if userDataDir option is passed [pass] -library/capabilities.spec.ts › Intl.ListFormat should work [pass] -library/capabilities.spec.ts › SharedArrayBuffer should work @smoke [fail] -library/capabilities.spec.ts › Web Assembly should work @smoke [pass] -library/capabilities.spec.ts › WebSocket should work @smoke [pass] -library/capabilities.spec.ts › loading in HTMLImageElement.prototype [pass] -library/capabilities.spec.ts › make sure that XMLHttpRequest upload events are emitted correctly [pass] -library/capabilities.spec.ts › navigator.clipboard should be present [pass] -library/capabilities.spec.ts › requestFullscreen [pass] -library/capabilities.spec.ts › service worker should cover the iframe [pass] -library/capabilities.spec.ts › service worker should register in an iframe [pass] -library/capabilities.spec.ts › serviceWorker should intercept document request [pass] -library/capabilities.spec.ts › should not crash on page with mp4 @smoke [pass] -library/capabilities.spec.ts › should not crash on showDirectoryPicker [unknown] -library/capabilities.spec.ts › should not crash on storage.getDirectory() [pass] -library/capabilities.spec.ts › should play audio @smoke [pass] -library/capabilities.spec.ts › should play video @smoke [pass] -library/capabilities.spec.ts › should play webm video @smoke [pass] -library/capabilities.spec.ts › should respect CSP @smoke [pass] -library/capabilities.spec.ts › should send no Content-Length header for GET requests with a Content-Type [pass] -library/capabilities.spec.ts › should set CloseEvent.wasClean to false when the server terminates a WebSocket connection [pass] -library/capabilities.spec.ts › should support webgl 2 @smoke [pass] -library/capabilities.spec.ts › should support webgl @smoke [pass] -library/capabilities.spec.ts › webkit should define window.safari [unknown] -library/capabilities.spec.ts › window.GestureEvent in WebKit [pass] -library/channels.spec.ts › exposeFunction should not leak [fail] -library/channels.spec.ts › should not generate dispatchers for subresources w/o listeners [pass] -library/channels.spec.ts › should scope CDPSession handles [unknown] -library/channels.spec.ts › should scope browser handles [pass] -library/channels.spec.ts › should scope context handles [pass] library/channels.spec.ts › should work with the domain module [timeout] -library/chromium/bfcache.spec.ts › bindings should work after restoring from bfcache [fail] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › serviceWorker(), and fromServiceWorker() work [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › setExtraHTTPHeaders [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › setOffline [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should intercept only serviceworker request, not page [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should intercept service worker importScripts [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should intercept service worker requests (main and within) [timeout] -library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should intercept service worker update requests [unknown] -library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should produce network events, routing, and annotations for Service Worker [fail] -library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should produce network events, routing, and annotations for Service Worker (advanced) [fail] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should report failure (due to content-type) of main service worker request [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should report failure (due to redirect) of main service worker request [timeout] library/chromium/chromium.spec.ts › PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1 › should report intercepted service worker requests in HAR [timeout] -library/chromium/chromium.spec.ts › Page.route should work with intervention headers [fail] library/chromium/chromium.spec.ts › http credentials › httpCredentials [timeout] library/chromium/chromium.spec.ts › serviceWorkers() should return current workers [timeout] library/chromium/chromium.spec.ts › should close service worker together with the context [timeout] library/chromium/chromium.spec.ts › should create a worker from a service worker [timeout] library/chromium/chromium.spec.ts › should create a worker from service worker with noop routing [timeout] library/chromium/chromium.spec.ts › should emit new service worker on update [timeout] -library/chromium/chromium.spec.ts › should not create a worker from a shared worker [pass] -library/chromium/chromium.spec.ts › should pass args with spaces [fail] -library/chromium/connect-over-cdp.spec.ts › emulate media should not be affected by second connectOverCDP [unknown] -library/chromium/connect-over-cdp.spec.ts › setInputFiles should preserve lastModified timestamp [fail] -library/chromium/connect-over-cdp.spec.ts › should allow tracing over cdp session [fail] -library/chromium/connect-over-cdp.spec.ts › should be able to connect via localhost [fail] -library/chromium/connect-over-cdp.spec.ts › should cleanup artifacts dir after connectOverCDP disconnects due to ws close [fail] -library/chromium/connect-over-cdp.spec.ts › should connect over a ws endpoint [fail] -library/chromium/connect-over-cdp.spec.ts › should connect to an existing cdp session [fail] -library/chromium/connect-over-cdp.spec.ts › should connect to an existing cdp session twice [fail] -library/chromium/connect-over-cdp.spec.ts › should connect to an existing cdp session when passed as a first argument [fail] -library/chromium/connect-over-cdp.spec.ts › should connect to existing page with iframe and navigate [fail] -library/chromium/connect-over-cdp.spec.ts › should connect to existing service workers [fail] -library/chromium/connect-over-cdp.spec.ts › should connect via https [fail] -library/chromium/connect-over-cdp.spec.ts › should connectOverCDP and manage downloads in default context [fail] -library/chromium/connect-over-cdp.spec.ts › should print custom ws close error [fail] -library/chromium/connect-over-cdp.spec.ts › should report all pages in an existing browser [fail] -library/chromium/connect-over-cdp.spec.ts › should report an expected error when the endpoint URL JSON webSocketDebuggerUrl is undefined [fail] -library/chromium/connect-over-cdp.spec.ts › should report an expected error when the endpointURL returns a non-expected status code [fail] -library/chromium/connect-over-cdp.spec.ts › should return valid browser from context.browser() [fail] library/chromium/connect-over-cdp.spec.ts › should send default User-Agent header with connect request [timeout] library/chromium/connect-over-cdp.spec.ts › should send extra headers with connect request [timeout] -library/chromium/connect-over-cdp.spec.ts › should use logger in default context [fail] -library/chromium/connect-over-cdp.spec.ts › should use proxy with connectOverCDP [fail] -library/chromium/css-coverage.spec.ts › should NOT report scripts across navigations [fail] -library/chromium/css-coverage.spec.ts › should ignore injected stylesheets [fail] -library/chromium/css-coverage.spec.ts › should report multiple stylesheets [fail] -library/chromium/css-coverage.spec.ts › should report sourceURLs [fail] -library/chromium/css-coverage.spec.ts › should report stylesheets across navigations [fail] -library/chromium/css-coverage.spec.ts › should report stylesheets that have no coverage [fail] -library/chromium/css-coverage.spec.ts › should work [fail] -library/chromium/css-coverage.spec.ts › should work with a recently loaded stylesheet [fail] -library/chromium/css-coverage.spec.ts › should work with complicated usecases [fail] -library/chromium/css-coverage.spec.ts › should work with media queries [fail] -library/chromium/disable-web-security.spec.ts › test init script w/ --disable-web-security [fail] -library/chromium/disable-web-security.spec.ts › test utility world in popup w/ --disable-web-security [pass] -library/chromium/js-coverage.spec.ts › should NOT report scripts across navigations when enabled [fail] -library/chromium/js-coverage.spec.ts › should ignore eval() scripts by default [fail] -library/chromium/js-coverage.spec.ts › should not hang when there is a debugger statement [fail] -library/chromium/js-coverage.spec.ts › should report multiple scripts [fail] -library/chromium/js-coverage.spec.ts › should report scripts across navigations when disabled [fail] -library/chromium/js-coverage.spec.ts › should report sourceURLs [fail] -library/chromium/js-coverage.spec.ts › should work [fail] -library/chromium/js-coverage.spec.ts › shouldn't ignore eval() scripts if reportAnonymousScripts is true [fail] -library/chromium/launcher.spec.ts › should not create pages automatically [fail] -library/chromium/launcher.spec.ts › should not throw with remote-debugging-port argument [fail] -library/chromium/launcher.spec.ts › should open devtools when "devtools: true" option is given [unknown] -library/chromium/launcher.spec.ts › should return background pages [fail] -library/chromium/launcher.spec.ts › should return background pages when recording video [fail] -library/chromium/launcher.spec.ts › should support request/response events when using backgroundPage() [fail] -library/chromium/launcher.spec.ts › should throw with remote-debugging-pipe argument [fail] -library/chromium/oopif.spec.ts › ElementHandle.boundingBox() should work [fail] -library/chromium/oopif.spec.ts › contentFrame should work [pass] -library/chromium/oopif.spec.ts › should allow cdp sessions on oopifs [fail] -library/chromium/oopif.spec.ts › should be able to click in iframe [pass] -library/chromium/oopif.spec.ts › should click [pass] -library/chromium/oopif.spec.ts › should click a button when it overlays oopif [pass] library/chromium/oopif.spec.ts › should emit filechooser event for iframe [timeout] -library/chromium/oopif.spec.ts › should emulate media [fail] -library/chromium/oopif.spec.ts › should emulate offline [fail] -library/chromium/oopif.spec.ts › should expose function [fail] -library/chromium/oopif.spec.ts › should get the proper viewport [unknown] -library/chromium/oopif.spec.ts › should handle oopif detach [pass] -library/chromium/oopif.spec.ts › should handle remote -> local -> remote transitions [fail] -library/chromium/oopif.spec.ts › should intercept response body from oopif [fail] -library/chromium/oopif.spec.ts › should load oopif iframes with subresources and route [fail] -library/chromium/oopif.spec.ts › should not throw on exposeFunction when oopif detaches [fail] -library/chromium/oopif.spec.ts › should report google.com frame with headed [fail] -library/chromium/oopif.spec.ts › should report main requests [pass] -library/chromium/oopif.spec.ts › should report oopif frames [pass] -library/chromium/oopif.spec.ts › should respect route [fail] -library/chromium/oopif.spec.ts › should support addInitScript [pass] -library/chromium/oopif.spec.ts › should support context options [fail] -library/chromium/oopif.spec.ts › should support exposeFunction [fail] -library/chromium/oopif.spec.ts › should take screenshot [fail] -library/chromium/session.spec.ts › should be able to detach session [fail] -library/chromium/session.spec.ts › should detach when page closes [fail] -library/chromium/session.spec.ts › should enable and disable domains independently [fail] -library/chromium/session.spec.ts › should not break page.close() [fail] -library/chromium/session.spec.ts › should only accept a page or frame [pass] -library/chromium/session.spec.ts › should reject protocol calls when page closes [fail] -library/chromium/session.spec.ts › should send events [fail] -library/chromium/session.spec.ts › should throw if target is part of main [fail] -library/chromium/session.spec.ts › should throw nice errors [fail] -library/chromium/session.spec.ts › should work [fail] -library/chromium/session.spec.ts › should work with main frame [fail] -library/chromium/session.spec.ts › should work with newBrowserCDPSession [fail] -library/chromium/tracing.spec.ts › should create directories as needed [fail] -library/chromium/tracing.spec.ts › should output a trace [fail] -library/chromium/tracing.spec.ts › should return a buffer [fail] -library/chromium/tracing.spec.ts › should run with custom categories if provided [fail] -library/chromium/tracing.spec.ts › should support a buffer without a path [fail] -library/chromium/tracing.spec.ts › should throw if tracing on two pages [fail] -library/chromium/tracing.spec.ts › should work without options [fail] -library/client-certificates.spec.ts › browser › persistentContext › should pass with matching certificates [fail] -library/client-certificates.spec.ts › browser › persistentContext › validate input [pass] -library/client-certificates.spec.ts › browser › should fail with matching certificates in legacy pfx format [pass] -library/client-certificates.spec.ts › browser › should fail with no client certificates [fail] -library/client-certificates.spec.ts › browser › should fail with self-signed client certificates [fail] -library/client-certificates.spec.ts › browser › should handle TLS renegotiation with client certificates [fail] -library/client-certificates.spec.ts › browser › should handle rejected certificate in handshake with HTTP/2 [pass] -library/client-certificates.spec.ts › browser › should have ignoreHTTPSErrors=false by default [fail] -library/client-certificates.spec.ts › browser › should keep supporting http [fail] -library/client-certificates.spec.ts › browser › should not hang on tls errors during TLS 1.2 handshake [fail] -library/client-certificates.spec.ts › browser › should pass with matching certificates [fail] -library/client-certificates.spec.ts › browser › should pass with matching certificates and trailing slash [fail] -library/client-certificates.spec.ts › browser › should pass with matching certificates in pfx format [fail] -library/client-certificates.spec.ts › browser › should pass with matching certificates in pfx format when passing as content [fail] -library/client-certificates.spec.ts › browser › should pass with matching certificates on context APIRequestContext instance [pass] -library/client-certificates.spec.ts › browser › should pass with matching certificates when passing as content [fail] -library/client-certificates.spec.ts › browser › should return target connection errors when using http2 [fail] -library/client-certificates.spec.ts › browser › should throw a http error if the pfx passphrase is incorect [pass] -library/client-certificates.spec.ts › browser › support http2 [fail] -library/client-certificates.spec.ts › browser › support http2 if the browser only supports http1.1 [unknown] -library/client-certificates.spec.ts › browser › validate input [pass] -library/client-certificates.spec.ts › fetch › pass with trusted client certificates [pass] -library/client-certificates.spec.ts › fetch › pass with trusted client certificates in pfx format [pass] -library/client-certificates.spec.ts › fetch › should fail with matching certificates in legacy pfx format [pass] -library/client-certificates.spec.ts › fetch › should fail with no client certificates provided [pass] -library/client-certificates.spec.ts › fetch › should keep supporting http [pass] -library/client-certificates.spec.ts › fetch › should throw a http error if the pfx passphrase is incorect [pass] -library/client-certificates.spec.ts › fetch › should throw with untrusted client certs [pass] -library/client-certificates.spec.ts › fetch › should work in the browser with request interception [fail] -library/client-certificates.spec.ts › fetch › validate input [pass] -library/clock.spec.ts › Intl API › Creates a RelativeTimeFormat like normal [pass] -library/clock.spec.ts › Intl API › Executes formatRange like normal [pass] -library/clock.spec.ts › Intl API › Executes formatRangeToParts like normal [pass] -library/clock.spec.ts › Intl API › Executes resolvedOptions like normal [pass] -library/clock.spec.ts › Intl API › Executes supportedLocalesOf like normal [pass] -library/clock.spec.ts › Intl API › formatToParts via isFirstOfMonth -> Returns false when passed a timestamp argument that is not first of the month [pass] -library/clock.spec.ts › Intl API › formatToParts via isFirstOfMonth -> Returns false when passed no timestamp and system time is not first of the month [pass] -library/clock.spec.ts › Intl API › formatToParts via isFirstOfMonth -> Returns true when passed a timestamp argument that is first of the month [pass] -library/clock.spec.ts › Intl API › formatToParts via isFirstOfMonth -> Returns true when passed no timestamp and system time is first of the month [pass] -library/clock.spec.ts › cancelAnimationFrame › does not remove interval [pass] -library/clock.spec.ts › cancelAnimationFrame › does not remove timeout [pass] -library/clock.spec.ts › cancelAnimationFrame › ignores null argument [pass] -library/clock.spec.ts › cancelAnimationFrame › removes animation frame [pass] -library/clock.spec.ts › cancelIdleCallback › removes idle callback [pass] -library/clock.spec.ts › clearInterval › ignores null argument [pass] -library/clock.spec.ts › clearInterval › removes interval [pass] -library/clock.spec.ts › clearInterval › removes interval with undefined interval [pass] -library/clock.spec.ts › clearInterval › removes timeout [pass] -library/clock.spec.ts › clearTimeout › ignores null argument [pass] -library/clock.spec.ts › clearTimeout › removes interval [pass] -library/clock.spec.ts › clearTimeout › removes interval with undefined interval [pass] -library/clock.spec.ts › clearTimeout › removes timeout [pass] -library/clock.spec.ts › date › creates Date objects representing clock time [pass] -library/clock.spec.ts › date › creates real Date objects [pass] -library/clock.spec.ts › date › creates regular date when passing a date as RFC 2822 string [pass] -library/clock.spec.ts › date › creates regular date when passing a date as string [pass] -library/clock.spec.ts › date › creates regular date when passing timestamp [pass] -library/clock.spec.ts › date › creates regular date when passing y, m, d [pass] -library/clock.spec.ts › date › creates regular date when passing y, m, d, h [pass] -library/clock.spec.ts › date › creates regular date when passing y, m, d, h, m [pass] -library/clock.spec.ts › date › creates regular date when passing y, m, d, h, m, s [pass] -library/clock.spec.ts › date › creates regular date when passing y, m, d, h, m, s, ms [pass] -library/clock.spec.ts › date › creates regular date when passing year, month [pass] -library/clock.spec.ts › date › listens to system clock changes [pass] -library/clock.spec.ts › date › listens to ticking clock [pass] -library/clock.spec.ts › date › mirrors UTC method [pass] -library/clock.spec.ts › date › mirrors native Date.prototype [pass] -library/clock.spec.ts › date › mirrors parse method [pass] -library/clock.spec.ts › date › mirrors toUTCString method [pass] -library/clock.spec.ts › date › provides date constructor [pass] -library/clock.spec.ts › date › returns clock.now() [pass] -library/clock.spec.ts › date › returns date as string representing clock time [pass] -library/clock.spec.ts › date › returns date as string when called as function [pass] -library/clock.spec.ts › date › returns date as string when calling with arguments [pass] -library/clock.spec.ts › date › returns date as string when calling with timestamp [pass] -library/clock.spec.ts › date › supports now method if present [pass] -library/clock.spec.ts › fastForward › handles multiple pending timers and types [pass] -library/clock.spec.ts › fastForward › ignores timers which wouldn't be run [pass] -library/clock.spec.ts › fastForward › pushes back execution time for skipped timers [pass] -library/clock.spec.ts › pauseAt › fire target timers [pass] -library/clock.spec.ts › pauseAt › pause at target time [pass] -library/clock.spec.ts › pauseAt › returns consumed clicks [pass] -library/clock.spec.ts › performance.now() › should listen to multiple ticks in performance.now [pass] -library/clock.spec.ts › performance.now() › should run along with clock.tick [pass] -library/clock.spec.ts › performance.now() › should run with ticks with timers set [pass] -library/clock.spec.ts › performance.now() › should start at 0 [pass] -library/clock.spec.ts › requestAnimationFrame › returns numeric id or object with numeric id [pass] -library/clock.spec.ts › requestAnimationFrame › returns unique id [pass] -library/clock.spec.ts › requestAnimationFrame › should be called with performance.now() even when performance unavailable [pass] -library/clock.spec.ts › requestAnimationFrame › should be called with performance.now() when available [pass] -library/clock.spec.ts › requestAnimationFrame › should call callback once [pass] -library/clock.spec.ts › requestAnimationFrame › should properly schedule callback for 3rd frame [pass] -library/clock.spec.ts › requestAnimationFrame › should run every 16ms [pass] -library/clock.spec.ts › requestAnimationFrame › should schedule for next frame if on current frame [pass] -library/clock.spec.ts › requestAnimationFrame › should schedule two callbacks before the next frame at the same time [pass] -library/clock.spec.ts › requestAnimationFrame › throws if no arguments [pass] -library/clock.spec.ts › requestIdleCallback › doesn't runs if there are any timers and no timeout option [pass] -library/clock.spec.ts › requestIdleCallback › returns numeric id [pass] -library/clock.spec.ts › requestIdleCallback › returns unique id [pass] -library/clock.spec.ts › requestIdleCallback › runs after all timers [pass] -library/clock.spec.ts › requestIdleCallback › runs no later than timeout option even if there are any timers [pass] -library/clock.spec.ts › requestIdleCallback › throws if no arguments [pass] -library/clock.spec.ts › runFor › calls function with global object or null (strict mode) as this [pass] -library/clock.spec.ts › runFor › creates updated Date while ticking [pass] -library/clock.spec.ts › runFor › creates updated Date while ticking promises [pass] -library/clock.spec.ts › runFor › does not fire canceled intervals [pass] -library/clock.spec.ts › runFor › does not fire intervals canceled in a promise [pass] -library/clock.spec.ts › runFor › does not silently catch errors [pass] -library/clock.spec.ts › runFor › does not trigger without sufficient delay [pass] -library/clock.spec.ts › runFor › fires nested setTimeout calls in user-created promises properly [pass] -library/clock.spec.ts › runFor › fires nested setTimeout calls properly [pass] -library/clock.spec.ts › runFor › fires promise timers in correct order [pass] -library/clock.spec.ts › runFor › fires timer in intervals of "13" [pass] -library/clock.spec.ts › runFor › fires timer in intervals of 13 [pass] -library/clock.spec.ts › runFor › fires timers in correct order [pass] -library/clock.spec.ts › runFor › is not influenced by forward system clock changes [pass] -library/clock.spec.ts › runFor › is not influenced by forward system clock changes 2 [pass] -library/clock.spec.ts › runFor › is not influenced by forward system clock changes in promises [pass] -library/clock.spec.ts › runFor › is not influenced by forward system clock changes when an error is thrown [pass] -library/clock.spec.ts › runFor › is not influenced by forward system clock changes when an error is thrown 2 [pass] -library/clock.spec.ts › runFor › mini integration test [pass] -library/clock.spec.ts › runFor › should settle chained user-created promises [pass] -library/clock.spec.ts › runFor › should settle local nested promises before calling timeouts [pass] -library/clock.spec.ts › runFor › should settle local promises before calling timeouts [pass] -library/clock.spec.ts › runFor › should settle multiple user-created promises [pass] -library/clock.spec.ts › runFor › should settle nested user-created promises [pass] -library/clock.spec.ts › runFor › should settle user-created promises [pass] -library/clock.spec.ts › runFor › should settle user-created promises before calling more timeouts [pass] -library/clock.spec.ts › runFor › should settle user-created promises even if some throw [pass] -library/clock.spec.ts › runFor › throws for negative minutes [pass] -library/clock.spec.ts › runFor › throws on negative ticks [pass] -library/clock.spec.ts › runFor › triggers after sufficient delay [pass] -library/clock.spec.ts › runFor › triggers even when some throw [pass] -library/clock.spec.ts › runFor › triggers immediately without specified delay [pass] -library/clock.spec.ts › runFor › triggers in the order scheduled [pass] -library/clock.spec.ts › runFor › triggers multiple simultaneous timers [pass] -library/clock.spec.ts › runFor › triggers multiple simultaneous timers with zero callAt [pass] -library/clock.spec.ts › runFor › triggers simultaneous timers [pass] -library/clock.spec.ts › runFor › triggers timeouts and intervals in the order scheduled [pass] -library/clock.spec.ts › runFor › waits after setTimeout was called [pass] -library/clock.spec.ts › setInterval › does not schedule recurring timeout when cleared [pass] -library/clock.spec.ts › setInterval › does not throw if |undefined| or |null| is passed as a callback [pass] -library/clock.spec.ts › setInterval › is not influenced by backward system clock changes [pass] -library/clock.spec.ts › setInterval › is not influenced by forward system clock changes [pass] -library/clock.spec.ts › setInterval › passes setTimeout parameters [pass] -library/clock.spec.ts › setInterval › returns numeric id or object with numeric id [pass] -library/clock.spec.ts › setInterval › returns unique id [pass] -library/clock.spec.ts › setInterval › schedules recurring timeout [pass] -library/clock.spec.ts › setInterval › throws if no arguments [pass] -library/clock.spec.ts › setTimeout › calls correct timeout on recursive tick [pass] -library/clock.spec.ts › setTimeout › does not depend on this [pass] -library/clock.spec.ts › setTimeout › does not throw if |undefined| or |null| is passed as a callback [pass] -library/clock.spec.ts › setTimeout › is not influenced by backward system clock changes [pass] -library/clock.spec.ts › setTimeout › is not influenced by forward system clock changes [pass] -library/clock.spec.ts › setTimeout › parses no-numeric string times [pass] -library/clock.spec.ts › setTimeout › parses numeric string times [pass] -library/clock.spec.ts › setTimeout › passes setTimeout parameters [pass] -library/clock.spec.ts › setTimeout › returns numeric id or object with numeric id [pass] -library/clock.spec.ts › setTimeout › returns unique id [pass] -library/clock.spec.ts › setTimeout › sets timers on instance [pass] -library/clock.spec.ts › setTimeout › starts id from a large number [pass] -library/clock.spec.ts › setTimeout › throws if no arguments [pass] -library/clock.spec.ts › setTimeout › use of eval when not in node › evals non-function callbacks [pass] -library/clock.spec.ts › setTimeout › use of eval when not in node › only evals on global scope [pass] -library/clock.spec.ts › stubTimers › deletes global property on uninstall if it was inherited onto the global object [unknown] -library/clock.spec.ts › stubTimers › does not fake methods not provided [pass] -library/clock.spec.ts › stubTimers › fake Date constructor should mirror Date's properties [pass] -library/clock.spec.ts › stubTimers › fakes Date constructor [pass] -library/clock.spec.ts › stubTimers › fakes provided methods [pass] -library/clock.spec.ts › stubTimers › global fake setTimeout should return id [pass] -library/clock.spec.ts › stubTimers › mirrors custom Date properties [pass] -library/clock.spec.ts › stubTimers › replace Event.prototype.timeStamp [pass] -library/clock.spec.ts › stubTimers › replaces global clearInterval [pass] -library/clock.spec.ts › stubTimers › replaces global clearTimeout [pass] -library/clock.spec.ts › stubTimers › replaces global performance.now [pass] -library/clock.spec.ts › stubTimers › replaces global setInterval [pass] -library/clock.spec.ts › stubTimers › replaces global setTimeout [pass] -library/clock.spec.ts › stubTimers › resets faked methods [pass] -library/clock.spec.ts › stubTimers › returns clock object [pass] -library/clock.spec.ts › stubTimers › sets initial timestamp [pass] -library/clock.spec.ts › stubTimers › should let performance.mark still be callable after install() (#136) [pass] -library/clock.spec.ts › stubTimers › should not alter the global performance properties and methods [pass] -library/clock.spec.ts › stubTimers › should replace the getEntries, getEntriesByX methods with noops that return [] [pass] -library/clock.spec.ts › stubTimers › takes an object parameter [pass] -library/clock.spec.ts › stubTimers › uninstalls Date constructor [pass] -library/clock.spec.ts › stubTimers › uninstalls global performance.now [pass] -library/clock.spec.ts › works with concurrent runFor calls [pass] -library/clock.spec.ts › works with slow setTimeout in busy embedder [pass] -library/clock.spec.ts › works with slow setTimeout in busy embedder when not paused [pass] -library/component-parser.spec.ts › should escape [pass] -library/component-parser.spec.ts › should parse [pass] -library/component-parser.spec.ts › should parse all operators [pass] -library/component-parser.spec.ts › should parse bool [pass] -library/component-parser.spec.ts › should parse float values [pass] -library/component-parser.spec.ts › should parse identifiers [pass] -library/component-parser.spec.ts › should parse int values [pass] -library/component-parser.spec.ts › should parse regex [pass] -library/component-parser.spec.ts › should parse short attributes [pass] -library/component-parser.spec.ts › should parse unquoted string [pass] -library/component-parser.spec.ts › should throw on malformed selector [pass] -library/component-parser.spec.ts › should tolerate spacing [pass] -library/css-parser.spec.ts › should parse css [pass] -library/css-parser.spec.ts › should throw on malformed css [pass] -library/debug-controller.spec.ts › should highlight all [fail] -library/debug-controller.spec.ts › should navigate all [fail] -library/debug-controller.spec.ts › should pick element [fail] -library/debug-controller.spec.ts › should record [fail] -library/debug-controller.spec.ts › should record custom data-testid [fail] -library/debug-controller.spec.ts › should report pages [fail] -library/debug-controller.spec.ts › should reset for reuse [fail] -library/debug-controller.spec.ts › should reset routes before reuse [fail] -library/defaultbrowsercontext-1.spec.ts › context.addCookies() should work [fail] -library/defaultbrowsercontext-1.spec.ts › context.clearCookies() should work [fail] -library/defaultbrowsercontext-1.spec.ts › context.cookies() should work @smoke [fail] -library/defaultbrowsercontext-1.spec.ts › should support acceptDownloads option [fail] -library/defaultbrowsercontext-1.spec.ts › should support bypassCSP option [fail] -library/defaultbrowsercontext-1.spec.ts › should support deviceScaleFactor option [fail] -library/defaultbrowsercontext-1.spec.ts › should support httpCredentials option [fail] -library/defaultbrowsercontext-1.spec.ts › should support javascriptEnabled option [fail] -library/defaultbrowsercontext-1.spec.ts › should support offline option [fail] -library/defaultbrowsercontext-1.spec.ts › should support userAgent option [fail] -library/defaultbrowsercontext-1.spec.ts › should support viewport option [fail] -library/defaultbrowsercontext-1.spec.ts › should(not) block third party cookies [fail] -library/defaultbrowsercontext-2.spec.ts › coverage should work [unknown] -library/defaultbrowsercontext-2.spec.ts › should accept userDataDir [fail] -library/defaultbrowsercontext-2.spec.ts › should connect to a browser with the default page [fail] -library/defaultbrowsercontext-2.spec.ts › should create userDataDir if it does not exist [fail] -library/defaultbrowsercontext-2.spec.ts › should fire close event for a persistent context [fail] library/defaultbrowsercontext-2.spec.ts › should handle exception [timeout] -library/defaultbrowsercontext-2.spec.ts › should handle timeout [pass] -library/defaultbrowsercontext-2.spec.ts › should have default URL when launching browser [fail] -library/defaultbrowsercontext-2.spec.ts › should have passed URL when launching with ignoreDefaultArgs: true [fail] -library/defaultbrowsercontext-2.spec.ts › should respect selectors [fail] -library/defaultbrowsercontext-2.spec.ts › should restore state from userDataDir [fail] -library/defaultbrowsercontext-2.spec.ts › should support colorScheme option [fail] -library/defaultbrowsercontext-2.spec.ts › should support extraHTTPHeaders option [fail] -library/defaultbrowsercontext-2.spec.ts › should support forcedColors option [fail] -library/defaultbrowsercontext-2.spec.ts › should support geolocation and permissions options [fail] -library/defaultbrowsercontext-2.spec.ts › should support har option [fail] -library/defaultbrowsercontext-2.spec.ts › should support hasTouch option [fail] -library/defaultbrowsercontext-2.spec.ts › should support ignoreHTTPSErrors option [fail] -library/defaultbrowsercontext-2.spec.ts › should support locale option [fail] -library/defaultbrowsercontext-2.spec.ts › should support reducedMotion option [fail] -library/defaultbrowsercontext-2.spec.ts › should support timezoneId option [fail] -library/defaultbrowsercontext-2.spec.ts › should throw if page argument is passed [pass] -library/defaultbrowsercontext-2.spec.ts › should work in persistent context [fail] -library/defaultbrowsercontext-2.spec.ts › user agent is up to date [fail] library/download.spec.ts › download event › should be able to cancel pending downloads [timeout] library/download.spec.ts › download event › should close the context without awaiting the download [timeout] -library/download.spec.ts › download event › should close the context without awaiting the failed download [unknown] library/download.spec.ts › download event › should create subdirectories when saving to non-existent user-specified path [timeout] library/download.spec.ts › download event › should delete downloads on browser gone [timeout] library/download.spec.ts › download event › should delete downloads on context destruction [timeout] @@ -991,7 +90,6 @@ library/download.spec.ts › download event › should report download when navi library/download.spec.ts › download event › should report downloads for download attribute [timeout] library/download.spec.ts › download event › should report downloads with acceptDownloads: false [timeout] library/download.spec.ts › download event › should report downloads with acceptDownloads: true [timeout] -library/download.spec.ts › download event › should report downloads with interception [fail] library/download.spec.ts › download event › should report new window downloads [timeout] library/download.spec.ts › download event › should report non-navigation downloads [timeout] library/download.spec.ts › download event › should report proper download url when download is from download attribute [timeout] @@ -1002,269 +100,22 @@ library/download.spec.ts › download event › should throw if browser dies [ti library/download.spec.ts › download event › should work with Cross-Origin-Opener-Policy [timeout] library/download.spec.ts › should be able to download a PDF file [timeout] library/download.spec.ts › should be able to download a inline PDF file via navigation [timeout] -library/download.spec.ts › should be able to download a inline PDF file via response interception [fail] library/download.spec.ts › should convert navigation to a resource with unsupported mime type into download [timeout] library/download.spec.ts › should download even if there is no "attachment" value [timeout] library/download.spec.ts › should download links with data url [timeout] library/download.spec.ts › should download successfully when routing [timeout] library/download.spec.ts › should save to user-specified path [timeout] -library/downloads-path.spec.ts › downloads path › should accept downloads in persistent context [fail] library/downloads-path.spec.ts › downloads path › should delete downloads when context closes [timeout] -library/downloads-path.spec.ts › downloads path › should delete downloads when persistent context closes [fail] library/downloads-path.spec.ts › downloads path › should keep downloadsPath folder [timeout] library/downloads-path.spec.ts › downloads path › should report downloads in downloadsPath folder [timeout] library/downloads-path.spec.ts › downloads path › should report downloads in downloadsPath folder with a relative path [timeout] -library/emulation-focus.spec.ts › should change document.activeElement [pass] -library/emulation-focus.spec.ts › should change focused iframe [pass] -library/emulation-focus.spec.ts › should focus popups by default [fail] -library/emulation-focus.spec.ts › should focus with more than one page/context [pass] -library/emulation-focus.spec.ts › should not affect mouse event target page [pass] -library/emulation-focus.spec.ts › should not affect screenshots [fail] -library/emulation-focus.spec.ts › should not fire blur events when interacting with more than one page/context [pass] -library/emulation-focus.spec.ts › should provide target for keyboard events [pass] -library/emulation-focus.spec.ts › should think that all pages are focused @smoke [pass] -library/emulation-focus.spec.ts › should think that it is focused by default [pass] -library/emulation-focus.spec.ts › should trigger hover state concurrently [pass] -library/events/add-listeners.spec.ts › EventEmitter tests › Listener order [pass] -library/events/add-listeners.spec.ts › EventEmitter tests › listener type check [pass] -library/events/add-listeners.spec.ts › EventEmitter tests › set max listeners test [pass] -library/events/add-listeners.spec.ts › EventEmitter tests › should work [pass] -library/events/check-listener-leaks.spec.ts › _maxListeners still has precedence over defaultMaxListeners [pass] -library/events/check-listener-leaks.spec.ts › defaultMaxListeners [pass] -library/events/check-listener-leaks.spec.ts › process-wide [pass] -library/events/events-list.spec.ts › EventEmitter › should maintain event names correctly [pass] -library/events/listener-count.spec.ts › Listener count test [pass] -library/events/listeners-side-effects.spec.ts › listeners empty check [pass] -library/events/listeners.spec.ts › Array copy modification does not modify orig [pass] -library/events/listeners.spec.ts › EventEmitter listeners with one listener [pass] -library/events/listeners.spec.ts › EventEmitter with no members [pass] -library/events/listeners.spec.ts › Modify array copy after multiple adds [pass] -library/events/listeners.spec.ts › listeners and once [pass] -library/events/listeners.spec.ts › listeners on prototype [pass] -library/events/listeners.spec.ts › listeners with conflicting types [pass] -library/events/listeners.spec.ts › raw listeners [pass] -library/events/listeners.spec.ts › raw listeners order [pass] -library/events/max-listeners.spec.ts › emit maxListeners on e [pass] -library/events/method-names.spec.ts › EventEmitter prototype test [pass] -library/events/modify-in-emit.spec.ts › add and remove listeners [pass] -library/events/modify-in-emit.spec.ts › removing callbacks in emit [pass] -library/events/num-args.spec.ts › should work [pass] -library/events/once.spec.ts › once() has different code paths based on the number of arguments being emitted [pass] -library/events/once.spec.ts › should work [pass] -library/events/prepend.spec.ts › EventEmitter functionality [pass] -library/events/prepend.spec.ts › Verify that the listener must be a function [pass] -library/events/remove-all-listeners-wait.spec.ts › should not throw with ignoreErrors [pass] -library/events/remove-all-listeners-wait.spec.ts › should wait [pass] -library/events/remove-all-listeners-wait.spec.ts › should wait all [pass] -library/events/remove-all-listeners-wait.spec.ts › wait should throw [pass] -library/events/remove-all-listeners.spec.ts › listener count after removeAllListeners [pass] -library/events/remove-all-listeners.spec.ts › listeners [pass] -library/events/remove-all-listeners.spec.ts › removeAllListeners on undefined _events [pass] -library/events/remove-all-listeners.spec.ts › removeAllListeners removes all listeners [pass] -library/events/remove-all-listeners.spec.ts › removeAllListeners returns EventEmitter [pass] -library/events/remove-all-listeners.spec.ts › removeAllListeners with no event type [pass] -library/events/remove-listeners.spec.ts › Eighth test [pass] -library/events/remove-listeners.spec.ts › Fifth test [pass] -library/events/remove-listeners.spec.ts › First test [pass] -library/events/remove-listeners.spec.ts › Fourth test [pass] -library/events/remove-listeners.spec.ts › Ninth test [pass] -library/events/remove-listeners.spec.ts › Second test [pass] -library/events/remove-listeners.spec.ts › Seventh test [pass] -library/events/remove-listeners.spec.ts › Sixth test [pass] -library/events/remove-listeners.spec.ts › Tenth test [pass] -library/events/remove-listeners.spec.ts › Third test [pass] -library/events/set-max-listeners-side-effects.spec.ts › set max listeners test [pass] -library/events/special-event-names.spec.ts › should support special event names [pass] -library/events/subclass.spec.ts › MyEE2 instance [pass] -library/events/subclass.spec.ts › myee instance [pass] -library/events/symbols.spec.ts › should support symbols [pass] -library/favicon.spec.ts › should load svg favicon with prefer-color-scheme [unknown] -library/fetch-proxy.spec.ts › context request should pick up proxy credentials [pass] -library/fetch-proxy.spec.ts › global request should pick up proxy credentials [pass] -library/fetch-proxy.spec.ts › should support proxy.bypass [pass] -library/fetch-proxy.spec.ts › should use socks proxy [pass] -library/fetch-proxy.spec.ts › should work with context level proxy [pass] -library/firefox/launcher.spec.ts › should pass firefox user preferences [fail] -library/firefox/launcher.spec.ts › should pass firefox user preferences in persistent [fail] library/geolocation.spec.ts › should isolate contexts [timeout] -library/geolocation.spec.ts › should not modify passed default options object [pass] -library/geolocation.spec.ts › should throw when invalid longitude [fail] -library/geolocation.spec.ts › should throw with missing latitude [pass] -library/geolocation.spec.ts › should throw with missing longitude in default options [pass] library/geolocation.spec.ts › should use context options [timeout] library/geolocation.spec.ts › should use context options for popup [timeout] library/geolocation.spec.ts › should work @smoke [timeout] library/geolocation.spec.ts › watchPosition should be notified [timeout] -library/global-fetch-cookie.spec.ts › should do case-insensitive match of cookie domain [pass] -library/global-fetch-cookie.spec.ts › should do case-insensitive match of request domain [pass] -library/global-fetch-cookie.spec.ts › should export cookies to storage state [pass] -library/global-fetch-cookie.spec.ts › should filter outgoing cookies by domain [pass] -library/global-fetch-cookie.spec.ts › should filter outgoing cookies by path [pass] -library/global-fetch-cookie.spec.ts › should override cookie from Set-Cookie header [pass] -library/global-fetch-cookie.spec.ts › should override cookie from Set-Cookie header even if it expired [pass] -library/global-fetch-cookie.spec.ts › should preserve local storage on import/export of storage state [pass] -library/global-fetch-cookie.spec.ts › should remove cookie with expires far in the past [pass] -library/global-fetch-cookie.spec.ts › should remove cookie with negative max-age [pass] -library/global-fetch-cookie.spec.ts › should remove expired cookies [pass] -library/global-fetch-cookie.spec.ts › should send cookies from storage state [pass] -library/global-fetch-cookie.spec.ts › should send not expired cookies [pass] -library/global-fetch-cookie.spec.ts › should send secure cookie over http for localhost [pass] -library/global-fetch-cookie.spec.ts › should send secure cookie over https [pass] -library/global-fetch-cookie.spec.ts › should store cookie from Set-Cookie header [pass] -library/global-fetch-cookie.spec.ts › should store cookie from Set-Cookie header even if it contains equal signs [pass] -library/global-fetch-cookie.spec.ts › should work with empty storage state [pass] -library/global-fetch-cookie.spec.ts › storage state should round-trip through file [pass] -library/global-fetch.spec.ts › delete should work @smoke [pass] -library/global-fetch.spec.ts › fetch should work @smoke [pass] -library/global-fetch.spec.ts › get should work @smoke [pass] -library/global-fetch.spec.ts › head should work @smoke [pass] -library/global-fetch.spec.ts › patch should work @smoke [pass] -library/global-fetch.spec.ts › post should work @smoke [pass] -library/global-fetch.spec.ts › put should work @smoke [pass] -library/global-fetch.spec.ts › should abort redirected requests when context is disposed [pass] -library/global-fetch.spec.ts › should abort requests when context is disposed [pass] -library/global-fetch.spec.ts › should accept already serialized data as Buffer when content-type is application/json [pass] -library/global-fetch.spec.ts › should be able to construct with context options [pass] -library/global-fetch.spec.ts › should dispose global request [pass] -library/global-fetch.spec.ts › should have nice toString [pass] -library/global-fetch.spec.ts › should json stringify array body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify bool (false) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify bool body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify literal string undefined body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify null body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify number (falsey) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify number body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify object body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify string (falsey) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should json stringify string body when content-type is application/json [pass] -library/global-fetch.spec.ts › should keep headers capitalization [pass] -library/global-fetch.spec.ts › should not double stringify array body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify bool (false) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify bool body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify literal string undefined body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify null body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify number (falsey) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify number body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify object body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify string (falsey) body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not double stringify string body when content-type is application/json [pass] -library/global-fetch.spec.ts › should not fail on empty body with encoding [pass] -library/global-fetch.spec.ts › should not follow redirects when maxRedirects is set to 0 [pass] -library/global-fetch.spec.ts › should propagate extra http headers with redirects [pass] -library/global-fetch.spec.ts › should propagate ignoreHTTPSErrors on redirects [pass] -library/global-fetch.spec.ts › should remove content-length from redirected post requests [pass] -library/global-fetch.spec.ts › should resolve url relative to global baseURL option [pass] -library/global-fetch.spec.ts › should retry ECONNRESET [pass] -library/global-fetch.spec.ts › should return body for failing requests [pass] -library/global-fetch.spec.ts › should return empty body [pass] -library/global-fetch.spec.ts › should return error with correct credentials and mismatching hostname [pass] -library/global-fetch.spec.ts › should return error with correct credentials and mismatching port [pass] -library/global-fetch.spec.ts › should return error with correct credentials and mismatching scheme [pass] -library/global-fetch.spec.ts › should return error with wrong credentials [pass] -library/global-fetch.spec.ts › should serialize post data on the client [pass] -library/global-fetch.spec.ts › should set playwright as user-agent [pass] -library/global-fetch.spec.ts › should support HTTPCredentials.send [pass] -library/global-fetch.spec.ts › should support WWW-Authenticate: Basic [pass] -library/global-fetch.spec.ts › should support global httpCredentials option [pass] -library/global-fetch.spec.ts › should support global ignoreHTTPSErrors option [pass] -library/global-fetch.spec.ts › should support global timeout option [pass] -library/global-fetch.spec.ts › should support global userAgent option [pass] -library/global-fetch.spec.ts › should throw after dispose [pass] -library/global-fetch.spec.ts › should throw an error when maxRedirects is exceeded [pass] -library/global-fetch.spec.ts › should throw an error when maxRedirects is less than 0 [pass] -library/global-fetch.spec.ts › should work with correct credentials and matching origin [pass] -library/global-fetch.spec.ts › should work with correct credentials and matching origin case insensitive [pass] -library/har.spec.ts › should attach content [fail] -library/har.spec.ts › should calculate time [pass] -library/har.spec.ts › should contain http2 for http2 requests [fail] -library/har.spec.ts › should filter by glob [pass] -library/har.spec.ts › should filter by regexp [pass] -library/har.spec.ts › should filter favicon and favicon redirects [unknown] -library/har.spec.ts › should have -1 _transferSize when its a failed request [pass] -library/har.spec.ts › should have browser [fail] -library/har.spec.ts › should have connection details [fail] -library/har.spec.ts › should have connection details for failed requests [fail] -library/har.spec.ts › should have connection details for redirects [fail] -library/har.spec.ts › should have different hars for concurrent contexts [pass] -library/har.spec.ts › should have pages [pass] -library/har.spec.ts › should have pages in persistent context [fail] -library/har.spec.ts › should have popup requests [pass] -library/har.spec.ts › should have security details [fail] -library/har.spec.ts › should have version and creator [pass] -library/har.spec.ts › should include API request [pass] -library/har.spec.ts › should include binary postData [fail] -library/har.spec.ts › should include content @smoke [fail] -library/har.spec.ts › should include cookies [pass] -library/har.spec.ts › should include form params [fail] -library/har.spec.ts › should include postData [fail] -library/har.spec.ts › should include query params [pass] -library/har.spec.ts › should include redirectURL [pass] -library/har.spec.ts › should include request [pass] -library/har.spec.ts › should include response [pass] -library/har.spec.ts › should include secure set-cookies [fail] -library/har.spec.ts › should include set-cookies [fail] -library/har.spec.ts › should include set-cookies with comma [fail] -library/har.spec.ts › should include sizes [fail] -library/har.spec.ts › should not contain internal pages [pass] -library/har.spec.ts › should not hang on resources served from cache [pass] -library/har.spec.ts › should not hang on slow chunked response [fail] -library/har.spec.ts › should omit content [pass] -library/har.spec.ts › should omit content legacy [pass] -library/har.spec.ts › should record failed request headers [pass] -library/har.spec.ts › should record failed request overrides [fail] -library/har.spec.ts › should record request overrides [fail] -library/har.spec.ts › should report the correct _transferSize with PNG files [fail] -library/har.spec.ts › should report the correct request body size [pass] -library/har.spec.ts › should report the correct request body size when the bodySize is 0 [pass] -library/har.spec.ts › should report the correct response body size when the bodySize is 0 [pass] -library/har.spec.ts › should return receive time [fail] -library/har.spec.ts › should return security details directly from response [fail] -library/har.spec.ts › should return server address directly from response [fail] -library/har.spec.ts › should skip invalid Expires [pass] -library/har.spec.ts › should throw without path [pass] -library/har.spec.ts › should use attach mode for zip extension [fail] -library/har.spec.ts › should work with gzip compression [fail] -library/headful.spec.ts › Page.bringToFront should work [pass] -library/headful.spec.ts › headless and headful should use same default fonts [fail] library/headful.spec.ts › should click background tab [timeout] -library/headful.spec.ts › should click bottom row w/ infobar in OOPIF [fail] -library/headful.spec.ts › should click in OOPIF [fail] -library/headful.spec.ts › should click when viewport size is larger than screen [pass] -library/headful.spec.ts › should close browser after context menu was triggered [pass] -library/headful.spec.ts › should close browser with beforeunload page [fail] library/headful.spec.ts › should close browsercontext with pending beforeunload dialog [timeout] -library/headful.spec.ts › should dispatch click events to oversized viewports [pass] -library/headful.spec.ts › should have default url when launching browser @smoke [fail] -library/headful.spec.ts › should not block third party SameSite=None cookies [fail] -library/headful.spec.ts › should not crash when creating second context [pass] -library/headful.spec.ts › should not override viewport size when passed null [fail] -library/headful.spec.ts › should(not) block third party cookies [pass] -library/hit-target.spec.ts › should block all events when hit target is wrong [pass] -library/hit-target.spec.ts › should block all events when hit target is wrong and element detaches [pass] -library/hit-target.spec.ts › should block click when mousedown fails [pass] -library/hit-target.spec.ts › should click an element inside closed shadow root [pass] -library/hit-target.spec.ts › should click in custom element [pass] -library/hit-target.spec.ts › should click in iframe with padding [pass] -library/hit-target.spec.ts › should click in iframe with padding 2 [pass] -library/hit-target.spec.ts › should click into frame inside closed shadow root [fail] -library/hit-target.spec.ts › should click the button again after document.write [pass] -library/hit-target.spec.ts › should click when element detaches in mousedown [pass] -library/hit-target.spec.ts › should detect overlaid element in a transformed iframe [fail] -library/hit-target.spec.ts › should detect overlay from another shadow root [pass] -library/hit-target.spec.ts › should not block programmatic events [pass] -library/hit-target.spec.ts › should not click an element overlaying iframe with the target [pass] -library/hit-target.spec.ts › should not click iframe overlaying the target [pass] -library/hit-target.spec.ts › should work with block inside inline [pass] -library/hit-target.spec.ts › should work with block inside inline in shadow dom [pass] -library/hit-target.spec.ts › should work with block-block-block inside inline-inline [pass] -library/hit-target.spec.ts › should work with drag and drop that moves the element under cursor [pass] -library/hit-target.spec.ts › should work with mui select [pass] -library/ignorehttpserrors.spec.ts › serviceWorker should intercept document request [fail] -library/ignorehttpserrors.spec.ts › should fail with WebSocket if not ignored [pass] -library/ignorehttpserrors.spec.ts › should isolate contexts [fail] -library/ignorehttpserrors.spec.ts › should work @smoke [fail] -library/ignorehttpserrors.spec.ts › should work with WebSocket [fail] -library/ignorehttpserrors.spec.ts › should work with mixed content [fail] library/inspector/cli-codegen-1.spec.ts › cli codegen › should assert navigation [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should await popup [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should check [timeout] @@ -1284,7 +135,6 @@ library/inspector/cli-codegen-1.spec.ts › cli codegen › should ignore progra library/inspector/cli-codegen-1.spec.ts › cli codegen › should make a positioned click on a canvas [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should middle click [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should not target selector preview by text regexp [timeout] -library/inspector/cli-codegen-1.spec.ts › cli codegen › should not throw csp directive violation errors [pass] library/inspector/cli-codegen-1.spec.ts › cli codegen › should press [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should record ArrowDown [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should record omnibox navigations after performAction [timeout] @@ -1296,7 +146,6 @@ library/inspector/cli-codegen-1.spec.ts › cli codegen › should uncheck [time library/inspector/cli-codegen-1.spec.ts › cli codegen › should update selected element after pressing Tab [timeout] library/inspector/cli-codegen-1.spec.ts › cli codegen › should work with TrustedTypes [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › click should emit events in order [timeout] -library/inspector/cli-codegen-2.spec.ts › cli codegen › should --save-trace [fail] library/inspector/cli-codegen-2.spec.ts › cli codegen › should check input with chaining id [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should clear files [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should contain close page [timeout] @@ -1311,7 +160,6 @@ library/inspector/cli-codegen-2.spec.ts › cli codegen › should not lead to a library/inspector/cli-codegen-2.spec.ts › cli codegen › should record navigations after identical pushState [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should record open in a new tab with url [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should reset hover model on action when element detaches [timeout] -library/inspector/cli-codegen-2.spec.ts › cli codegen › should save assets via SIGINT [fail] library/inspector/cli-codegen-2.spec.ts › cli codegen › should update active model on action [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should update hover model on action [timeout] library/inspector/cli-codegen-2.spec.ts › cli codegen › should upload a single file [timeout] @@ -1337,557 +185,31 @@ library/inspector/cli-codegen-3.spec.ts › cli codegen › should generate getB library/inspector/cli-codegen-3.spec.ts › cli codegen › should generate getByPlaceholder [timeout] library/inspector/cli-codegen-3.spec.ts › cli codegen › should generate getByTestId [timeout] library/inspector/cli-codegen-3.spec.ts › cli codegen › should generate role locators undef frame locators [timeout] -library/inspector/cli-codegen-csharp.spec.ts › should not print context options method override in mstest if no options were passed [fail] -library/inspector/cli-codegen-csharp.spec.ts › should not print context options method override in nunit if no options were passed [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print a valid basic program in mstest [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print a valid basic program in nunit [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print context options method override in mstest if options were passed [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print context options method override in nunit if options were passed [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print load/save storageState [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-csharp.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-csharp.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-csharp.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-csharp.spec.ts › should work with --save-har [fail] -library/inspector/cli-codegen-java.spec.ts › should print a valid basic program in junit [fail] -library/inspector/cli-codegen-java.spec.ts › should print load/save storage_state [fail] -library/inspector/cli-codegen-java.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-java.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-java.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-java.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-java.spec.ts › should print the correct imports in junit [fail] -library/inspector/cli-codegen-java.spec.ts › should work with --save-har [fail] -library/inspector/cli-codegen-javascript.spec.ts › should print load/save storageState [fail] -library/inspector/cli-codegen-javascript.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-javascript.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-javascript.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-javascript.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-javascript.spec.ts › should save the codegen output to a file if specified [fail] -library/inspector/cli-codegen-pytest.spec.ts › should print the correct context options when using a device and lang [unknown] -library/inspector/cli-codegen-pytest.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-pytest.spec.ts › should save the codegen output to a file if specified [fail] -library/inspector/cli-codegen-python-async.spec.ts › should print load/save storage_state [fail] -library/inspector/cli-codegen-python-async.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-python-async.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-python-async.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-python-async.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-python-async.spec.ts › should save the codegen output to a file if specified [fail] -library/inspector/cli-codegen-python-async.spec.ts › should work with --save-har [fail] -library/inspector/cli-codegen-python.spec.ts › should print load/save storage_state [fail] -library/inspector/cli-codegen-python.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-python.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-python.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-python.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-python.spec.ts › should save the codegen output to a file if specified [fail] -library/inspector/cli-codegen-test.spec.ts › should print load storageState [fail] -library/inspector/cli-codegen-test.spec.ts › should print the correct context options for custom settings [fail] -library/inspector/cli-codegen-test.spec.ts › should print the correct context options when using a device [unknown] -library/inspector/cli-codegen-test.spec.ts › should print the correct context options when using a device and additional options [unknown] -library/inspector/cli-codegen-test.spec.ts › should print the correct imports and context options [fail] -library/inspector/cli-codegen-test.spec.ts › should work with --save-har [fail] -library/inspector/console-api.spec.ts › expected properties on playwright object [pass] -library/inspector/console-api.spec.ts › should support locator.and() [pass] -library/inspector/console-api.spec.ts › should support locator.or() [pass] -library/inspector/console-api.spec.ts › should support playwright.$, playwright.$$ [pass] -library/inspector/console-api.spec.ts › should support playwright.getBy* [pass] -library/inspector/console-api.spec.ts › should support playwright.locator({ has }) [pass] -library/inspector/console-api.spec.ts › should support playwright.locator({ hasNot }) [pass] -library/inspector/console-api.spec.ts › should support playwright.locator.value [pass] -library/inspector/console-api.spec.ts › should support playwright.locator.values [pass] -library/inspector/console-api.spec.ts › should support playwright.selector [pass] -library/inspector/pause.spec.ts › pause › should hide internal calls [pass] library/inspector/pause.spec.ts › pause › should highlight locators with custom testId [timeout] library/inspector/pause.spec.ts › pause › should highlight on explore [timeout] library/inspector/pause.spec.ts › pause › should highlight on explore (csharp) [timeout] library/inspector/pause.spec.ts › pause › should highlight pointer, only in main frame [timeout] -library/inspector/pause.spec.ts › pause › should highlight waitForEvent [pass] -library/inspector/pause.spec.ts › pause › should not prevent key events [pass] -library/inspector/pause.spec.ts › pause › should pause after a navigation [pass] -library/inspector/pause.spec.ts › pause › should pause and resume the script [pass] -library/inspector/pause.spec.ts › pause › should pause and resume the script with keyboard shortcut [pass] -library/inspector/pause.spec.ts › pause › should pause on context close [pass] -library/inspector/pause.spec.ts › pause › should pause on next pause [pass] -library/inspector/pause.spec.ts › pause › should pause on page close [pass] -library/inspector/pause.spec.ts › pause › should populate log [pass] -library/inspector/pause.spec.ts › pause › should populate log with error [fail] -library/inspector/pause.spec.ts › pause › should populate log with error in waitForEvent [pass] -library/inspector/pause.spec.ts › pause › should populate log with waitForEvent [pass] -library/inspector/pause.spec.ts › pause › should resume from console [fail] -library/inspector/pause.spec.ts › pause › should show expect.toHaveText [pass] -library/inspector/pause.spec.ts › pause › should show source [pass] -library/inspector/pause.spec.ts › pause › should skip input when resuming [pass] -library/inspector/pause.spec.ts › pause › should step [pass] -library/inspector/pause.spec.ts › pause › should step with keyboard shortcut [pass] -library/inspector/pause.spec.ts › should not reset timeouts [pass] -library/inspector/pause.spec.ts › should resume when closing inspector [pass] -library/launcher.spec.ts › should have a devices object [pass] -library/launcher.spec.ts › should have an errors object [pass] -library/launcher.spec.ts › should kill browser process on timeout after close [pass] -library/launcher.spec.ts › should throw a friendly error if its headed and there is no xserver on linux running [fail] -library/locator-generator.spec.ts › asLocator internal:and [pass] -library/locator-generator.spec.ts › asLocator internal:chain [pass] -library/locator-generator.spec.ts › asLocator internal:or [pass] -library/locator-generator.spec.ts › asLocator xpath [pass] -library/locator-generator.spec.ts › generate multiple locators [pass] -library/locator-generator.spec.ts › parse locators strictly [pass] -library/locator-generator.spec.ts › parseLocator css [pass] -library/locator-generator.spec.ts › parseLocator quotes [pass] -library/locator-generator.spec.ts › reverse engineer frameLocator [pass] -library/locator-generator.spec.ts › reverse engineer getByRole [pass] -library/locator-generator.spec.ts › reverse engineer has [pass] -library/locator-generator.spec.ts › reverse engineer has + hasText [pass] -library/locator-generator.spec.ts › reverse engineer hasNot [pass] -library/locator-generator.spec.ts › reverse engineer hasNotText [pass] -library/locator-generator.spec.ts › reverse engineer hasText [pass] -library/locator-generator.spec.ts › reverse engineer ignore-case locators [pass] -library/locator-generator.spec.ts › reverse engineer internal:has-text locators [pass] -library/locator-generator.spec.ts › reverse engineer locators [pass] -library/locator-generator.spec.ts › reverse engineer locators with regex [pass] -library/locator-generator.spec.ts › reverse engineer ordered locators [pass] -library/logger.spec.ts › should log @smoke [pass] -library/logger.spec.ts › should log context-level [pass] -library/modernizr.spec.ts › Mobile Safari [unknown] -library/modernizr.spec.ts › Safari Desktop [unknown] -library/page-clock.frozen.spec.ts › clock should be frozen [unknown] -library/page-clock.frozen.spec.ts › clock should be realtime [unknown] -library/page-clock.spec.ts › Date.now › check Date.now is an integer [pass] -library/page-clock.spec.ts › Date.now › check Date.now is an integer (2) [pass] -library/page-clock.spec.ts › fastForward › ignores timers which wouldn't be run [pass] -library/page-clock.spec.ts › fastForward › pushes back execution time for skipped timers [fail] -library/page-clock.spec.ts › fastForward › supports string time arguments [fail] -library/page-clock.spec.ts › popup › should not run time before popup on pause [fail] -library/page-clock.spec.ts › popup › should run time before popup [pass] -library/page-clock.spec.ts › popup › should tick after popup [fail] -library/page-clock.spec.ts › popup › should tick before popup [fail] -library/page-clock.spec.ts › runFor › creates updated Date while ticking [fail] -library/page-clock.spec.ts › runFor › does not trigger without sufficient delay [pass] -library/page-clock.spec.ts › runFor › passes 1 minute [fail] -library/page-clock.spec.ts › runFor › passes 2 hours, 34 minutes and 10 seconds [fail] -library/page-clock.spec.ts › runFor › passes 8 seconds [fail] -library/page-clock.spec.ts › runFor › returns the current now value [pass] -library/page-clock.spec.ts › runFor › throws for invalid format [pass] -library/page-clock.spec.ts › runFor › triggers after sufficient delay [fail] -library/page-clock.spec.ts › runFor › triggers event when some throw [fail] -library/page-clock.spec.ts › runFor › triggers immediately without specified delay [fail] -library/page-clock.spec.ts › runFor › triggers multiple simultaneous timers [fail] -library/page-clock.spec.ts › runFor › triggers simultaneous timers [fail] -library/page-clock.spec.ts › runFor › waits after setTimeout was called [fail] -library/page-clock.spec.ts › setFixedTime › allows installing fake timers after settings time [fail] -library/page-clock.spec.ts › setFixedTime › allows setting time multiple times [pass] -library/page-clock.spec.ts › setFixedTime › does not fake methods [pass] -library/page-clock.spec.ts › setFixedTime › fixed time is not affected by clock manipulation [pass] -library/page-clock.spec.ts › stubTimers › fakes Date constructor [pass] -library/page-clock.spec.ts › stubTimers › global fake setTimeout should return id [pass] -library/page-clock.spec.ts › stubTimers › replaces global clearInterval [pass] -library/page-clock.spec.ts › stubTimers › replaces global clearTimeout [pass] -library/page-clock.spec.ts › stubTimers › replaces global performance.now [pass] -library/page-clock.spec.ts › stubTimers › replaces global performance.timeOrigin [pass] -library/page-clock.spec.ts › stubTimers › replaces global setInterval [fail] -library/page-clock.spec.ts › stubTimers › replaces global setTimeout [fail] -library/page-clock.spec.ts › stubTimers › sets initial timestamp [pass] -library/page-clock.spec.ts › stubTimers › should throw for invalid date [pass] -library/page-clock.spec.ts › while on pause › fastForward should not run nested immediate [fail] -library/page-clock.spec.ts › while on pause › runFor should not run nested immediate [fail] -library/page-clock.spec.ts › while on pause › runFor should not run nested immediate from microtask [fail] -library/page-clock.spec.ts › while running › should fastForward [pass] -library/page-clock.spec.ts › while running › should fastForwardTo [pass] -library/page-clock.spec.ts › while running › should pause [pass] -library/page-clock.spec.ts › while running › should pause and fastForward [pass] -library/page-clock.spec.ts › while running › should progress time [pass] -library/page-clock.spec.ts › while running › should runFor [pass] -library/page-clock.spec.ts › while running › should set system time on pause [pass] library/page-event-crash.spec.ts › should be able to close context when page crashes [timeout] library/page-event-crash.spec.ts › should cancel navigation when page crashes [timeout] library/page-event-crash.spec.ts › should cancel waitForEvent when page crashes [timeout] library/page-event-crash.spec.ts › should emit crash event when page crashes [timeout] library/page-event-crash.spec.ts › should throw on any action after page crashes [timeout] -library/pdf.spec.ts › should be able to generate outline [unknown] -library/pdf.spec.ts › should be able to save file [unknown] -library/permissions.spec.ts › permissions › should accumulate when adding [fail] -library/permissions.spec.ts › permissions › should be prompt by default [pass] -library/permissions.spec.ts › permissions › should clear permissions [fail] -library/permissions.spec.ts › permissions › should deny permission when not listed [fail] -library/permissions.spec.ts › permissions › should fail when bad permission is given [fail] -library/permissions.spec.ts › permissions › should grant geolocation permission when origin is listed [fail] -library/permissions.spec.ts › permissions › should grant notifications permission when listed [fail] -library/permissions.spec.ts › permissions › should grant permission when creating context [fail] -library/permissions.spec.ts › permissions › should grant permission when listed for all domains [fail] -library/permissions.spec.ts › permissions › should isolate permissions between browser contexts [fail] -library/permissions.spec.ts › permissions › should prompt for geolocation permission when origin is not listed [pass] -library/permissions.spec.ts › permissions › should reset permissions [fail] -library/permissions.spec.ts › permissions › should trigger permission onchange [fail] -library/permissions.spec.ts › should support clipboard read [fail] -library/permissions.spec.ts › storage access [unknown] -library/popup.spec.ts › BrowserContext.addInitScript should apply to a cross-process popup [fail] -library/popup.spec.ts › BrowserContext.addInitScript should apply to an in-process popup [fail] -library/popup.spec.ts › should expose function from browser context [fail] -library/popup.spec.ts › should inherit extra headers from browser context [fail] -library/popup.spec.ts › should inherit http credentials from browser context [fail] -library/popup.spec.ts › should inherit offline from browser context [fail] -library/popup.spec.ts › should inherit touch support from browser context [fail] -library/popup.spec.ts › should inherit user agent from browser context @smoke [fail] -library/popup.spec.ts › should inherit viewport size from browser context [fail] -library/popup.spec.ts › should not dispatch binding on a closed page [fail] -library/popup.spec.ts › should not throttle rAF in the opener page [pass] -library/popup.spec.ts › should not throw when click closes popup [pass] -library/popup.spec.ts › should respect routes from browser context [fail] -library/popup.spec.ts › should respect routes from browser context when using window.open [fail] library/popup.spec.ts › should use viewport size from window features [timeout] -library/proxy-pattern.spec.ts › socks proxy patter matcher [pass] -library/proxy.spec.ts › does launch without a port [pass] -library/proxy.spec.ts › should authenticate [fail] -library/proxy.spec.ts › should exclude patterns [pass] -library/proxy.spec.ts › should proxy local network requests › by default › link-local [pass] -library/proxy.spec.ts › should proxy local network requests › by default › localhost [pass] -library/proxy.spec.ts › should proxy local network requests › by default › loopback address [pass] -library/proxy.spec.ts › should proxy local network requests › with other bypasses › link-local [pass] -library/proxy.spec.ts › should proxy local network requests › with other bypasses › localhost [pass] -library/proxy.spec.ts › should proxy local network requests › with other bypasses › loopback address [pass] -library/proxy.spec.ts › should throw for bad server value [pass] -library/proxy.spec.ts › should use SOCKS proxy for websocket requests [pass] -library/proxy.spec.ts › should use proxy @smoke [pass] -library/proxy.spec.ts › should use proxy for second page [pass] -library/proxy.spec.ts › should use proxy with emulated user agent [unknown] -library/proxy.spec.ts › should use socks proxy [pass] -library/proxy.spec.ts › should use socks proxy in second page [pass] -library/proxy.spec.ts › should work with IP:PORT notion [pass] -library/proxy.spec.ts › should work with authenticate followed by redirect [fail] -library/resource-timing.spec.ts › should work @smoke [pass] -library/resource-timing.spec.ts › should work for SSL [fail] -library/resource-timing.spec.ts › should work for redirect [fail] -library/resource-timing.spec.ts › should work for subresource [fail] -library/resource-timing.spec.ts › should work when serving from memory cache [fail] -library/role-utils.spec.ts › accessible name nested treeitem [pass] -library/role-utils.spec.ts › accessible name with slots [pass] -library/role-utils.spec.ts › axe-core accessible-text [pass] -library/role-utils.spec.ts › axe-core implicit-role [pass] -library/role-utils.spec.ts › control embedded in a label [pass] -library/role-utils.spec.ts › control embedded in a target element [pass] -library/role-utils.spec.ts › display:contents should be visible when contents are visible [pass] -library/role-utils.spec.ts › label/labelled-by aria-hidden with descendants [pass] -library/role-utils.spec.ts › native controls [pass] -library/role-utils.spec.ts › native controls labelled-by [pass] -library/role-utils.spec.ts › own aria-label concatenated with aria-labelledby [pass] -library/role-utils.spec.ts › should ignore stylesheet from hidden aria-labelledby subtree [pass] -library/role-utils.spec.ts › should not include hidden pseudo into accessible name [pass] -library/role-utils.spec.ts › should work with form and tricky input names [pass] -library/role-utils.spec.ts › svg role=presentation [pass] -library/role-utils.spec.ts › svg title [pass] -library/role-utils.spec.ts › wpt accname #0 [pass] -library/role-utils.spec.ts › wpt accname #1 [pass] -library/role-utils.spec.ts › wpt accname #2 [pass] -library/role-utils.spec.ts › wpt accname #3 [pass] -library/role-utils.spec.ts › wpt accname non-manual [pass] -library/screenshot.spec.ts › element screenshot › element screenshot should work with a mobile viewport [fail] -library/screenshot.spec.ts › element screenshot › element screenshot should work with device scale factor [fail] -library/screenshot.spec.ts › element screenshot › element screenshots should handle vh units [fail] -library/screenshot.spec.ts › element screenshot › page screenshot should capture css transform with device pixels [fail] -library/screenshot.spec.ts › element screenshot › should capture full element when larger than viewport with device scale factor [fail] -library/screenshot.spec.ts › element screenshot › should capture full element when larger than viewport with device scale factor and scale:css [fail] -library/screenshot.spec.ts › element screenshot › should restore default viewport after fullPage screenshot [fail] -library/screenshot.spec.ts › element screenshot › should restore viewport after element screenshot and exception [pass] -library/screenshot.spec.ts › element screenshot › should restore viewport after page screenshot and exception [pass] -library/screenshot.spec.ts › element screenshot › should restore viewport after page screenshot and timeout [fail] -library/screenshot.spec.ts › element screenshot › should take element screenshot when default viewport is null and restore back [fail] -library/screenshot.spec.ts › element screenshot › should take fullPage screenshots when default viewport is null [fail] -library/screenshot.spec.ts › element screenshot › should take screenshots when default viewport is null [fail] -library/screenshot.spec.ts › element screenshot › should work if the main resource hangs [fail] -library/screenshot.spec.ts › page screenshot › should handle vh units [fail] -library/screenshot.spec.ts › page screenshot › should run in parallel in multiple pages [fail] -library/screenshot.spec.ts › page screenshot › should throw if screenshot size is too large with device scale factor [fail] -library/screenshot.spec.ts › page screenshot › should work with a mobile viewport [fail] -library/screenshot.spec.ts › page screenshot › should work with a mobile viewport and clip [fail] -library/screenshot.spec.ts › page screenshot › should work with a mobile viewport and fullPage [fail] -library/screenshot.spec.ts › page screenshot › should work with device scale factor [fail] -library/screenshot.spec.ts › page screenshot › should work with device scale factor and clip [fail] -library/screenshot.spec.ts › page screenshot › should work with device scale factor and scale:css [fail] -library/screenshot.spec.ts › page screenshot › should work with device scale factor, clip and scale:css [fail] -library/screenshot.spec.ts › page screenshot › should work with large size [fail] -library/selector-generator.spec.ts › selector generator › should accept valid aria-label for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should accept valid data-test-id for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should chain text after parent [pass] -library/selector-generator.spec.ts › selector generator › should escape text with quote [pass] -library/selector-generator.spec.ts › selector generator › should escape text with slash [pass] -library/selector-generator.spec.ts › selector generator › should find text in shadow dom [pass] -library/selector-generator.spec.ts › selector generator › should generate exact label when necessary [pass] -library/selector-generator.spec.ts › selector generator › should generate exact placeholder when necessary [pass] -library/selector-generator.spec.ts › selector generator › should generate exact role when necessary [pass] -library/selector-generator.spec.ts › selector generator › should generate exact text when necessary [pass] -library/selector-generator.spec.ts › selector generator › should generate exact title when necessary [pass] -library/selector-generator.spec.ts › selector generator › should generate label selector [pass] -library/selector-generator.spec.ts › selector generator › should generate multiple: noId [pass] -library/selector-generator.spec.ts › selector generator › should generate multiple: noId noText [pass] -library/selector-generator.spec.ts › selector generator › should generate multiple: noText in role [pass] -library/selector-generator.spec.ts › selector generator › should generate multiple: noText in text [pass] -library/selector-generator.spec.ts › selector generator › should generate relative selector [pass] -library/selector-generator.spec.ts › selector generator › should generate text and normalize whitespace [pass] -library/selector-generator.spec.ts › selector generator › should generate text for [pass] -library/selector-generator.spec.ts › selector generator › should generate title selector [pass] -library/selector-generator.spec.ts › selector generator › should handle first non-unique data-testid [pass] -library/selector-generator.spec.ts › selector generator › should handle second non-unique data-testid [pass] -library/selector-generator.spec.ts › selector generator › should ignore empty aria-label for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should ignore empty data-test-id for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should ignore empty role for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should match in deep shadow dom [pass] -library/selector-generator.spec.ts › selector generator › should match in shadow dom [pass] -library/selector-generator.spec.ts › selector generator › should not accept invalid role for candidate consideration [pass] -library/selector-generator.spec.ts › selector generator › should not escape spaces inside named attr selectors [pass] -library/selector-generator.spec.ts › selector generator › should not escape text with >> [pass] -library/selector-generator.spec.ts › selector generator › should not improve guid text [pass] -library/selector-generator.spec.ts › selector generator › should not prefer zero-sized button over inner span [pass] -library/selector-generator.spec.ts › selector generator › should not use generated id [pass] -library/selector-generator.spec.ts › selector generator › should not use input[value] [pass] -library/selector-generator.spec.ts › selector generator › should not use text for select [pass] -library/selector-generator.spec.ts › selector generator › should prefer button over inner span [pass] -library/selector-generator.spec.ts › selector generator › should prefer data-testid [pass] -library/selector-generator.spec.ts › selector generator › should prefer role other input[type] [pass] -library/selector-generator.spec.ts › selector generator › should prefer role=button over inner span [pass] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › name [pass] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › placeholder [pass] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › role [pass] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › type [pass] -library/selector-generator.spec.ts › selector generator › should properly join child selectors under nested ordinals [pass] -library/selector-generator.spec.ts › selector generator › should separate selectors by >> [pass] -library/selector-generator.spec.ts › selector generator › should trim long text [pass] -library/selector-generator.spec.ts › selector generator › should trim text [pass] -library/selector-generator.spec.ts › selector generator › should try to improve label text by shortening [pass] -library/selector-generator.spec.ts › selector generator › should try to improve role name [pass] -library/selector-generator.spec.ts › selector generator › should try to improve text [pass] -library/selector-generator.spec.ts › selector generator › should try to improve text by shortening [pass] -library/selector-generator.spec.ts › selector generator › should use data-testid in strict errors [pass] -library/selector-generator.spec.ts › selector generator › should use internal:has-text [pass] -library/selector-generator.spec.ts › selector generator › should use internal:has-text with regexp [pass] -library/selector-generator.spec.ts › selector generator › should use internal:has-text with regexp with a quote [pass] -library/selector-generator.spec.ts › selector generator › should use nested ordinals [pass] -library/selector-generator.spec.ts › selector generator › should use ordinal for identical nodes [pass] -library/selector-generator.spec.ts › selector generator › should use parent text [pass] -library/selector-generator.spec.ts › selector generator › should use readable id [pass] -library/selector-generator.spec.ts › selector generator › should use the name attributes for elements that can have it [pass] -library/selector-generator.spec.ts › selector generator › should work in dynamic iframes without navigation [fail] -library/selector-generator.spec.ts › selector generator › should work with tricky attributes [pass] -library/selector-generator.spec.ts › selector generator › should work without CSS.escape [pass] -library/selectors-register.spec.ts › should handle errors [pass] -library/selectors-register.spec.ts › should not rely on engines working from the root [pass] -library/selectors-register.spec.ts › should throw a nice error if the selector returns a bad value [pass] -library/selectors-register.spec.ts › should work [pass] -library/selectors-register.spec.ts › should work in main and isolated world [pass] -library/selectors-register.spec.ts › should work when registered on global [pass] -library/selectors-register.spec.ts › should work with path [pass] library/shared-worker.spec.ts › should survive shared worker restart [timeout] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo check [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo click [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo press [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo selectOption [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo setInputFiles [fail] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo type [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo uncheck [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo check [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo click [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo goto [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo press [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo selectOption [pass] library/slowmo.spec.ts › slowMo › Frame SlowMo setInputFiles [timeout] -library/slowmo.spec.ts › slowMo › Frame SlowMo type [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo uncheck [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo check [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo click [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo goto [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo press [pass] library/slowmo.spec.ts › slowMo › Page SlowMo reload [timeout] -library/slowmo.spec.ts › slowMo › Page SlowMo selectOption [pass] library/slowmo.spec.ts › slowMo › Page SlowMo setInputFiles [timeout] -library/slowmo.spec.ts › slowMo › Page SlowMo type [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo uncheck [pass] -library/snapshotter.spec.ts › snapshots › empty adopted style sheets should not prevent node refs [pass] -library/snapshotter.spec.ts › snapshots › should capture frame [fail] -library/snapshotter.spec.ts › snapshots › should capture iframe [fail] -library/snapshotter.spec.ts › snapshots › should capture iframe with srcdoc [fail] -library/snapshotter.spec.ts › snapshots › should capture resources [fail] library/snapshotter.spec.ts › snapshots › should capture snapshot target [timeout] -library/snapshotter.spec.ts › snapshots › should collect multiple [pass] -library/snapshotter.spec.ts › snapshots › should collect on attribute change [pass] -library/snapshotter.spec.ts › snapshots › should collect snapshot [pass] -library/snapshotter.spec.ts › snapshots › should have a custom doctype [pass] -library/snapshotter.spec.ts › snapshots › should not navigate on anchor clicks [pass] -library/snapshotter.spec.ts › snapshots › should preserve BASE and other content on reset [pass] -library/snapshotter.spec.ts › snapshots › should replace meta charset attr that specifies charset [pass] -library/snapshotter.spec.ts › snapshots › should replace meta content attr that specifies charset [pass] -library/snapshotter.spec.ts › snapshots › should respect CSSOM change through CSSGroupingRule [pass] -library/snapshotter.spec.ts › snapshots › should respect attr removal [pass] -library/snapshotter.spec.ts › snapshots › should respect inline CSSOM change [pass] -library/snapshotter.spec.ts › snapshots › should respect node removal [pass] -library/snapshotter.spec.ts › snapshots › should respect subresource CSSOM change [fail] -library/tap.spec.ts › locators › should send all of the correct events [fail] -library/tap.spec.ts › should not send mouse events touchstart is canceled [fail] -library/tap.spec.ts › should not send mouse events when touchend is canceled [fail] -library/tap.spec.ts › should not wait for a navigation caused by a tap [fail] -library/tap.spec.ts › should send all of the correct events @smoke [fail] -library/tap.spec.ts › should send well formed touch points [fail] -library/tap.spec.ts › should wait until an element is visible to tap it [fail] -library/tap.spec.ts › should work with modifiers [fail] -library/tap.spec.ts › trial run should not tap [fail] -library/trace-viewer.spec.ts › should allow hiding route actions [fail] -library/trace-viewer.spec.ts › should allow showing screenshots instead of snapshots [fail] -library/trace-viewer.spec.ts › should capture data-url svg iframe [fail] -library/trace-viewer.spec.ts › should capture iframe with sandbox attribute [fail] -library/trace-viewer.spec.ts › should complain about newer version of trace in old viewer [pass] -library/trace-viewer.spec.ts › should contain action info [pass] -library/trace-viewer.spec.ts › should contain adopted style sheets [pass] -library/trace-viewer.spec.ts › should display language-specific locators [pass] -library/trace-viewer.spec.ts › should display waitForLoadState even if did not wait for it [pass] -library/trace-viewer.spec.ts › should filter network requests by resource type [pass] -library/trace-viewer.spec.ts › should filter network requests by url [pass] -library/trace-viewer.spec.ts › should follow redirects [fail] -library/trace-viewer.spec.ts › should handle case where neither snapshots nor screenshots exist [pass] -library/trace-viewer.spec.ts › should handle file URIs [unknown] -library/trace-viewer.spec.ts › should handle multiple headers [fail] -library/trace-viewer.spec.ts › should handle src=blob [fail] -library/trace-viewer.spec.ts › should have correct snapshot size [pass] -library/trace-viewer.spec.ts › should have correct stack trace [pass] -library/trace-viewer.spec.ts › should have network request overrides [fail] -library/trace-viewer.spec.ts › should have network request overrides 2 [fail] -library/trace-viewer.spec.ts › should have network requests [pass] -library/trace-viewer.spec.ts › should highlight expect failure [pass] -library/trace-viewer.spec.ts › should highlight locator in iframe while typing [fail] -library/trace-viewer.spec.ts › should highlight target element in shadow dom [fail] -library/trace-viewer.spec.ts › should highlight target elements [fail] -library/trace-viewer.spec.ts › should ignore 304 responses [fail] -library/trace-viewer.spec.ts › should include metainfo [pass] -library/trace-viewer.spec.ts › should include requestUrl in route.abort [fail] -library/trace-viewer.spec.ts › should include requestUrl in route.continue [fail] -library/trace-viewer.spec.ts › should include requestUrl in route.fulfill [fail] -library/trace-viewer.spec.ts › should not crash with broken locator [pass] -library/trace-viewer.spec.ts › should open console errors on click [fail] -library/trace-viewer.spec.ts › should open simple trace viewer [pass] -library/trace-viewer.spec.ts › should open snapshot in new browser context [pass] -library/trace-viewer.spec.ts › should open trace viewer on specific host [pass] -library/trace-viewer.spec.ts › should open trace-1.31 [pass] -library/trace-viewer.spec.ts › should open trace-1.37 [pass] -library/trace-viewer.spec.ts › should open two trace files [pass] -library/trace-viewer.spec.ts › should open two trace files of the same test [pass] -library/trace-viewer.spec.ts › should open two trace viewers [pass] -library/trace-viewer.spec.ts › should pick locator [pass] -library/trace-viewer.spec.ts › should pick locator in iframe [fail] -library/trace-viewer.spec.ts › should popup snapshot [pass] -library/trace-viewer.spec.ts › should prefer later resource request with the same method [fail] -library/trace-viewer.spec.ts › should preserve currentSrc [pass] -library/trace-viewer.spec.ts › should preserve noscript when javascript is disabled [pass] -library/trace-viewer.spec.ts › should register custom elements [pass] -library/trace-viewer.spec.ts › should remove noscript by default [pass] -library/trace-viewer.spec.ts › should remove noscript when javaScriptEnabled is set to true [pass] -library/trace-viewer.spec.ts › should render console [fail] -library/trace-viewer.spec.ts › should render network bars [pass] -library/trace-viewer.spec.ts › should restore control values [fail] -library/trace-viewer.spec.ts › should restore scroll positions [pass] library/trace-viewer.spec.ts › should serve css without content-type [timeout] -library/trace-viewer.spec.ts › should serve overridden request [fail] -library/trace-viewer.spec.ts › should show action source [pass] -library/trace-viewer.spec.ts › should show baseURL in metadata pane [pass] -library/trace-viewer.spec.ts › should show correct request start time [fail] -library/trace-viewer.spec.ts › should show empty trace viewer [pass] -library/trace-viewer.spec.ts › should show font preview [fail] -library/trace-viewer.spec.ts › should show null as a param [pass] -library/trace-viewer.spec.ts › should show only one pointer with multilevel iframes [unknown] -library/trace-viewer.spec.ts › should show params and return value [pass] -library/trace-viewer.spec.ts › should show similar actions from library-only trace [pass] -library/trace-viewer.spec.ts › should show snapshot URL [pass] -library/trace-viewer.spec.ts › should update highlight when typing [pass] -library/trace-viewer.spec.ts › should work with adopted style sheets and all: unset [fail] -library/trace-viewer.spec.ts › should work with adopted style sheets and replace/replaceSync [pass] -library/trace-viewer.spec.ts › should work with meta CSP [pass] -library/trace-viewer.spec.ts › should work with nesting CSS selectors [pass] -library/tracing.spec.ts › should collect sources [pass] library/tracing.spec.ts › should collect trace with resources, but no js [timeout] -library/tracing.spec.ts › should collect two traces [pass] -library/tracing.spec.ts › should exclude internal pages [pass] -library/tracing.spec.ts › should export trace concurrently to second navigation [fail] -library/tracing.spec.ts › should flush console events on tracing stop [pass] -library/tracing.spec.ts › should hide internal stack frames [pass] -library/tracing.spec.ts › should hide internal stack frames in expect [pass] -library/tracing.spec.ts › should ignore iframes in head [pass] -library/tracing.spec.ts › should include context API requests [pass] -library/tracing.spec.ts › should include interrupted actions [pass] -library/tracing.spec.ts › should not collect snapshots by default [pass] -library/tracing.spec.ts › should not crash when browser closes mid-trace [pass] -library/tracing.spec.ts › should not emit after w/o before [pass] -library/tracing.spec.ts › should not flush console events [pass] -library/tracing.spec.ts › should not hang for clicks that open dialogs [pass] -library/tracing.spec.ts › should not include buffers in the trace [fail] -library/tracing.spec.ts › should not include trace resources from the previous chunks [fail] -library/tracing.spec.ts › should not stall on dialogs [pass] -library/tracing.spec.ts › should not throw when stopping without start but not exporting [pass] -library/tracing.spec.ts › should overwrite existing file [fail] -library/tracing.spec.ts › should produce screencast frames crop [fail] -library/tracing.spec.ts › should produce screencast frames fit [fail] -library/tracing.spec.ts › should produce screencast frames scale [fail] -library/tracing.spec.ts › should record global request trace [pass] -library/tracing.spec.ts › should record network failures [fail] -library/tracing.spec.ts › should respect tracesDir and name [fail] -library/tracing.spec.ts › should store global request traces separately [pass] -library/tracing.spec.ts › should store postData for global request [pass] -library/tracing.spec.ts › should survive browser.close with auto-created traces dir [pass] -library/tracing.spec.ts › should throw when starting with different options [pass] -library/tracing.spec.ts › should throw when stopping without start [pass] -library/tracing.spec.ts › should use the correct apiName for event driven callbacks [fail] -library/tracing.spec.ts › should work with multiple chunks [pass] -library/unroute-behavior.spec.ts › context.close should not wait for active route handlers on the owned pages [fail] library/unroute-behavior.spec.ts › context.unroute should not wait for pending handlers to complete [timeout] -library/unroute-behavior.spec.ts › context.unrouteAll removes all handlers [pass] library/unroute-behavior.spec.ts › context.unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors [timeout] library/unroute-behavior.spec.ts › context.unrouteAll should wait for pending handlers to complete [timeout] -library/unroute-behavior.spec.ts › page.close does not wait for active route handlers [fail] -library/unroute-behavior.spec.ts › page.close should not wait for active route handlers on the owning context [fail] -library/unroute-behavior.spec.ts › page.unroute should not wait for pending handlers to complete [fail] -library/unroute-behavior.spec.ts › page.unrouteAll removes all routes [fail] -library/unroute-behavior.spec.ts › page.unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors [fail] -library/unroute-behavior.spec.ts › page.unrouteAll should wait for pending handlers to complete [fail] -library/unroute-behavior.spec.ts › route.continue should not throw if page has been closed [fail] -library/unroute-behavior.spec.ts › route.fallback should not throw if page has been closed [fail] -library/unroute-behavior.spec.ts › route.fulfill should not throw if page has been closed [fail] -library/video.spec.ts › screencast › saveAs should throw when no video frames [pass] -library/video.spec.ts › screencast › should be 800x450 by default [fail] -library/video.spec.ts › screencast › should be 800x600 with null viewport [fail] -library/video.spec.ts › screencast › should capture css transformation [fail] -library/video.spec.ts › screencast › should capture full viewport [fail] -library/video.spec.ts › screencast › should capture full viewport on hidpi [fail] -library/video.spec.ts › screencast › should capture navigation [fail] -library/video.spec.ts › screencast › should capture static page [fail] -library/video.spec.ts › screencast › should capture static page in persistent context @smoke [fail] -library/video.spec.ts › screencast › should continue recording main page after popup closes [fail] -library/video.spec.ts › screencast › should delete video [fail] -library/video.spec.ts › screencast › should emulate an iphone [fail] library/video.spec.ts › screencast › should expose video path [timeout] library/video.spec.ts › screencast › should expose video path blank page [timeout] library/video.spec.ts › screencast › should expose video path blank popup [timeout] -library/video.spec.ts › screencast › should not create video for internal pages [unknown] -library/video.spec.ts › screencast › should scale frames down to the requested size [fail] -library/video.spec.ts › screencast › should throw if browser dies [fail] -library/video.spec.ts › screencast › should throw on browser close [fail] -library/video.spec.ts › screencast › should throw without recordVideo.dir [pass] -library/video.spec.ts › screencast › should use viewport scaled down to fit into 800x800 as default size [fail] -library/video.spec.ts › screencast › should wait for video to finish if page was closed [fail] -library/video.spec.ts › screencast › should work for popups [fail] -library/video.spec.ts › screencast › should work with old options [fail] library/video.spec.ts › screencast › should work with relative path for recordVideo.dir [timeout] -library/video.spec.ts › screencast › should work with video+trace [fail] library/video.spec.ts › screencast › should work with weird screen resolution [timeout] -library/video.spec.ts › screencast › videoSize should require videosPath [pass] -library/video.spec.ts › should saveAs video [fail] library/web-socket.spec.ts › should emit binary frame events [timeout] library/web-socket.spec.ts › should emit close events [timeout] library/web-socket.spec.ts › should emit error [timeout] @@ -1896,6 +218,4 @@ library/web-socket.spec.ts › should filter out the close events when the serve library/web-socket.spec.ts › should not have stray error events [timeout] library/web-socket.spec.ts › should pass self as argument to close event [timeout] library/web-socket.spec.ts › should reject waitForEvent on page close [timeout] -library/web-socket.spec.ts › should reject waitForEvent on socket close [timeout] -library/web-socket.spec.ts › should turn off when offline [unknown] -library/web-socket.spec.ts › should work @smoke [pass] \ No newline at end of file +library/web-socket.spec.ts › should reject waitForEvent on socket close [timeout] \ No newline at end of file diff --git a/tests/bidi/expectations/bidi-chromium-page.txt b/tests/bidi/expectations/bidi-chromium-page.txt index 0664b52591..d02e876e24 100644 --- a/tests/bidi/expectations/bidi-chromium-page.txt +++ b/tests/bidi/expectations/bidi-chromium-page.txt @@ -1,933 +1,15 @@ -page/elementhandle-bounding-box.spec.ts › should force a layout [pass] -page/elementhandle-bounding-box.spec.ts › should get frame box [pass] -page/elementhandle-bounding-box.spec.ts › should handle nested frames [fail] -page/elementhandle-bounding-box.spec.ts › should handle scroll offset and click [pass] -page/elementhandle-bounding-box.spec.ts › should return null for invisible elements [fail] -page/elementhandle-bounding-box.spec.ts › should work [pass] -page/elementhandle-bounding-box.spec.ts › should work when inline box child is outside of viewport [pass] -page/elementhandle-bounding-box.spec.ts › should work with SVG nodes [pass] -page/elementhandle-click.spec.ts › should double click the button [pass] -page/elementhandle-click.spec.ts › should throw for
elements with force [pass] -page/elementhandle-click.spec.ts › should throw for detached nodes [pass] -page/elementhandle-click.spec.ts › should throw for hidden nodes with force [pass] -page/elementhandle-click.spec.ts › should throw for recursively hidden nodes with force [pass] -page/elementhandle-click.spec.ts › should work @smoke [pass] -page/elementhandle-click.spec.ts › should work for Shadow DOM v1 [pass] -page/elementhandle-click.spec.ts › should work for TextNodes [fail] -page/elementhandle-click.spec.ts › should work with Node removed [pass] -page/elementhandle-content-frame.spec.ts › should return null for document.documentElement [pass] -page/elementhandle-content-frame.spec.ts › should return null for non-iframes [pass] -page/elementhandle-content-frame.spec.ts › should work [pass] -page/elementhandle-content-frame.spec.ts › should work for cross-frame evaluations [fail] -page/elementhandle-content-frame.spec.ts › should work for cross-process iframes [pass] -page/elementhandle-convenience.spec.ts › getAttribute should work [pass] -page/elementhandle-convenience.spec.ts › innerHTML should work [pass] -page/elementhandle-convenience.spec.ts › innerText should throw [pass] -page/elementhandle-convenience.spec.ts › innerText should work [pass] -page/elementhandle-convenience.spec.ts › inputValue should work [pass] -page/elementhandle-convenience.spec.ts › isChecked should work [pass] -page/elementhandle-convenience.spec.ts › isEditable should work [pass] -page/elementhandle-convenience.spec.ts › isEnabled and isDisabled should work [pass] -page/elementhandle-convenience.spec.ts › isEnabled and isDisabled should work with [fail] -page/page-fill.spec.ts › should throw on incorrect color value [pass] -page/page-fill.spec.ts › should throw on incorrect date [pass] -page/page-fill.spec.ts › should throw on incorrect datetime-local [unknown] -page/page-fill.spec.ts › should throw on incorrect month [unknown] -page/page-fill.spec.ts › should throw on incorrect range value [pass] -page/page-fill.spec.ts › should throw on incorrect time [pass] -page/page-fill.spec.ts › should throw on incorrect week [unknown] -page/page-fill.spec.ts › should throw on unsupported inputs [pass] -page/page-focus.spec.ts › clicking checkbox should activate it [unknown] -page/page-focus.spec.ts › keeps focus on element when attempting to focus a non-focusable element [pass] -page/page-focus.spec.ts › should emit blur event [fail] -page/page-focus.spec.ts › should emit focus event [fail] -page/page-focus.spec.ts › should traverse focus [fail] -page/page-focus.spec.ts › should traverse focus in all directions [pass] -page/page-focus.spec.ts › should traverse only form elements [unknown] -page/page-focus.spec.ts › should work @smoke [pass] -page/page-goto.spec.ts › js redirect overrides url bar navigation [fail] -page/page-goto.spec.ts › should be able to navigate to a page controlled by service worker [pass] -page/page-goto.spec.ts › should capture cross-process iframe navigation request [pass] -page/page-goto.spec.ts › should capture iframe navigation request [pass] -page/page-goto.spec.ts › should disable timeout when its set to 0 [pass] -page/page-goto.spec.ts › should fail when canceled by another navigation [pass] -page/page-goto.spec.ts › should fail when exceeding browser context navigation timeout [pass] -page/page-goto.spec.ts › should fail when exceeding browser context timeout [pass] -page/page-goto.spec.ts › should fail when exceeding default maximum navigation timeout [pass] -page/page-goto.spec.ts › should fail when exceeding default maximum timeout [pass] -page/page-goto.spec.ts › should fail when exceeding maximum navigation timeout [pass] -page/page-goto.spec.ts › should fail when main resources failed to load [fail] -page/page-goto.spec.ts › should fail when navigating and show the url at the error message [pass] -page/page-goto.spec.ts › should fail when navigating to bad SSL [fail] -page/page-goto.spec.ts › should fail when navigating to bad SSL after redirects [fail] -page/page-goto.spec.ts › should fail when navigating to bad url [fail] -page/page-goto.spec.ts › should fail when replaced by another navigation [pass] -page/page-goto.spec.ts › should fail when server returns 204 [fail] -page/page-goto.spec.ts › should navigate to URL with hash and fire requests without hash [pass] -page/page-goto.spec.ts › should navigate to about:blank [pass] -page/page-goto.spec.ts › should navigate to dataURL and not fire dataURL requests [pass] -page/page-goto.spec.ts › should navigate to empty page with domcontentloaded [pass] -page/page-goto.spec.ts › should not crash when RTCPeerConnection is used [pass] -page/page-goto.spec.ts › should not crash when navigating to bad SSL after a cross origin navigation [pass] -page/page-goto.spec.ts › should not leak listeners during 20 waitForNavigation [pass] -page/page-goto.spec.ts › should not leak listeners during bad navigation [pass] -page/page-goto.spec.ts › should not leak listeners during navigation [pass] -page/page-goto.spec.ts › should not resolve goto upon window.stop() [pass] -page/page-goto.spec.ts › should not throw if networkidle0 is passed as an option [pass] -page/page-goto.spec.ts › should not throw unhandled rejections on invalid url [pass] -page/page-goto.spec.ts › should override referrer-policy [fail] -page/page-goto.spec.ts › should prioritize default navigation timeout over default timeout [pass] -page/page-goto.spec.ts › should properly wait for load [pass] -page/page-goto.spec.ts › should reject referer option when setExtraHTTPHeaders provides referer [pass] -page/page-goto.spec.ts › should report raw buffer for main resource [fail] -page/page-goto.spec.ts › should return from goto if new navigation is started [pass] -page/page-goto.spec.ts › should return last response in redirect chain [pass] -page/page-goto.spec.ts › should return response when page changes its URL after load [pass] -page/page-goto.spec.ts › should return url with basic auth info [pass] -page/page-goto.spec.ts › should return when navigation is committed if commit is specified [fail] -page/page-goto.spec.ts › should send referer [fail] -page/page-goto.spec.ts › should send referer of cross-origin URL [fail] -page/page-goto.spec.ts › should succeed on url bar navigation when there is pending navigation [pass] -page/page-goto.spec.ts › should throw if networkidle2 is passed as an option [pass] -page/page-goto.spec.ts › should use http for no protocol [pass] -page/page-goto.spec.ts › should wait for load when iframe attaches and detaches [pass] -page/page-goto.spec.ts › should work @smoke [pass] -page/page-goto.spec.ts › should work cross-process [pass] -page/page-goto.spec.ts › should work when navigating to 404 [pass] -page/page-goto.spec.ts › should work when navigating to data url [pass] -page/page-goto.spec.ts › should work when navigating to valid url [pass] -page/page-goto.spec.ts › should work when page calls history API in beforeunload [pass] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy [pass] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy after redirect [pass] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy and interception [fail] page/page-goto.spec.ts › should work with anchor navigation [timeout] -page/page-goto.spec.ts › should work with cross-process that fails before committing [pass] -page/page-goto.spec.ts › should work with file URL [pass] -page/page-goto.spec.ts › should work with file URL with subframes [pass] -page/page-goto.spec.ts › should work with lazy loading iframes [pass] -page/page-goto.spec.ts › should work with redirects [pass] -page/page-goto.spec.ts › should work with self requesting page [pass] -page/page-goto.spec.ts › should work with subframes return 204 [pass] -page/page-goto.spec.ts › should work with subframes return 204 with domcontentloaded [pass] -page/page-history.spec.ts › goBack/goForward should work with bfcache-able pages [fail] page/page-history.spec.ts › page.goBack during renderer-initiated navigation [timeout] -page/page-history.spec.ts › page.goBack should work @smoke [fail] -page/page-history.spec.ts › page.goBack should work for file urls [fail] -page/page-history.spec.ts › page.goBack should work with HistoryAPI [fail] -page/page-history.spec.ts › page.goForward during renderer-initiated navigation [fail] -page/page-history.spec.ts › page.reload during renderer-initiated navigation [pass] -page/page-history.spec.ts › page.reload should not resolve with same-document navigation [fail] -page/page-history.spec.ts › page.reload should work [pass] -page/page-history.spec.ts › page.reload should work on a page with a hash [pass] -page/page-history.spec.ts › page.reload should work on a page with a hash at the end [pass] -page/page-history.spec.ts › page.reload should work with cross-origin redirect [pass] page/page-history.spec.ts › page.reload should work with data url [timeout] -page/page-history.spec.ts › page.reload should work with same origin redirect [pass] -page/page-history.spec.ts › regression test for issue 20791 [pass] -page/page-history.spec.ts › should reload proper page [pass] -page/page-keyboard.spec.ts › insertText should only emit input event [fail] -page/page-keyboard.spec.ts › pressing Meta should not result in any text insertion on any platform [pass] -page/page-keyboard.spec.ts › should be able to prevent selectAll [pass] -page/page-keyboard.spec.ts › should dispatch a click event on a button when Enter gets pressed [pass] -page/page-keyboard.spec.ts › should dispatch a click event on a button when Space gets pressed [pass] -page/page-keyboard.spec.ts › should dispatch insertText after context menu was opened [pass] -page/page-keyboard.spec.ts › should expose keyIdentifier in webkit [unknown] -page/page-keyboard.spec.ts › should handle selectAll [pass] -page/page-keyboard.spec.ts › should have correct Keydown/Keyup order when pressing Escape key [pass] -page/page-keyboard.spec.ts › should move around the selection in a contenteditable [pass] -page/page-keyboard.spec.ts › should move to the start of the document [fail] -page/page-keyboard.spec.ts › should move with the arrow keys [pass] -page/page-keyboard.spec.ts › should not type canceled events [pass] -page/page-keyboard.spec.ts › should press Enter [fail] -page/page-keyboard.spec.ts › should press plus [fail] -page/page-keyboard.spec.ts › should press shift plus [fail] -page/page-keyboard.spec.ts › should press the meta key [pass] -page/page-keyboard.spec.ts › should report multiple modifiers [pass] -page/page-keyboard.spec.ts › should report shiftKey [pass] -page/page-keyboard.spec.ts › should scroll with PageDown [pass] -page/page-keyboard.spec.ts › should send a character with ElementHandle.press [pass] -page/page-keyboard.spec.ts › should send a character with insertText [fail] -page/page-keyboard.spec.ts › should send proper codes while typing [pass] page/page-keyboard.spec.ts › should send proper codes while typing with shift [timeout] -page/page-keyboard.spec.ts › should shift raw codes [pass] -page/page-keyboard.spec.ts › should specify location [fail] -page/page-keyboard.spec.ts › should specify repeat property [pass] -page/page-keyboard.spec.ts › should support MacOS shortcuts [fail] -page/page-keyboard.spec.ts › should support multiple plus-separated modifiers [pass] -page/page-keyboard.spec.ts › should support plus-separated modifiers [pass] -page/page-keyboard.spec.ts › should support simple copy-pasting [pass] -page/page-keyboard.spec.ts › should support simple cut-pasting [pass] -page/page-keyboard.spec.ts › should support undo-redo [pass] -page/page-keyboard.spec.ts › should throw on unknown keys [pass] -page/page-keyboard.spec.ts › should type after context menu was opened [pass] -page/page-keyboard.spec.ts › should type all kinds of characters [pass] -page/page-keyboard.spec.ts › should type emoji [pass] -page/page-keyboard.spec.ts › should type emoji into an iframe [pass] -page/page-keyboard.spec.ts › should type into a textarea @smoke [pass] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom [pass] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom with nested elements [pass] -page/page-keyboard.spec.ts › should type repeatedly in input in shadow dom [pass] -page/page-keyboard.spec.ts › should work after a cross origin navigation [pass] -page/page-keyboard.spec.ts › should work with keyboard events with empty.html [pass] -page/page-keyboard.spec.ts › type to non-focusable element should maintain old focus [pass] -page/page-leaks.spec.ts › click should not leak [pass] -page/page-leaks.spec.ts › expect should not leak [pass] -page/page-leaks.spec.ts › fill should not leak [pass] -page/page-leaks.spec.ts › waitFor should not leak [pass] -page/page-listeners.spec.ts › should not throw with ignoreErrors [pass] -page/page-listeners.spec.ts › should wait [pass] -page/page-listeners.spec.ts › wait should throw [pass] -page/page-mouse.spec.ts › down and up should generate click [pass] -page/page-mouse.spec.ts › should always round down [fail] -page/page-mouse.spec.ts › should click the document @smoke [pass] -page/page-mouse.spec.ts › should dblclick the div [pass] -page/page-mouse.spec.ts › should dispatch mouse move after context menu was opened [pass] -page/page-mouse.spec.ts › should not crash on mouse drag with any button [pass] -page/page-mouse.spec.ts › should pointerdown the div with a custom button [pass] -page/page-mouse.spec.ts › should report correct buttons property [pass] -page/page-mouse.spec.ts › should select the text with mouse [pass] -page/page-mouse.spec.ts › should set modifier keys on click [pass] -page/page-mouse.spec.ts › should trigger hover state [pass] -page/page-mouse.spec.ts › should trigger hover state on disabled button [pass] -page/page-mouse.spec.ts › should trigger hover state with removed window.Node [pass] -page/page-mouse.spec.ts › should tween mouse movement [pass] -page/page-navigation.spec.ts › should work with _blank target [pass] -page/page-navigation.spec.ts › should work with _blank target in form [pass] -page/page-navigation.spec.ts › should work with cross-process _blank target [pass] -page/page-network-idle.spec.ts › should navigate to empty page with networkidle [pass] -page/page-network-idle.spec.ts › should wait for networkidle from the child frame [pass] -page/page-network-idle.spec.ts › should wait for networkidle from the popup [pass] -page/page-network-idle.spec.ts › should wait for networkidle in setContent [pass] -page/page-network-idle.spec.ts › should wait for networkidle in setContent from the child frame [pass] -page/page-network-idle.spec.ts › should wait for networkidle in setContent with request from previous navigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle in waitForNavigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle to succeed navigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle to succeed navigation with request from previous navigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle when iframe attaches and detaches [pass] -page/page-network-idle.spec.ts › should wait for networkidle when navigating iframe [pass] -page/page-network-idle.spec.ts › should work after repeated navigations in the same page [fail] -page/page-network-request.spec.ts › page.reload return 304 status code [fail] -page/page-network-request.spec.ts › should get the same headers as the server [fail] -page/page-network-request.spec.ts › should get the same headers as the server CORS [fail] -page/page-network-request.spec.ts › should get |undefined| with postData() when there is no post data [pass] -page/page-network-request.spec.ts › should get |undefined| with postDataJSON() when there is no post data [pass] -page/page-network-request.spec.ts › should handle mixed-content blocked requests [unknown] -page/page-network-request.spec.ts › should not allow to access frame on popup main request [pass] -page/page-network-request.spec.ts › should not get preflight CORS requests when intercepting [fail] -page/page-network-request.spec.ts › should not return allHeaders() until they are available [fail] -page/page-network-request.spec.ts › should not work for a redirect and interception [fail] -page/page-network-request.spec.ts › should override post data content type [fail] -page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded [fail] -page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded; charset=UTF-8 [fail] -page/page-network-request.spec.ts › should parse the json post data [fail] -page/page-network-request.spec.ts › should report all cookies in one header [pass] -page/page-network-request.spec.ts › should report raw headers [fail] -page/page-network-request.spec.ts › should report raw response headers in redirects [fail] -page/page-network-request.spec.ts › should return event source [fail] -page/page-network-request.spec.ts › should return headers [pass] -page/page-network-request.spec.ts › should return multipart/form-data [fail] -page/page-network-request.spec.ts › should return navigation bit [pass] -page/page-network-request.spec.ts › should return navigation bit when navigating to image [pass] -page/page-network-request.spec.ts › should return postData [fail] -page/page-network-request.spec.ts › should work for a redirect [pass] -page/page-network-request.spec.ts › should work for fetch requests @smoke [pass] -page/page-network-request.spec.ts › should work for main frame navigation request [pass] -page/page-network-request.spec.ts › should work for subframe navigation request [pass] -page/page-network-request.spec.ts › should work with binary post data [fail] -page/page-network-request.spec.ts › should work with binary post data and interception [fail] -page/page-network-response.spec.ts › should behave the same way for headers and allHeaders [fail] -page/page-network-response.spec.ts › should bypass disk cache when context interception is enabled [fail] -page/page-network-response.spec.ts › should bypass disk cache when page interception is enabled [fail] -page/page-network-response.spec.ts › should provide a Response with a file URL [fail] -page/page-network-response.spec.ts › should reject response.finished if context closes [fail] -page/page-network-response.spec.ts › should reject response.finished if page closes [fail] -page/page-network-response.spec.ts › should report all headers [fail] -page/page-network-response.spec.ts › should report if request was fromServiceWorker [fail] -page/page-network-response.spec.ts › should report multiple set-cookie headers [fail] -page/page-network-response.spec.ts › should return body [fail] -page/page-network-response.spec.ts › should return body for prefetch script [fail] -page/page-network-response.spec.ts › should return body with compression [fail] -page/page-network-response.spec.ts › should return headers after route.fulfill [fail] -page/page-network-response.spec.ts › should return json [fail] -page/page-network-response.spec.ts › should return multiple header value [fail] -page/page-network-response.spec.ts › should return set-cookie header after route.fulfill [fail] -page/page-network-response.spec.ts › should return status text [pass] -page/page-network-response.spec.ts › should return text [fail] -page/page-network-response.spec.ts › should return uncompressed text [fail] -page/page-network-response.spec.ts › should throw when requesting body of redirected response [pass] -page/page-network-response.spec.ts › should wait until response completes [fail] -page/page-network-response.spec.ts › should work @smoke [fail] -page/page-network-sizes.spec.ts › should handle redirects [fail] -page/page-network-sizes.spec.ts › should have correct responseBodySize for 404 with content [pass] -page/page-network-sizes.spec.ts › should have the correct responseBodySize [fail] -page/page-network-sizes.spec.ts › should have the correct responseBodySize for chunked request [fail] -page/page-network-sizes.spec.ts › should have the correct responseBodySize with gzip compression [fail] -page/page-network-sizes.spec.ts › should return sizes without hanging [pass] -page/page-network-sizes.spec.ts › should set bodySize and headersSize [pass] -page/page-network-sizes.spec.ts › should set bodySize to 0 if there was no body [pass] -page/page-network-sizes.spec.ts › should set bodySize to 0 when there was no response body [pass] -page/page-network-sizes.spec.ts › should set bodySize, headersSize, and transferSize [fail] -page/page-network-sizes.spec.ts › should throw for failed requests [pass] -page/page-network-sizes.spec.ts › should work with 200 status code [fail] -page/page-network-sizes.spec.ts › should work with 401 status code [fail] -page/page-network-sizes.spec.ts › should work with 404 status code [fail] -page/page-network-sizes.spec.ts › should work with 500 status code [fail] -page/page-object-count.spec.ts › should count objects [unknown] -page/page-request-continue.spec.ts › continue should delete headers on redirects [fail] -page/page-request-continue.spec.ts › continue should not change multipart/form-data body [fail] -page/page-request-continue.spec.ts › continue should propagate headers to redirects [fail] -page/page-request-continue.spec.ts › post data › should amend binary post data [fail] -page/page-request-continue.spec.ts › post data › should amend longer post data [fail] -page/page-request-continue.spec.ts › post data › should amend method and post data [fail] -page/page-request-continue.spec.ts › post data › should amend post data [fail] -page/page-request-continue.spec.ts › post data › should amend utf8 post data [fail] -page/page-request-continue.spec.ts › post data › should compute content-length from post data [fail] -page/page-request-continue.spec.ts › post data › should use content-type from original request [fail] -page/page-request-continue.spec.ts › redirected requests should report overridden headers [fail] -page/page-request-continue.spec.ts › should amend HTTP headers [fail] -page/page-request-continue.spec.ts › should amend method [fail] -page/page-request-continue.spec.ts › should amend method on main request [fail] -page/page-request-continue.spec.ts › should continue preload link requests [fail] -page/page-request-continue.spec.ts › should delete header with undefined value [fail] -page/page-request-continue.spec.ts › should delete the origin header [fail] -page/page-request-continue.spec.ts › should intercept css variable with background url [fail] -page/page-request-continue.spec.ts › should not allow changing protocol when overriding url [fail] -page/page-request-continue.spec.ts › should not throw if request was cancelled by the page [fail] -page/page-request-continue.spec.ts › should not throw when continuing after page is closed [fail] -page/page-request-continue.spec.ts › should not throw when continuing while page is closing [fail] -page/page-request-continue.spec.ts › should override method along with url [fail] -page/page-request-continue.spec.ts › should override request url [fail] -page/page-request-continue.spec.ts › should work [fail] -page/page-request-continue.spec.ts › should work with Cross-Origin-Opener-Policy [fail] -page/page-request-fallback.spec.ts › post data › should amend binary post data [fail] -page/page-request-fallback.spec.ts › post data › should amend json post data [fail] -page/page-request-fallback.spec.ts › post data › should amend post data [fail] -page/page-request-fallback.spec.ts › should amend HTTP headers [fail] -page/page-request-fallback.spec.ts › should amend method [fail] -page/page-request-fallback.spec.ts › should chain once [fail] -page/page-request-fallback.spec.ts › should delete header with undefined value [fail] -page/page-request-fallback.spec.ts › should fall back [fail] -page/page-request-fallback.spec.ts › should fall back after exception [fail] -page/page-request-fallback.spec.ts › should fall back async [fail] -page/page-request-fallback.spec.ts › should not chain abort [fail] -page/page-request-fallback.spec.ts › should not chain fulfill [fail] -page/page-request-fallback.spec.ts › should override request url [fail] -page/page-request-fallback.spec.ts › should work [fail] -page/page-request-fulfill.spec.ts › headerValue should return set-cookie from intercepted response [fail] -page/page-request-fulfill.spec.ts › should allow mocking binary responses [fail] -page/page-request-fulfill.spec.ts › should allow mocking svg with charset [fail] -page/page-request-fulfill.spec.ts › should fetch original request and fulfill [fail] -page/page-request-fulfill.spec.ts › should fulfill json [fail] -page/page-request-fulfill.spec.ts › should fulfill preload link requests [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch response that has multiple set-cookie [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch result [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch result and overrides [fail] -page/page-request-fulfill.spec.ts › should fulfill with global fetch result [fail] -page/page-request-fulfill.spec.ts › should fulfill with gzip and readback [fail] -page/page-request-fulfill.spec.ts › should fulfill with har response [fail] -page/page-request-fulfill.spec.ts › should fulfill with multiple set-cookie [fail] -page/page-request-fulfill.spec.ts › should fulfill with unuassigned status codes [fail] -page/page-request-fulfill.spec.ts › should include the origin header [fail] -page/page-request-fulfill.spec.ts › should not go to the network for fulfilled requests body [fail] -page/page-request-fulfill.spec.ts › should not modify the headers sent to the server [fail] -page/page-request-fulfill.spec.ts › should not throw if request was cancelled by the page [fail] -page/page-request-fulfill.spec.ts › should stringify intercepted request response headers [fail] -page/page-request-fulfill.spec.ts › should work [fail] -page/page-request-fulfill.spec.ts › should work with buffer as body [fail] -page/page-request-fulfill.spec.ts › should work with file path [fail] -page/page-request-fulfill.spec.ts › should work with status code 422 [fail] -page/page-request-intercept.spec.ts › request.postData is not null when fetching FormData with a Blob [fail] -page/page-request-intercept.spec.ts › should fulfill intercepted response [fail] -page/page-request-intercept.spec.ts › should fulfill intercepted response using alias [fail] -page/page-request-intercept.spec.ts › should fulfill popup main request using alias [fail] -page/page-request-intercept.spec.ts › should fulfill response with empty body [fail] -page/page-request-intercept.spec.ts › should fulfill with any response [fail] -page/page-request-intercept.spec.ts › should give access to the intercepted response [fail] -page/page-request-intercept.spec.ts › should give access to the intercepted response body [fail] -page/page-request-intercept.spec.ts › should intercept multipart/form-data request body [unknown] -page/page-request-intercept.spec.ts › should intercept with post data override [fail] -page/page-request-intercept.spec.ts › should intercept with url override [fail] -page/page-request-intercept.spec.ts › should not follow redirects when maxRedirects is set to 0 in route.fetch [fail] -page/page-request-intercept.spec.ts › should override with defaults when intercepted response not provided [fail] -page/page-request-intercept.spec.ts › should support fulfill after intercept [fail] -page/page-request-intercept.spec.ts › should support timeout option in route.fetch [fail] -page/page-route.spec.ts › route.abort should throw if called twice [fail] -page/page-route.spec.ts › route.continue should throw if called twice [fail] -page/page-route.spec.ts › route.fallback should throw if called twice [fail] -page/page-route.spec.ts › route.fulfill should throw if called twice [fail] -page/page-route.spec.ts › should add Access-Control-Allow-Origin by default when fulfill [fail] -page/page-route.spec.ts › should allow null origin for about:blank [fail] -page/page-route.spec.ts › should be able to fetch dataURL and not fire dataURL requests [fail] -page/page-route.spec.ts › should be able to remove headers [fail] -page/page-route.spec.ts › should be abortable [fail] -page/page-route.spec.ts › should be abortable with custom error codes [fail] -page/page-route.spec.ts › should chain fallback w/ dynamic URL [fail] -page/page-route.spec.ts › should contain raw request header [fail] -page/page-route.spec.ts › should contain raw response header [fail] -page/page-route.spec.ts › should contain raw response header after fulfill [fail] -page/page-route.spec.ts › should contain referer header [fail] -page/page-route.spec.ts › should fail navigation when aborting main resource [fail] -page/page-route.spec.ts › should fulfill with redirect status [fail] -page/page-route.spec.ts › should intercept @smoke [fail] -page/page-route.spec.ts › should intercept main resource during cross-process navigation [fail] -page/page-route.spec.ts › should intercept when postData is more than 1MB [fail] -page/page-route.spec.ts › should navigate to URL with hash and and fire requests without hash [fail] -page/page-route.spec.ts › should navigate to dataURL and not fire dataURL requests [fail] -page/page-route.spec.ts › should not auto-intercept non-preflight OPTIONS [fail] -page/page-route.spec.ts › should not fulfill with redirect status [unknown] -page/page-route.spec.ts › should not throw "Invalid Interception Id" if the request was cancelled [fail] -page/page-route.spec.ts › should not throw if request was cancelled by the page [fail] -page/page-route.spec.ts › should not work with redirects [fail] -page/page-route.spec.ts › should override cookie header [pass] -page/page-route.spec.ts › should pause intercepted XHR until continue [fail] -page/page-route.spec.ts › should pause intercepted fetch request until continue [fail] -page/page-route.spec.ts › should properly return navigation response when URL has cookies [fail] -page/page-route.spec.ts › should reject cors with disallowed credentials [fail] -page/page-route.spec.ts › should respect cors overrides [fail] -page/page-route.spec.ts › should send referer [fail] -page/page-route.spec.ts › should show custom HTTP headers [fail] -page/page-route.spec.ts › should support ? in glob pattern [fail] -page/page-route.spec.ts › should support async handler w/ times [fail] -page/page-route.spec.ts › should support cors for different methods [fail] -page/page-route.spec.ts › should support cors with GET [fail] -page/page-route.spec.ts › should support cors with POST [fail] -page/page-route.spec.ts › should support cors with credentials [fail] -page/page-route.spec.ts › should support the times parameter with route matching [fail] -page/page-route.spec.ts › should unroute [fail] -page/page-route.spec.ts › should work if handler with times parameter was removed from another handler [fail] -page/page-route.spec.ts › should work when POST is redirected with 302 [fail] -page/page-route.spec.ts › should work when header manipulation headers with redirect [fail] -page/page-route.spec.ts › should work with badly encoded server [fail] -page/page-route.spec.ts › should work with custom referer headers [fail] -page/page-route.spec.ts › should work with encoded server [fail] -page/page-route.spec.ts › should work with encoded server - 2 [fail] -page/page-route.spec.ts › should work with equal requests [fail] -page/page-route.spec.ts › should work with redirect inside sync XHR [fail] -page/page-route.spec.ts › should work with redirects for subresources [fail] -page/page-screenshot.spec.ts › page screenshot animations › should capture screenshots after layoutchanges in transitionend event [fail] -page/page-screenshot.spec.ts › page screenshot animations › should fire transitionend for finite transitions [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not capture css animations in shadow DOM [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite web animations [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not capture pseudo element css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not change animation with playbackRate equal to 0 [fail] -page/page-screenshot.spec.ts › page screenshot animations › should resume infinite animations [fail] -page/page-screenshot.spec.ts › page screenshot animations › should stop animations that happen right before screenshot [fail] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for INfinite css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for css transitions [fail] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for finite css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should wait for fonts to load [fail] -page/page-screenshot.spec.ts › page screenshot should capture css transform [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should hide elements based on attr [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask in parallel [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask inside iframe [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask multiple elements [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should remove elements based on attr [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should remove mask after screenshot [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when mask color is not pink #F0F [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when subframe has stalled navigation [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when subframe used document.open after a weird url [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work with elementhandle [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work with locator [fail] -page/page-screenshot.spec.ts › page screenshot › path option should create subdirectories [fail] -page/page-screenshot.spec.ts › page screenshot › path option should detect jpeg [fail] -page/page-screenshot.spec.ts › page screenshot › path option should throw for unsupported mime type [pass] -page/page-screenshot.spec.ts › page screenshot › path option should work [fail] -page/page-screenshot.spec.ts › page screenshot › quality option should throw for png [pass] -page/page-screenshot.spec.ts › page screenshot › should allow transparency [fail] -page/page-screenshot.spec.ts › page screenshot › should capture blinking caret if explicitly asked for [fail] -page/page-screenshot.spec.ts › page screenshot › should capture blinking caret in shadow dom [fail] -page/page-screenshot.spec.ts › page screenshot › should capture canvas changes [fail] -page/page-screenshot.spec.ts › page screenshot › should clip elements to the viewport [fail] -page/page-screenshot.spec.ts › page screenshot › should clip rect [fail] -page/page-screenshot.spec.ts › page screenshot › should clip rect with fullPage [fail] -page/page-screenshot.spec.ts › page screenshot › should not capture blinking caret by default [fail] -page/page-screenshot.spec.ts › page screenshot › should not issue resize event [fail] -page/page-screenshot.spec.ts › page screenshot › should prefer type over extension [fail] -page/page-screenshot.spec.ts › page screenshot › should render white background on jpeg file [fail] -page/page-screenshot.spec.ts › page screenshot › should restore viewport after fullPage screenshot [fail] -page/page-screenshot.spec.ts › page screenshot › should run in parallel [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots and mask elements outside of it [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots during navigation [fail] -page/page-screenshot.spec.ts › page screenshot › should throw on clip outside the viewport [pass] -page/page-screenshot.spec.ts › page screenshot › should work @smoke [fail] -page/page-screenshot.spec.ts › page screenshot › should work for canvas [fail] -page/page-screenshot.spec.ts › page screenshot › should work for translateZ [fail] -page/page-screenshot.spec.ts › page screenshot › should work for webgl [fail] -page/page-screenshot.spec.ts › page screenshot › should work while navigating [fail] -page/page-screenshot.spec.ts › page screenshot › should work with Array deleted [fail] -page/page-screenshot.spec.ts › page screenshot › should work with iframe in shadow [fail] -page/page-screenshot.spec.ts › page screenshot › should work with odd clip size on Retina displays [fail] -page/page-screenshot.spec.ts › page screenshot › zero quality option should throw for png [pass] -page/page-screenshot.spec.ts › should capture css box-shadow [fail] -page/page-screenshot.spec.ts › should throw if screenshot size is too large [fail] -page/page-select-option.spec.ts › input event.composed should be true and cross shadow dom boundary [pass] -page/page-select-option.spec.ts › should deselect all options when passed no values for a multiple select [pass] -page/page-select-option.spec.ts › should deselect all options when passed no values for a select without multiple [pass] -page/page-select-option.spec.ts › should fall back to selecting by label [pass] -page/page-select-option.spec.ts › should not allow null items [pass] -page/page-select-option.spec.ts › should not select single option when some attributes do not match [pass] -page/page-select-option.spec.ts › should not throw when select causes navigation [pass] -page/page-select-option.spec.ts › should respect event bubbling [pass] -page/page-select-option.spec.ts › should return [] on no matched values [pass] -page/page-select-option.spec.ts › should return [] on no values [pass] -page/page-select-option.spec.ts › should return an array of matched values [pass] -page/page-select-option.spec.ts › should return an array of one element when multiple is not set [pass] -page/page-select-option.spec.ts › should select multiple options [pass] -page/page-select-option.spec.ts › should select multiple options with attributes [pass] -page/page-select-option.spec.ts › should select only first option [pass] -page/page-select-option.spec.ts › should select single option @smoke [pass] -page/page-select-option.spec.ts › should select single option by handle [pass] -page/page-select-option.spec.ts › should select single option by index [pass] -page/page-select-option.spec.ts › should select single option by label [pass] -page/page-select-option.spec.ts › should select single option by multiple attributes [pass] -page/page-select-option.spec.ts › should select single option by value [pass] -page/page-select-option.spec.ts › should throw if passed wrong types [pass] -page/page-select-option.spec.ts › should throw when element is not a [fail] -library/selector-generator.spec.ts › selector generator › should generate title selector [fail] -library/selector-generator.spec.ts › selector generator › should handle first non-unique data-testid [fail] -library/selector-generator.spec.ts › selector generator › should handle second non-unique data-testid [fail] -library/selector-generator.spec.ts › selector generator › should ignore empty aria-label for candidate consideration [fail] -library/selector-generator.spec.ts › selector generator › should ignore empty data-test-id for candidate consideration [fail] -library/selector-generator.spec.ts › selector generator › should ignore empty role for candidate consideration [fail] -library/selector-generator.spec.ts › selector generator › should match in deep shadow dom [fail] -library/selector-generator.spec.ts › selector generator › should match in shadow dom [fail] -library/selector-generator.spec.ts › selector generator › should not accept invalid role for candidate consideration [fail] -library/selector-generator.spec.ts › selector generator › should not escape spaces inside named attr selectors [fail] -library/selector-generator.spec.ts › selector generator › should not escape text with >> [fail] -library/selector-generator.spec.ts › selector generator › should not improve guid text [fail] -library/selector-generator.spec.ts › selector generator › should not prefer zero-sized button over inner span [fail] -library/selector-generator.spec.ts › selector generator › should not use generated id [fail] -library/selector-generator.spec.ts › selector generator › should not use input[value] [fail] -library/selector-generator.spec.ts › selector generator › should not use text for select [fail] -library/selector-generator.spec.ts › selector generator › should prefer button over inner span [fail] -library/selector-generator.spec.ts › selector generator › should prefer data-testid [fail] -library/selector-generator.spec.ts › selector generator › should prefer role other input[type] [fail] -library/selector-generator.spec.ts › selector generator › should prefer role=button over inner span [fail] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › name [fail] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › placeholder [fail] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › role [fail] -library/selector-generator.spec.ts › selector generator › should prioritise attributes correctly › type [fail] -library/selector-generator.spec.ts › selector generator › should properly join child selectors under nested ordinals [fail] -library/selector-generator.spec.ts › selector generator › should separate selectors by >> [fail] -library/selector-generator.spec.ts › selector generator › should trim long text [fail] -library/selector-generator.spec.ts › selector generator › should trim text [fail] -library/selector-generator.spec.ts › selector generator › should try to improve label text by shortening [fail] -library/selector-generator.spec.ts › selector generator › should try to improve role name [fail] -library/selector-generator.spec.ts › selector generator › should try to improve text [fail] -library/selector-generator.spec.ts › selector generator › should try to improve text by shortening [fail] -library/selector-generator.spec.ts › selector generator › should use data-testid in strict errors [pass] -library/selector-generator.spec.ts › selector generator › should use internal:has-text [pass] -library/selector-generator.spec.ts › selector generator › should use internal:has-text with regexp [fail] -library/selector-generator.spec.ts › selector generator › should use internal:has-text with regexp with a quote [fail] -library/selector-generator.spec.ts › selector generator › should use nested ordinals [fail] -library/selector-generator.spec.ts › selector generator › should use ordinal for identical nodes [fail] -library/selector-generator.spec.ts › selector generator › should use parent text [fail] -library/selector-generator.spec.ts › selector generator › should use readable id [fail] -library/selector-generator.spec.ts › selector generator › should use the name attributes for elements that can have it [fail] -library/selector-generator.spec.ts › selector generator › should work in dynamic iframes without navigation [pass] -library/selector-generator.spec.ts › selector generator › should work with tricky attributes [fail] -library/selector-generator.spec.ts › selector generator › should work without CSS.escape [fail] -library/selectors-register.spec.ts › should handle errors [pass] -library/selectors-register.spec.ts › should not rely on engines working from the root [pass] -library/selectors-register.spec.ts › should throw a nice error if the selector returns a bad value [pass] -library/selectors-register.spec.ts › should work [pass] -library/selectors-register.spec.ts › should work in main and isolated world [pass] -library/selectors-register.spec.ts › should work when registered on global [pass] -library/selectors-register.spec.ts › should work with path [pass] -library/shared-worker.spec.ts › should survive shared worker restart [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo check [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo click [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo press [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo selectOption [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo setInputFiles [fail] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo type [pass] -library/slowmo.spec.ts › slowMo › ElementHandle SlowMo uncheck [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo check [fail] -library/slowmo.spec.ts › slowMo › Frame SlowMo click [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo goto [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo press [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo selectOption [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo setInputFiles [fail] -library/slowmo.spec.ts › slowMo › Frame SlowMo type [pass] -library/slowmo.spec.ts › slowMo › Frame SlowMo uncheck [fail] -library/slowmo.spec.ts › slowMo › Page SlowMo check [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo click [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo dblclick [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo dispatchEvent [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo fill [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo focus [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo goto [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo hover [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo press [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo reload [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo selectOption [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo setInputFiles [fail] -library/slowmo.spec.ts › slowMo › Page SlowMo type [pass] -library/slowmo.spec.ts › slowMo › Page SlowMo uncheck [pass] -library/snapshotter.spec.ts › snapshots › empty adopted style sheets should not prevent node refs [pass] -library/snapshotter.spec.ts › snapshots › should capture frame [pass] -library/snapshotter.spec.ts › snapshots › should capture iframe [pass] -library/snapshotter.spec.ts › snapshots › should capture iframe with srcdoc [pass] -library/snapshotter.spec.ts › snapshots › should capture resources [pass] -library/snapshotter.spec.ts › snapshots › should capture snapshot target [fail] -library/snapshotter.spec.ts › snapshots › should collect multiple [pass] -library/snapshotter.spec.ts › snapshots › should collect on attribute change [pass] -library/snapshotter.spec.ts › snapshots › should collect snapshot [pass] -library/snapshotter.spec.ts › snapshots › should have a custom doctype [pass] -library/snapshotter.spec.ts › snapshots › should not navigate on anchor clicks [fail] -library/snapshotter.spec.ts › snapshots › should preserve BASE and other content on reset [pass] -library/snapshotter.spec.ts › snapshots › should replace meta charset attr that specifies charset [pass] -library/snapshotter.spec.ts › snapshots › should replace meta content attr that specifies charset [pass] -library/snapshotter.spec.ts › snapshots › should respect CSSOM change through CSSGroupingRule [pass] -library/snapshotter.spec.ts › snapshots › should respect attr removal [pass] -library/snapshotter.spec.ts › snapshots › should respect inline CSSOM change [pass] -library/snapshotter.spec.ts › snapshots › should respect node removal [pass] -library/snapshotter.spec.ts › snapshots › should respect subresource CSSOM change [pass] -library/tap.spec.ts › locators › should send all of the correct events [fail] -library/tap.spec.ts › should not send mouse events touchstart is canceled [fail] -library/tap.spec.ts › should not send mouse events when touchend is canceled [fail] -library/tap.spec.ts › should not wait for a navigation caused by a tap [fail] -library/tap.spec.ts › should send all of the correct events @smoke [fail] -library/tap.spec.ts › should send well formed touch points [fail] -library/tap.spec.ts › should wait until an element is visible to tap it [fail] -library/tap.spec.ts › should work with modifiers [fail] -library/tap.spec.ts › trial run should not tap [fail] -library/trace-viewer.spec.ts › should allow hiding route actions [unknown] -library/trace-viewer.spec.ts › should allow showing screenshots instead of snapshots [unknown] -library/trace-viewer.spec.ts › should capture data-url svg iframe [fail] -library/trace-viewer.spec.ts › should capture iframe with sandbox attribute [pass] -library/trace-viewer.spec.ts › should complain about newer version of trace in old viewer [pass] -library/trace-viewer.spec.ts › should contain action info [pass] -library/trace-viewer.spec.ts › should contain adopted style sheets [pass] -library/trace-viewer.spec.ts › should display language-specific locators [pass] -library/trace-viewer.spec.ts › should display waitForLoadState even if did not wait for it [pass] -library/trace-viewer.spec.ts › should filter network requests by resource type [pass] -library/trace-viewer.spec.ts › should filter network requests by url [pass] -library/trace-viewer.spec.ts › should follow redirects [fail] -library/trace-viewer.spec.ts › should handle case where neither snapshots nor screenshots exist [fail] -library/trace-viewer.spec.ts › should handle file URIs [fail] -library/trace-viewer.spec.ts › should handle multiple headers [fail] -library/trace-viewer.spec.ts › should handle src=blob [fail] -library/trace-viewer.spec.ts › should have correct snapshot size [fail] -library/trace-viewer.spec.ts › should have correct stack trace [unknown] -library/trace-viewer.spec.ts › should have network request overrides [fail] -library/trace-viewer.spec.ts › should have network request overrides 2 [unknown] -library/trace-viewer.spec.ts › should have network requests [unknown] -library/trace-viewer.spec.ts › should highlight expect failure [pass] -library/trace-viewer.spec.ts › should highlight locator in iframe while typing [fail] -library/trace-viewer.spec.ts › should highlight target element in shadow dom [pass] -library/trace-viewer.spec.ts › should highlight target elements [pass] -library/trace-viewer.spec.ts › should ignore 304 responses [fail] -library/trace-viewer.spec.ts › should include metainfo [pass] -library/trace-viewer.spec.ts › should include requestUrl in route.abort [unknown] -library/trace-viewer.spec.ts › should include requestUrl in route.continue [unknown] -library/trace-viewer.spec.ts › should include requestUrl in route.fulfill [unknown] -library/trace-viewer.spec.ts › should not crash with broken locator [pass] -library/trace-viewer.spec.ts › should not record route actions [pass] -library/trace-viewer.spec.ts › should open console errors on click [fail] -library/trace-viewer.spec.ts › should open simple trace viewer [pass] -library/trace-viewer.spec.ts › should open snapshot in new browser context [pass] -library/trace-viewer.spec.ts › should open trace viewer on specific host [unknown] -library/trace-viewer.spec.ts › should open trace-1.31 [pass] -library/trace-viewer.spec.ts › should open trace-1.37 [pass] -library/trace-viewer.spec.ts › should open two trace files [pass] -library/trace-viewer.spec.ts › should open two trace files of the same test [pass] -library/trace-viewer.spec.ts › should open two trace viewers [unknown] -library/trace-viewer.spec.ts › should pick locator [pass] -library/trace-viewer.spec.ts › should pick locator in iframe [fail] -library/trace-viewer.spec.ts › should popup snapshot [pass] -library/trace-viewer.spec.ts › should prefer later resource request with the same method [fail] -library/trace-viewer.spec.ts › should preserve currentSrc [fail] -library/trace-viewer.spec.ts › should preserve noscript when javascript is disabled [pass] -library/trace-viewer.spec.ts › should properly synchronize local and remote time [pass] -library/trace-viewer.spec.ts › should register custom elements [pass] -library/trace-viewer.spec.ts › should remove noscript by default [pass] -library/trace-viewer.spec.ts › should remove noscript when javaScriptEnabled is set to true [pass] -library/trace-viewer.spec.ts › should render console [fail] -library/trace-viewer.spec.ts › should render network bars [pass] -library/trace-viewer.spec.ts › should restore control values [fail] -library/trace-viewer.spec.ts › should restore scroll positions [pass] library/trace-viewer.spec.ts › should serve css without content-type [timeout] -library/trace-viewer.spec.ts › should serve overridden request [fail] -library/trace-viewer.spec.ts › should show action source [pass] -library/trace-viewer.spec.ts › should show baseURL in metadata pane [pass] -library/trace-viewer.spec.ts › should show correct request start time [fail] -library/trace-viewer.spec.ts › should show empty trace viewer [fail] -library/trace-viewer.spec.ts › should show font preview [fail] -library/trace-viewer.spec.ts › should show null as a param [unknown] -library/trace-viewer.spec.ts › should show only one pointer with multilevel iframes [pass] -library/trace-viewer.spec.ts › should show params and return value [unknown] -library/trace-viewer.spec.ts › should show similar actions from library-only trace [pass] -library/trace-viewer.spec.ts › should show snapshot URL [unknown] -library/trace-viewer.spec.ts › should update highlight when typing [pass] -library/trace-viewer.spec.ts › should work with adopted style sheets and all: unset [pass] -library/trace-viewer.spec.ts › should work with adopted style sheets and replace/replaceSync [pass] -library/trace-viewer.spec.ts › should work with meta CSP [fail] -library/trace-viewer.spec.ts › should work with nesting CSS selectors [pass] -library/tracing.spec.ts › should collect sources [fail] -library/tracing.spec.ts › should collect trace with resources, but no js [fail] -library/tracing.spec.ts › should collect two traces [pass] -library/tracing.spec.ts › should exclude internal pages [pass] -library/tracing.spec.ts › should export trace concurrently to second navigation [pass] -library/tracing.spec.ts › should flush console events on tracing stop [pass] -library/tracing.spec.ts › should hide internal stack frames [pass] -library/tracing.spec.ts › should hide internal stack frames in expect [pass] -library/tracing.spec.ts › should ignore iframes in head [pass] -library/tracing.spec.ts › should include context API requests [pass] -library/tracing.spec.ts › should include interrupted actions [pass] -library/tracing.spec.ts › should not collect snapshots by default [pass] -library/tracing.spec.ts › should not crash when browser closes mid-trace [pass] -library/tracing.spec.ts › should not emit after w/o before [pass] -library/tracing.spec.ts › should not flush console events [pass] -library/tracing.spec.ts › should not hang for clicks that open dialogs [pass] -library/tracing.spec.ts › should not include buffers in the trace [pass] -library/tracing.spec.ts › should not include trace resources from the previous chunks [fail] -library/tracing.spec.ts › should not stall on dialogs [pass] -library/tracing.spec.ts › should not throw when stopping without start but not exporting [pass] -library/tracing.spec.ts › should overwrite existing file [fail] -library/tracing.spec.ts › should produce screencast frames crop [fail] -library/tracing.spec.ts › should produce screencast frames fit [fail] -library/tracing.spec.ts › should produce screencast frames scale [fail] -library/tracing.spec.ts › should record global request trace [pass] -library/tracing.spec.ts › should record network failures [pass] -library/tracing.spec.ts › should respect tracesDir and name [fail] -library/tracing.spec.ts › should store global request traces separately [pass] -library/tracing.spec.ts › should store postData for global request [pass] -library/tracing.spec.ts › should survive browser.close with auto-created traces dir [pass] -library/tracing.spec.ts › should throw when starting with different options [pass] -library/tracing.spec.ts › should throw when stopping without start [pass] -library/tracing.spec.ts › should use the correct apiName for event driven callbacks [pass] -library/tracing.spec.ts › should work with multiple chunks [fail] -library/unroute-behavior.spec.ts › context.close should not wait for active route handlers on the owned pages [pass] library/unroute-behavior.spec.ts › context.unroute should not wait for pending handlers to complete [timeout] -library/unroute-behavior.spec.ts › context.unrouteAll removes all handlers [pass] library/unroute-behavior.spec.ts › context.unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors [timeout] library/unroute-behavior.spec.ts › context.unrouteAll should wait for pending handlers to complete [timeout] -library/unroute-behavior.spec.ts › page.close does not wait for active route handlers [pass] -library/unroute-behavior.spec.ts › page.close should not wait for active route handlers on the owning context [pass] -library/unroute-behavior.spec.ts › page.unroute should not wait for pending handlers to complete [pass] -library/unroute-behavior.spec.ts › page.unrouteAll removes all routes [pass] -library/unroute-behavior.spec.ts › page.unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors [pass] -library/unroute-behavior.spec.ts › page.unrouteAll should wait for pending handlers to complete [pass] -library/unroute-behavior.spec.ts › route.continue should not throw if page has been closed [pass] -library/unroute-behavior.spec.ts › route.fallback should not throw if page has been closed [pass] -library/unroute-behavior.spec.ts › route.fulfill should not throw if page has been closed [pass] library/video.spec.ts › screencast › saveAs should throw when no video frames [timeout] library/video.spec.ts › screencast › should be 800x450 by default [timeout] library/video.spec.ts › screencast › should be 800x600 with null viewport [timeout] library/video.spec.ts › screencast › should capture css transformation [timeout] -library/video.spec.ts › screencast › should capture full viewport [fail] -library/video.spec.ts › screencast › should capture full viewport on hidpi [fail] library/video.spec.ts › screencast › should capture navigation [timeout] library/video.spec.ts › screencast › should capture static page [timeout] -library/video.spec.ts › screencast › should capture static page in persistent context @smoke [fail] -library/video.spec.ts › screencast › should continue recording main page after popup closes [fail] library/video.spec.ts › screencast › should delete video [timeout] library/video.spec.ts › screencast › should emulate an iphone [timeout] -library/video.spec.ts › screencast › should expose video path [fail] library/video.spec.ts › screencast › should expose video path blank page [timeout] -library/video.spec.ts › screencast › should expose video path blank popup [fail] -library/video.spec.ts › screencast › should not create video for internal pages [unknown] library/video.spec.ts › screencast › should scale frames down to the requested size [timeout] -library/video.spec.ts › screencast › should throw if browser dies [fail] -library/video.spec.ts › screencast › should throw on browser close [fail] -library/video.spec.ts › screencast › should throw without recordVideo.dir [pass] library/video.spec.ts › screencast › should use viewport scaled down to fit into 800x800 as default size [timeout] -library/video.spec.ts › screencast › should wait for video to finish if page was closed [fail] -library/video.spec.ts › screencast › should work for popups [fail] library/video.spec.ts › screencast › should work with old options [timeout] library/video.spec.ts › screencast › should work with relative path for recordVideo.dir [timeout] library/video.spec.ts › screencast › should work with video+trace [timeout] library/video.spec.ts › screencast › should work with weird screen resolution [timeout] -library/video.spec.ts › screencast › videoSize should require videosPath [pass] library/video.spec.ts › should saveAs video [timeout] library/web-socket.spec.ts › should emit binary frame events [timeout] library/web-socket.spec.ts › should emit close events [timeout] @@ -1950,6 +132,4 @@ library/web-socket.spec.ts › should filter out the close events when the serve library/web-socket.spec.ts › should not have stray error events [timeout] library/web-socket.spec.ts › should pass self as argument to close event [timeout] library/web-socket.spec.ts › should reject waitForEvent on page close [timeout] -library/web-socket.spec.ts › should reject waitForEvent on socket close [timeout] -library/web-socket.spec.ts › should turn off when offline [unknown] -library/web-socket.spec.ts › should work @smoke [pass] \ No newline at end of file +library/web-socket.spec.ts › should reject waitForEvent on socket close [timeout] \ No newline at end of file diff --git a/tests/bidi/expectations/bidi-firefox-nightly-page.txt b/tests/bidi/expectations/bidi-firefox-nightly-page.txt index e9edb983e3..501b0dc46f 100644 --- a/tests/bidi/expectations/bidi-firefox-nightly-page.txt +++ b/tests/bidi/expectations/bidi-firefox-nightly-page.txt @@ -1,1965 +1,40 @@ -page/elementhandle-bounding-box.spec.ts › should force a layout [pass] -page/elementhandle-bounding-box.spec.ts › should get frame box [pass] -page/elementhandle-bounding-box.spec.ts › should handle nested frames [fail] -page/elementhandle-bounding-box.spec.ts › should handle scroll offset and click [pass] -page/elementhandle-bounding-box.spec.ts › should return null for invisible elements [fail] -page/elementhandle-bounding-box.spec.ts › should work [fail] -page/elementhandle-bounding-box.spec.ts › should work when inline box child is outside of viewport [pass] -page/elementhandle-bounding-box.spec.ts › should work with SVG nodes [pass] -page/elementhandle-click.spec.ts › should double click the button [fail] -page/elementhandle-click.spec.ts › should throw for
elements with force [pass] -page/elementhandle-click.spec.ts › should throw for detached nodes [pass] -page/elementhandle-click.spec.ts › should throw for hidden nodes with force [pass] -page/elementhandle-click.spec.ts › should throw for recursively hidden nodes with force [pass] -page/elementhandle-click.spec.ts › should work @smoke [pass] -page/elementhandle-click.spec.ts › should work for Shadow DOM v1 [pass] -page/elementhandle-click.spec.ts › should work for TextNodes [fail] -page/elementhandle-click.spec.ts › should work with Node removed [pass] -page/elementhandle-content-frame.spec.ts › should return null for document.documentElement [pass] -page/elementhandle-content-frame.spec.ts › should return null for non-iframes [pass] -page/elementhandle-content-frame.spec.ts › should work [pass] -page/elementhandle-content-frame.spec.ts › should work for cross-frame evaluations [fail] -page/elementhandle-content-frame.spec.ts › should work for cross-process iframes [pass] -page/elementhandle-convenience.spec.ts › getAttribute should work [pass] -page/elementhandle-convenience.spec.ts › innerHTML should work [pass] -page/elementhandle-convenience.spec.ts › innerText should throw [pass] -page/elementhandle-convenience.spec.ts › innerText should work [pass] -page/elementhandle-convenience.spec.ts › inputValue should work [pass] -page/elementhandle-convenience.spec.ts › isChecked should work [pass] -page/elementhandle-convenience.spec.ts › isEditable should work [pass] -page/elementhandle-convenience.spec.ts › isEnabled and isDisabled should work [pass] -page/elementhandle-convenience.spec.ts › isEnabled and isDisabled should work with [fail] -page/page-fill.spec.ts › should throw on incorrect color value [pass] -page/page-fill.spec.ts › should throw on incorrect date [pass] -page/page-fill.spec.ts › should throw on incorrect datetime-local [unknown] -page/page-fill.spec.ts › should throw on incorrect month [unknown] -page/page-fill.spec.ts › should throw on incorrect range value [pass] -page/page-fill.spec.ts › should throw on incorrect time [pass] -page/page-fill.spec.ts › should throw on incorrect week [unknown] -page/page-fill.spec.ts › should throw on unsupported inputs [pass] -page/page-focus.spec.ts › clicking checkbox should activate it [unknown] -page/page-focus.spec.ts › keeps focus on element when attempting to focus a non-focusable element [pass] -page/page-focus.spec.ts › should emit blur event [pass] -page/page-focus.spec.ts › should emit focus event [pass] -page/page-focus.spec.ts › should traverse focus [pass] -page/page-focus.spec.ts › should traverse focus in all directions [pass] -page/page-focus.spec.ts › should traverse only form elements [unknown] -page/page-focus.spec.ts › should work @smoke [pass] -page/page-focus.spec.ts › tab should cycle between document elements and browser [unknown] -page/page-focus.spec.ts › tab should cycle between single input and browser [unknown] -page/page-force-gc.spec.ts › should work [fail] -page/page-goto.spec.ts › js redirect overrides url bar navigation [pass] -page/page-goto.spec.ts › should be able to navigate to a page controlled by service worker [pass] -page/page-goto.spec.ts › should capture cross-process iframe navigation request [pass] -page/page-goto.spec.ts › should capture iframe navigation request [pass] -page/page-goto.spec.ts › should disable timeout when its set to 0 [pass] -page/page-goto.spec.ts › should fail when canceled by another navigation [pass] -page/page-goto.spec.ts › should fail when exceeding browser context navigation timeout [pass] -page/page-goto.spec.ts › should fail when exceeding browser context timeout [pass] -page/page-goto.spec.ts › should fail when exceeding default maximum navigation timeout [pass] -page/page-goto.spec.ts › should fail when exceeding default maximum timeout [pass] -page/page-goto.spec.ts › should fail when exceeding maximum navigation timeout [pass] -page/page-goto.spec.ts › should fail when main resources failed to load [pass] -page/page-goto.spec.ts › should fail when navigating and show the url at the error message [pass] -page/page-goto.spec.ts › should fail when navigating to bad SSL [fail] -page/page-goto.spec.ts › should fail when navigating to bad SSL after redirects [fail] -page/page-goto.spec.ts › should fail when navigating to bad url [fail] -page/page-goto.spec.ts › should fail when replaced by another navigation [pass] page/page-goto.spec.ts › should fail when server returns 204 [timeout] -page/page-goto.spec.ts › should navigate to URL with hash and fire requests without hash [pass] -page/page-goto.spec.ts › should navigate to about:blank [pass] -page/page-goto.spec.ts › should navigate to dataURL and not fire dataURL requests [pass] -page/page-goto.spec.ts › should navigate to empty page with domcontentloaded [pass] -page/page-goto.spec.ts › should not crash when RTCPeerConnection is used [pass] -page/page-goto.spec.ts › should not crash when navigating to bad SSL after a cross origin navigation [pass] -page/page-goto.spec.ts › should not leak listeners during 20 waitForNavigation [pass] -page/page-goto.spec.ts › should not leak listeners during bad navigation [pass] -page/page-goto.spec.ts › should not leak listeners during navigation [pass] -page/page-goto.spec.ts › should not resolve goto upon window.stop() [pass] -page/page-goto.spec.ts › should not throw if networkidle0 is passed as an option [pass] -page/page-goto.spec.ts › should not throw unhandled rejections on invalid url [pass] -page/page-goto.spec.ts › should override referrer-policy [fail] -page/page-goto.spec.ts › should prioritize default navigation timeout over default timeout [pass] -page/page-goto.spec.ts › should properly wait for load [pass] -page/page-goto.spec.ts › should reject referer option when setExtraHTTPHeaders provides referer [pass] -page/page-goto.spec.ts › should report raw buffer for main resource [fail] -page/page-goto.spec.ts › should return from goto if new navigation is started [pass] -page/page-goto.spec.ts › should return last response in redirect chain [pass] -page/page-goto.spec.ts › should return response when page changes its URL after load [pass] -page/page-goto.spec.ts › should return url with basic auth info [pass] -page/page-goto.spec.ts › should return when navigation is committed if commit is specified [pass] -page/page-goto.spec.ts › should send referer [fail] -page/page-goto.spec.ts › should send referer of cross-origin URL [fail] -page/page-goto.spec.ts › should succeed on url bar navigation when there is pending navigation [pass] -page/page-goto.spec.ts › should throw if networkidle2 is passed as an option [pass] -page/page-goto.spec.ts › should use http for no protocol [pass] -page/page-goto.spec.ts › should wait for load when iframe attaches and detaches [pass] -page/page-goto.spec.ts › should work @smoke [pass] -page/page-goto.spec.ts › should work cross-process [pass] -page/page-goto.spec.ts › should work when navigating to 404 [pass] -page/page-goto.spec.ts › should work when navigating to data url [pass] -page/page-goto.spec.ts › should work when navigating to valid url [pass] -page/page-goto.spec.ts › should work when page calls history API in beforeunload [fail] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy [pass] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy after redirect [pass] -page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy and interception [pass] page/page-goto.spec.ts › should work with anchor navigation [timeout] -page/page-goto.spec.ts › should work with cross-process that fails before committing [pass] -page/page-goto.spec.ts › should work with file URL [pass] -page/page-goto.spec.ts › should work with file URL with subframes [pass] -page/page-goto.spec.ts › should work with lazy loading iframes [pass] -page/page-goto.spec.ts › should work with redirects [pass] -page/page-goto.spec.ts › should work with self requesting page [pass] -page/page-goto.spec.ts › should work with subframes return 204 [pass] -page/page-goto.spec.ts › should work with subframes return 204 with domcontentloaded [pass] -page/page-history.spec.ts › goBack/goForward should work with bfcache-able pages [fail] -page/page-history.spec.ts › page.goBack during renderer-initiated navigation [fail] -page/page-history.spec.ts › page.goBack should work @smoke [pass] -page/page-history.spec.ts › page.goBack should work for file urls [fail] -page/page-history.spec.ts › page.goBack should work with HistoryAPI [fail] -page/page-history.spec.ts › page.goForward during renderer-initiated navigation [pass] -page/page-history.spec.ts › page.reload during renderer-initiated navigation [pass] -page/page-history.spec.ts › page.reload should not resolve with same-document navigation [fail] -page/page-history.spec.ts › page.reload should work [pass] -page/page-history.spec.ts › page.reload should work on a page with a hash [pass] -page/page-history.spec.ts › page.reload should work on a page with a hash at the end [pass] -page/page-history.spec.ts › page.reload should work with cross-origin redirect [pass] -page/page-history.spec.ts › page.reload should work with data url [pass] -page/page-history.spec.ts › page.reload should work with same origin redirect [pass] -page/page-history.spec.ts › regression test for issue 20791 [pass] page/page-history.spec.ts › should reload proper page [timeout] -page/page-keyboard.spec.ts › insertText should only emit input event [fail] -page/page-keyboard.spec.ts › pressing Meta should not result in any text insertion on any platform [pass] -page/page-keyboard.spec.ts › should be able to prevent selectAll [pass] -page/page-keyboard.spec.ts › should dispatch a click event on a button when Enter gets pressed [pass] -page/page-keyboard.spec.ts › should dispatch a click event on a button when Space gets pressed [pass] -page/page-keyboard.spec.ts › should dispatch insertText after context menu was opened [pass] -page/page-keyboard.spec.ts › should expose keyIdentifier in webkit [unknown] -page/page-keyboard.spec.ts › should handle selectAll [pass] -page/page-keyboard.spec.ts › should have correct Keydown/Keyup order when pressing Escape key [pass] -page/page-keyboard.spec.ts › should move around the selection in a contenteditable [pass] -page/page-keyboard.spec.ts › should move to the start of the document [unknown] -page/page-keyboard.spec.ts › should move with the arrow keys [pass] -page/page-keyboard.spec.ts › should not type canceled events [pass] -page/page-keyboard.spec.ts › should press Enter [fail] -page/page-keyboard.spec.ts › should press plus [fail] -page/page-keyboard.spec.ts › should press shift plus [fail] -page/page-keyboard.spec.ts › should press the meta key [pass] -page/page-keyboard.spec.ts › should report multiple modifiers [fail] -page/page-keyboard.spec.ts › should report shiftKey [pass] -page/page-keyboard.spec.ts › should scroll with PageDown [pass] -page/page-keyboard.spec.ts › should send a character with ElementHandle.press [pass] -page/page-keyboard.spec.ts › should send a character with insertText [fail] -page/page-keyboard.spec.ts › should send proper codes while typing [pass] -page/page-keyboard.spec.ts › should send proper codes while typing with shift [pass] -page/page-keyboard.spec.ts › should shift raw codes [pass] -page/page-keyboard.spec.ts › should specify location [fail] -page/page-keyboard.spec.ts › should specify repeat property [pass] -page/page-keyboard.spec.ts › should support MacOS shortcuts [unknown] -page/page-keyboard.spec.ts › should support multiple plus-separated modifiers [pass] -page/page-keyboard.spec.ts › should support plus-separated modifiers [pass] -page/page-keyboard.spec.ts › should support simple copy-pasting [pass] -page/page-keyboard.spec.ts › should support simple cut-pasting [pass] -page/page-keyboard.spec.ts › should support undo-redo [pass] -page/page-keyboard.spec.ts › should throw on unknown keys [pass] -page/page-keyboard.spec.ts › should type after context menu was opened [pass] -page/page-keyboard.spec.ts › should type all kinds of characters [pass] -page/page-keyboard.spec.ts › should type emoji [pass] -page/page-keyboard.spec.ts › should type emoji into an iframe [pass] -page/page-keyboard.spec.ts › should type into a textarea @smoke [pass] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom [pass] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom with nested elements [pass] -page/page-keyboard.spec.ts › should type repeatedly in input in shadow dom [pass] -page/page-keyboard.spec.ts › should work after a cross origin navigation [pass] -page/page-keyboard.spec.ts › should work with keyboard events with empty.html [pass] -page/page-keyboard.spec.ts › type to non-focusable element should maintain old focus [pass] -page/page-leaks.spec.ts › click should not leak [fail] -page/page-leaks.spec.ts › expect should not leak [fail] -page/page-leaks.spec.ts › fill should not leak [fail] -page/page-leaks.spec.ts › waitFor should not leak [fail] -page/page-listeners.spec.ts › should not throw with ignoreErrors [pass] -page/page-listeners.spec.ts › should wait [pass] -page/page-listeners.spec.ts › wait should throw [pass] -page/page-mouse.spec.ts › down and up should generate click [pass] -page/page-mouse.spec.ts › should always round down [fail] -page/page-mouse.spec.ts › should click the document @smoke [pass] -page/page-mouse.spec.ts › should dblclick the div [fail] -page/page-mouse.spec.ts › should dispatch mouse move after context menu was opened [pass] -page/page-mouse.spec.ts › should not crash on mouse drag with any button [pass] -page/page-mouse.spec.ts › should pointerdown the div with a custom button [fail] -page/page-mouse.spec.ts › should report correct buttons property [pass] -page/page-mouse.spec.ts › should select the text with mouse [pass] -page/page-mouse.spec.ts › should set modifier keys on click [pass] -page/page-mouse.spec.ts › should trigger hover state [pass] -page/page-mouse.spec.ts › should trigger hover state on disabled button [pass] -page/page-mouse.spec.ts › should trigger hover state with removed window.Node [pass] -page/page-mouse.spec.ts › should tween mouse movement [pass] -page/page-navigation.spec.ts › should work with _blank target [pass] -page/page-navigation.spec.ts › should work with _blank target in form [pass] -page/page-navigation.spec.ts › should work with cross-process _blank target [pass] -page/page-network-idle.spec.ts › should navigate to empty page with networkidle [pass] -page/page-network-idle.spec.ts › should wait for networkidle from the child frame [pass] -page/page-network-idle.spec.ts › should wait for networkidle from the popup [fail] -page/page-network-idle.spec.ts › should wait for networkidle in setContent [pass] -page/page-network-idle.spec.ts › should wait for networkidle in setContent from the child frame [pass] -page/page-network-idle.spec.ts › should wait for networkidle in setContent with request from previous navigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle in waitForNavigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle to succeed navigation [pass] -page/page-network-idle.spec.ts › should wait for networkidle to succeed navigation with request from previous navigation [fail] -page/page-network-idle.spec.ts › should wait for networkidle when iframe attaches and detaches [pass] -page/page-network-idle.spec.ts › should wait for networkidle when navigating iframe [pass] -page/page-network-idle.spec.ts › should work after repeated navigations in the same page [pass] -page/page-network-request.spec.ts › page.reload return 304 status code [pass] -page/page-network-request.spec.ts › should get the same headers as the server [fail] -page/page-network-request.spec.ts › should get the same headers as the server CORS [fail] -page/page-network-request.spec.ts › should get |undefined| with postData() when there is no post data [pass] -page/page-network-request.spec.ts › should get |undefined| with postDataJSON() when there is no post data [pass] -page/page-network-request.spec.ts › should handle mixed-content blocked requests [unknown] -page/page-network-request.spec.ts › should not allow to access frame on popup main request [pass] -page/page-network-request.spec.ts › should not get preflight CORS requests when intercepting [fail] -page/page-network-request.spec.ts › should not return allHeaders() until they are available [fail] -page/page-network-request.spec.ts › should not work for a redirect and interception [pass] -page/page-network-request.spec.ts › should override post data content type [pass] -page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded [fail] -page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded; charset=UTF-8 [fail] -page/page-network-request.spec.ts › should parse the json post data [fail] -page/page-network-request.spec.ts › should report all cookies in one header [pass] -page/page-network-request.spec.ts › should report raw headers [fail] -page/page-network-request.spec.ts › should report raw response headers in redirects [pass] -page/page-network-request.spec.ts › should return event source [fail] -page/page-network-request.spec.ts › should return headers [pass] -page/page-network-request.spec.ts › should return multipart/form-data [fail] -page/page-network-request.spec.ts › should return navigation bit [pass] -page/page-network-request.spec.ts › should return navigation bit when navigating to image [pass] -page/page-network-request.spec.ts › should return postData [fail] -page/page-network-request.spec.ts › should work for a redirect [pass] -page/page-network-request.spec.ts › should work for fetch requests @smoke [pass] -page/page-network-request.spec.ts › should work for main frame navigation request [pass] -page/page-network-request.spec.ts › should work for subframe navigation request [pass] -page/page-network-request.spec.ts › should work with binary post data [fail] -page/page-network-request.spec.ts › should work with binary post data and interception [fail] -page/page-network-response.spec.ts › should behave the same way for headers and allHeaders [pass] -page/page-network-response.spec.ts › should bypass disk cache when context interception is enabled [fail] -page/page-network-response.spec.ts › should bypass disk cache when page interception is enabled [pass] -page/page-network-response.spec.ts › should provide a Response with a file URL [fail] page/page-network-response.spec.ts › should reject response.finished if context closes [timeout] -page/page-network-response.spec.ts › should reject response.finished if page closes [pass] -page/page-network-response.spec.ts › should report all headers [fail] -page/page-network-response.spec.ts › should report if request was fromServiceWorker [fail] -page/page-network-response.spec.ts › should report multiple set-cookie headers [fail] -page/page-network-response.spec.ts › should return body [fail] -page/page-network-response.spec.ts › should return body for prefetch script [fail] -page/page-network-response.spec.ts › should return body with compression [fail] -page/page-network-response.spec.ts › should return headers after route.fulfill [pass] -page/page-network-response.spec.ts › should return json [fail] -page/page-network-response.spec.ts › should return multiple header value [pass] -page/page-network-response.spec.ts › should return set-cookie header after route.fulfill [pass] -page/page-network-response.spec.ts › should return status text [pass] -page/page-network-response.spec.ts › should return text [fail] -page/page-network-response.spec.ts › should return uncompressed text [fail] -page/page-network-response.spec.ts › should throw when requesting body of redirected response [pass] -page/page-network-response.spec.ts › should wait until response completes [fail] -page/page-network-response.spec.ts › should work @smoke [pass] -page/page-network-sizes.spec.ts › should handle redirects [pass] -page/page-network-sizes.spec.ts › should have correct responseBodySize for 404 with content [pass] -page/page-network-sizes.spec.ts › should have the correct responseBodySize [pass] -page/page-network-sizes.spec.ts › should have the correct responseBodySize for chunked request [fail] -page/page-network-sizes.spec.ts › should have the correct responseBodySize with gzip compression [pass] -page/page-network-sizes.spec.ts › should return sizes without hanging [pass] -page/page-network-sizes.spec.ts › should set bodySize and headersSize [pass] -page/page-network-sizes.spec.ts › should set bodySize to 0 if there was no body [pass] -page/page-network-sizes.spec.ts › should set bodySize to 0 when there was no response body [pass] -page/page-network-sizes.spec.ts › should set bodySize, headersSize, and transferSize [pass] -page/page-network-sizes.spec.ts › should throw for failed requests [pass] -page/page-network-sizes.spec.ts › should work with 200 status code [pass] -page/page-network-sizes.spec.ts › should work with 401 status code [pass] -page/page-network-sizes.spec.ts › should work with 404 status code [pass] -page/page-network-sizes.spec.ts › should work with 500 status code [pass] -page/page-object-count.spec.ts › should count objects [unknown] -page/page-request-continue.spec.ts › continue should delete headers on redirects [fail] -page/page-request-continue.spec.ts › continue should not change multipart/form-data body [pass] -page/page-request-continue.spec.ts › continue should propagate headers to redirects [fail] -page/page-request-continue.spec.ts › post data › should amend binary post data [pass] -page/page-request-continue.spec.ts › post data › should amend longer post data [pass] -page/page-request-continue.spec.ts › post data › should amend method and post data [pass] -page/page-request-continue.spec.ts › post data › should amend post data [pass] -page/page-request-continue.spec.ts › post data › should amend utf8 post data [pass] -page/page-request-continue.spec.ts › post data › should compute content-length from post data [pass] -page/page-request-continue.spec.ts › post data › should use content-type from original request [pass] -page/page-request-continue.spec.ts › redirected requests should report overridden headers [fail] -page/page-request-continue.spec.ts › should amend HTTP headers [pass] -page/page-request-continue.spec.ts › should amend method [pass] -page/page-request-continue.spec.ts › should amend method on main request [pass] -page/page-request-continue.spec.ts › should continue preload link requests [pass] -page/page-request-continue.spec.ts › should delete header with undefined value [pass] -page/page-request-continue.spec.ts › should delete the origin header [pass] -page/page-request-continue.spec.ts › should intercept css variable with background url [pass] -page/page-request-continue.spec.ts › should not allow changing protocol when overriding url [pass] page/page-request-continue.spec.ts › should not throw if request was cancelled by the page [timeout] -page/page-request-continue.spec.ts › should not throw when continuing after page is closed [pass] -page/page-request-continue.spec.ts › should not throw when continuing while page is closing [fail] page/page-request-continue.spec.ts › should override method along with url [timeout] page/page-request-continue.spec.ts › should override request url [timeout] -page/page-request-continue.spec.ts › should work [pass] -page/page-request-continue.spec.ts › should work with Cross-Origin-Opener-Policy [pass] -page/page-request-fallback.spec.ts › post data › should amend binary post data [pass] -page/page-request-fallback.spec.ts › post data › should amend json post data [pass] -page/page-request-fallback.spec.ts › post data › should amend post data [pass] -page/page-request-fallback.spec.ts › should amend HTTP headers [pass] -page/page-request-fallback.spec.ts › should amend method [fail] -page/page-request-fallback.spec.ts › should chain once [fail] -page/page-request-fallback.spec.ts › should delete header with undefined value [pass] -page/page-request-fallback.spec.ts › should fall back [pass] -page/page-request-fallback.spec.ts › should fall back after exception [pass] -page/page-request-fallback.spec.ts › should fall back async [pass] -page/page-request-fallback.spec.ts › should not chain abort [pass] -page/page-request-fallback.spec.ts › should not chain fulfill [fail] -page/page-request-fallback.spec.ts › should override request url [fail] -page/page-request-fallback.spec.ts › should work [pass] -page/page-request-fulfill.spec.ts › headerValue should return set-cookie from intercepted response [pass] -page/page-request-fulfill.spec.ts › should allow mocking binary responses [fail] -page/page-request-fulfill.spec.ts › should allow mocking svg with charset [fail] -page/page-request-fulfill.spec.ts › should fetch original request and fulfill [pass] -page/page-request-fulfill.spec.ts › should fulfill json [pass] -page/page-request-fulfill.spec.ts › should fulfill preload link requests [pass] -page/page-request-fulfill.spec.ts › should fulfill with fetch response that has multiple set-cookie [pass] -page/page-request-fulfill.spec.ts › should fulfill with fetch result [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch result and overrides [fail] -page/page-request-fulfill.spec.ts › should fulfill with global fetch result [fail] -page/page-request-fulfill.spec.ts › should fulfill with gzip and readback [fail] -page/page-request-fulfill.spec.ts › should fulfill with har response [pass] -page/page-request-fulfill.spec.ts › should fulfill with multiple set-cookie [pass] -page/page-request-fulfill.spec.ts › should fulfill with unuassigned status codes [pass] -page/page-request-fulfill.spec.ts › should include the origin header [pass] -page/page-request-fulfill.spec.ts › should not go to the network for fulfilled requests body [fail] -page/page-request-fulfill.spec.ts › should not modify the headers sent to the server [fail] -page/page-request-fulfill.spec.ts › should not throw if request was cancelled by the page [fail] -page/page-request-fulfill.spec.ts › should stringify intercepted request response headers [pass] -page/page-request-fulfill.spec.ts › should work [pass] -page/page-request-fulfill.spec.ts › should work with buffer as body [pass] -page/page-request-fulfill.spec.ts › should work with file path [pass] -page/page-request-fulfill.spec.ts › should work with status code 422 [pass] -page/page-request-gc.spec.ts › should work [fail] -page/page-request-intercept.spec.ts › request.postData is not null when fetching FormData with a Blob [fail] -page/page-request-intercept.spec.ts › should fulfill intercepted response [pass] -page/page-request-intercept.spec.ts › should fulfill intercepted response using alias [pass] -page/page-request-intercept.spec.ts › should fulfill popup main request using alias [pass] -page/page-request-intercept.spec.ts › should fulfill response with empty body [fail] -page/page-request-intercept.spec.ts › should fulfill with any response [fail] -page/page-request-intercept.spec.ts › should give access to the intercepted response [pass] -page/page-request-intercept.spec.ts › should give access to the intercepted response body [pass] -page/page-request-intercept.spec.ts › should intercept multipart/form-data request body [unknown] -page/page-request-intercept.spec.ts › should intercept with post data override [pass] -page/page-request-intercept.spec.ts › should intercept with url override [fail] -page/page-request-intercept.spec.ts › should not follow redirects when maxRedirects is set to 0 in route.fetch [fail] -page/page-request-intercept.spec.ts › should override with defaults when intercepted response not provided [fail] -page/page-request-intercept.spec.ts › should support fulfill after intercept [fail] -page/page-request-intercept.spec.ts › should support timeout option in route.fetch [pass] -page/page-route.spec.ts › route.abort should throw if called twice [pass] -page/page-route.spec.ts › route.continue should throw if called twice [pass] -page/page-route.spec.ts › route.fallback should throw if called twice [pass] -page/page-route.spec.ts › route.fulfill should throw if called twice [pass] -page/page-route.spec.ts › should add Access-Control-Allow-Origin by default when fulfill [fail] -page/page-route.spec.ts › should allow null origin for about:blank [fail] -page/page-route.spec.ts › should be able to fetch dataURL and not fire dataURL requests [fail] -page/page-route.spec.ts › should be able to remove headers [pass] -page/page-route.spec.ts › should be abortable [pass] -page/page-route.spec.ts › should be abortable with custom error codes [fail] -page/page-route.spec.ts › should chain fallback w/ dynamic URL [fail] -page/page-route.spec.ts › should contain raw request header [pass] -page/page-route.spec.ts › should contain raw response header [pass] -page/page-route.spec.ts › should contain raw response header after fulfill [pass] -page/page-route.spec.ts › should contain referer header [pass] -page/page-route.spec.ts › should fail navigation when aborting main resource [fail] -page/page-route.spec.ts › should fulfill with redirect status [pass] -page/page-route.spec.ts › should intercept @smoke [fail] -page/page-route.spec.ts › should intercept main resource during cross-process navigation [pass] -page/page-route.spec.ts › should intercept when postData is more than 1MB [fail] -page/page-route.spec.ts › should navigate to URL with hash and and fire requests without hash [pass] -page/page-route.spec.ts › should navigate to dataURL and not fire dataURL requests [pass] -page/page-route.spec.ts › should not auto-intercept non-preflight OPTIONS [fail] -page/page-route.spec.ts › should not fulfill with redirect status [fail] -page/page-route.spec.ts › should not throw "Invalid Interception Id" if the request was cancelled [fail] page/page-route.spec.ts › should not throw if request was cancelled by the page [timeout] -page/page-route.spec.ts › should not work with redirects [fail] -page/page-route.spec.ts › should override cookie header [pass] -page/page-route.spec.ts › should pause intercepted XHR until continue [pass] -page/page-route.spec.ts › should pause intercepted fetch request until continue [pass] -page/page-route.spec.ts › should properly return navigation response when URL has cookies [pass] -page/page-route.spec.ts › should reject cors with disallowed credentials [fail] -page/page-route.spec.ts › should respect cors overrides [fail] -page/page-route.spec.ts › should send referer [fail] -page/page-route.spec.ts › should show custom HTTP headers [fail] -page/page-route.spec.ts › should support ? in glob pattern [pass] -page/page-route.spec.ts › should support async handler w/ times [pass] -page/page-route.spec.ts › should support cors for different methods [fail] -page/page-route.spec.ts › should support cors with GET [pass] -page/page-route.spec.ts › should support cors with POST [fail] -page/page-route.spec.ts › should support cors with credentials [fail] -page/page-route.spec.ts › should support the times parameter with route matching [pass] -page/page-route.spec.ts › should unroute [pass] -page/page-route.spec.ts › should work if handler with times parameter was removed from another handler [pass] -page/page-route.spec.ts › should work when POST is redirected with 302 [pass] -page/page-route.spec.ts › should work when header manipulation headers with redirect [pass] -page/page-route.spec.ts › should work with badly encoded server [pass] -page/page-route.spec.ts › should work with custom referer headers [fail] -page/page-route.spec.ts › should work with encoded server [pass] -page/page-route.spec.ts › should work with encoded server - 2 [fail] -page/page-route.spec.ts › should work with equal requests [pass] -page/page-route.spec.ts › should work with redirect inside sync XHR [pass] -page/page-route.spec.ts › should work with redirects for subresources [fail] -page/page-screenshot.spec.ts › page screenshot animations › should capture screenshots after layoutchanges in transitionend event [pass] -page/page-screenshot.spec.ts › page screenshot animations › should fire transitionend for finite transitions [pass] -page/page-screenshot.spec.ts › page screenshot animations › should not capture css animations in shadow DOM [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite css animation [pass] -page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite web animations [pass] -page/page-screenshot.spec.ts › page screenshot animations › should not capture pseudo element css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not change animation with playbackRate equal to 0 [pass] -page/page-screenshot.spec.ts › page screenshot animations › should resume infinite animations [pass] -page/page-screenshot.spec.ts › page screenshot animations › should stop animations that happen right before screenshot [pass] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for INfinite css animation [pass] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for css transitions [pass] -page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for finite css animation [pass] -page/page-screenshot.spec.ts › page screenshot animations › should wait for fonts to load [fail] -page/page-screenshot.spec.ts › page screenshot should capture css transform [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should hide elements based on attr [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask in parallel [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask inside iframe [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should mask multiple elements [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should remove elements based on attr [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should remove mask after screenshot [pass] -page/page-screenshot.spec.ts › page screenshot › mask option › should work [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when mask color is not pink #F0F [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when subframe has stalled navigation [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work when subframe used document.open after a weird url [pass] -page/page-screenshot.spec.ts › page screenshot › mask option › should work with elementhandle [fail] -page/page-screenshot.spec.ts › page screenshot › mask option › should work with locator [fail] -page/page-screenshot.spec.ts › page screenshot › path option should create subdirectories [pass] -page/page-screenshot.spec.ts › page screenshot › path option should detect jpeg [fail] -page/page-screenshot.spec.ts › page screenshot › path option should throw for unsupported mime type [pass] -page/page-screenshot.spec.ts › page screenshot › path option should work [pass] -page/page-screenshot.spec.ts › page screenshot › quality option should throw for png [pass] -page/page-screenshot.spec.ts › page screenshot › should allow transparency [fail] -page/page-screenshot.spec.ts › page screenshot › should capture blinking caret if explicitly asked for [fail] -page/page-screenshot.spec.ts › page screenshot › should capture blinking caret in shadow dom [pass] -page/page-screenshot.spec.ts › page screenshot › should capture canvas changes [fail] -page/page-screenshot.spec.ts › page screenshot › should clip elements to the viewport [fail] -page/page-screenshot.spec.ts › page screenshot › should clip rect [fail] -page/page-screenshot.spec.ts › page screenshot › should clip rect with fullPage [fail] -page/page-screenshot.spec.ts › page screenshot › should not capture blinking caret by default [pass] -page/page-screenshot.spec.ts › page screenshot › should not issue resize event [pass] -page/page-screenshot.spec.ts › page screenshot › should prefer type over extension [fail] -page/page-screenshot.spec.ts › page screenshot › should render white background on jpeg file [fail] -page/page-screenshot.spec.ts › page screenshot › should restore viewport after fullPage screenshot [fail] -page/page-screenshot.spec.ts › page screenshot › should run in parallel [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots and mask elements outside of it [fail] -page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots during navigation [pass] -page/page-screenshot.spec.ts › page screenshot › should throw on clip outside the viewport [pass] -page/page-screenshot.spec.ts › page screenshot › should work @smoke [fail] -page/page-screenshot.spec.ts › page screenshot › should work for canvas [fail] -page/page-screenshot.spec.ts › page screenshot › should work for translateZ [fail] -page/page-screenshot.spec.ts › page screenshot › should work for webgl [fail] -page/page-screenshot.spec.ts › page screenshot › should work while navigating [fail] -page/page-screenshot.spec.ts › page screenshot › should work with Array deleted [fail] -page/page-screenshot.spec.ts › page screenshot › should work with iframe in shadow [fail] -page/page-screenshot.spec.ts › page screenshot › should work with odd clip size on Retina displays [fail] -page/page-screenshot.spec.ts › page screenshot › zero quality option should throw for png [pass] -page/page-screenshot.spec.ts › should capture css box-shadow [fail] -page/page-screenshot.spec.ts › should throw if screenshot size is too large [pass] -page/page-select-option.spec.ts › input event.composed should be true and cross shadow dom boundary [pass] -page/page-select-option.spec.ts › should deselect all options when passed no values for a multiple select [pass] -page/page-select-option.spec.ts › should deselect all options when passed no values for a select without multiple [pass] -page/page-select-option.spec.ts › should fall back to selecting by label [pass] -page/page-select-option.spec.ts › should not allow null items [pass] -page/page-select-option.spec.ts › should not select single option when some attributes do not match [pass] -page/page-select-option.spec.ts › should not throw when select causes navigation [pass] -page/page-select-option.spec.ts › should respect event bubbling [pass] -page/page-select-option.spec.ts › should return [] on no matched values [pass] -page/page-select-option.spec.ts › should return [] on no values [pass] -page/page-select-option.spec.ts › should return an array of matched values [pass] -page/page-select-option.spec.ts › should return an array of one element when multiple is not set [pass] -page/page-select-option.spec.ts › should select multiple options [pass] -page/page-select-option.spec.ts › should select multiple options with attributes [pass] -page/page-select-option.spec.ts › should select only first option [pass] -page/page-select-option.spec.ts › should select single option @smoke [pass] -page/page-select-option.spec.ts › should select single option by handle [pass] -page/page-select-option.spec.ts › should select single option by index [pass] -page/page-select-option.spec.ts › should select single option by label [pass] -page/page-select-option.spec.ts › should select single option by multiple attributes [pass] -page/page-select-option.spec.ts › should select single option by value [pass] -page/page-select-option.spec.ts › should throw if passed wrong types [pass] -page/page-select-option.spec.ts › should throw when element is not a `); @@ -573,6 +574,8 @@ it('should escape special yaml values', async ({ page }) => { - text: "123" - link "-1.2" - text: "-1.2" + - link "-" + - text: "-" - textbox: "555" `); }); From 7923d35e3283eef4b06d4f7abb323124466f5c99 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Jan 2025 11:15:00 -0800 Subject: [PATCH 51/83] fix(retarget): do not unconditionally retarget to enclosing label (#34229) --- .../playwright-core/src/server/injected/injectedScript.ts | 6 +++--- tests/page/retarget.spec.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index c3d6e296e5..4042ab5b49 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -531,10 +531,10 @@ export class InjectedScript { if (!element.matches('a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]') && !(element as any).isContentEditable) { // Go up to the label that might be connected to the input/textarea. - element = element.closest('label') || element; + const enclosingLabel: HTMLLabelElement | null = element.closest('label'); + if (enclosingLabel && enclosingLabel.control) + element = enclosingLabel.control; } - if (element.nodeName === 'LABEL') - element = (element as HTMLLabelElement).control || element; } return element; } diff --git a/tests/page/retarget.spec.ts b/tests/page/retarget.spec.ts index 94e0f440e7..6c09d3b907 100644 --- a/tests/page/retarget.spec.ts +++ b/tests/page/retarget.spec.ts @@ -109,6 +109,7 @@ it('enabled/disabled retargeting', async ({ page, asset }) => { { dom: domInButton(``), enabled: true, locator: 'input' }, { dom: domInLink(``), enabled: true, locator: 'input' }, { dom: domInButton(``, { disabled: true }), enabled: true, locator: 'input' }, + { dom: domInLabel(``), enabled: true, locator: 'li' }, { dom: domInLabel(``), enabled: false, locator: 'label' }, { dom: domLabelFor(``), enabled: false, locator: 'label' }, @@ -116,6 +117,7 @@ it('enabled/disabled retargeting', async ({ page, asset }) => { { dom: domInButton(``), enabled: false, locator: 'input' }, { dom: domInLink(``), enabled: false, locator: 'input' }, { dom: domInButton(``, { disabled: true }), enabled: false, locator: 'input' }, + { dom: domInLabel(``), enabled: false, locator: 'li' }, ]; for (const { dom, enabled, locator } of cases) { await it.step(`"${locator}" in "${dom}" should be enabled=${enabled}`, async () => { From 0008816ee3650fbc5a792584a3b247dee577a4fc Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 7 Jan 2025 11:49:14 -0800 Subject: [PATCH 52/83] test: reenable "return empty content there is no iframe src" in cr and ff (#34241) --- tests/page/page-set-content.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/page/page-set-content.spec.ts b/tests/page/page-set-content.spec.ts index 4f0c903bfc..27fe18c860 100644 --- a/tests/page/page-set-content.spec.ts +++ b/tests/page/page-set-content.spec.ts @@ -123,8 +123,8 @@ it('content() should throw nice error during navigation', async ({ page, server } }); -it('should return empty content there is no iframe src', async ({ page }) => { - it.fixme(true, 'Hangs in all browsers because there is no utility context'); +it('should return empty content there is no iframe src', async ({ page, browserName }) => { + it.fixme(browserName === 'webkit', 'Hangs in all browsers because there is no utility context'); await page.setContent(``); expect(page.frames().length).toBe(2); expect(await page.frames()[1].content()).toBe(''); From 0d34369fc0ca32342c15c0543c19ba36e8a43858 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Jan 2025 15:31:18 -0800 Subject: [PATCH 53/83] chore: include actual value in the elementState (#34245) --- packages/playwright-core/src/server/dom.ts | 10 +- packages/playwright-core/src/server/frames.ts | 28 +---- .../src/server/injected/injectedScript.ts | 119 +++++++++++------- packages/playwright/src/matchers/matchers.ts | 27 ++-- .../playwright/src/matchers/toBeTruthy.ts | 6 +- tests/page/expect-matcher-result.spec.ts | 2 +- 6 files changed, 101 insertions(+), 91 deletions(-) diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 8e65c7c67f..962f385c90 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -778,7 +778,9 @@ export class ElementHandle extends js.JSHandle { async _setChecked(progress: Progress, state: boolean, options: { position?: types.Point } & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { const isChecked = async () => { const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {}); - return throwRetargetableDOMError(result); + if (result === 'error:notconnected' || result.received === 'error:notconnected') + throwElementIsNotAttached(); + return result.matches; }; await this._markAsTargetElement(progress.metadata); if (await isChecked() === state) @@ -913,10 +915,14 @@ export class ElementHandle extends js.JSHandle { export function throwRetargetableDOMError(result: T | 'error:notconnected'): T { if (result === 'error:notconnected') - throw new Error('Element is not attached to the DOM'); + throwElementIsNotAttached(); return result; } +export function throwElementIsNotAttached(): never { + throw new Error('Element is not attached to the DOM'); +} + export function assertDone(result: 'done'): void { // This function converts 'done' to void and ensures typescript catches unhandled errors. } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index ad793aded8..1d2098b92c 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1301,7 +1301,9 @@ export class Frame extends SdkObject { const result = await this._callOnElementOnceMatches(metadata, selector, (injected, element, data) => { return injected.elementState(element, data.state); }, { state }, options, scope); - return dom.throwRetargetableDOMError(result); + if (result.received === 'error:notconnected') + dom.throwElementIsNotAttached(); + return result.matches; } async isVisible(metadata: CallMetadata, selector: string, options: types.StrictOptions = {}, scope?: dom.ElementHandle): Promise { @@ -1319,8 +1321,8 @@ export class Frame extends SdkObject { return false; return await resolved.injected.evaluate((injected, { info, root }) => { const element = injected.querySelector(info.parsed, root || document, info.strict); - const state = element ? injected.elementState(element, 'visible') : false; - return state === 'error:notconnected' ? false : state; + const state = element ? injected.elementState(element, 'visible') : { matches: false, received: 'error:notconnected' }; + return state.matches; }, { info: resolved.info, root: resolved.frame === this ? scope : undefined }); } catch (e) { if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e) || isSessionClosedError(e)) @@ -1809,26 +1811,6 @@ function verifyLifecycle(name: string, waitUntil: types.LifecycleEvent): types.L } function renderUnexpectedValue(expression: string, received: any): string { - if (expression === 'to.be.checked') - return received ? 'checked' : 'unchecked'; - if (expression === 'to.be.unchecked') - return received ? 'unchecked' : 'checked'; - if (expression === 'to.be.visible') - return received ? 'visible' : 'hidden'; - if (expression === 'to.be.hidden') - return received ? 'hidden' : 'visible'; - if (expression === 'to.be.enabled') - return received ? 'enabled' : 'disabled'; - if (expression === 'to.be.disabled') - return received ? 'disabled' : 'enabled'; - if (expression === 'to.be.editable') - return received ? 'editable' : 'readonly'; - if (expression === 'to.be.readonly') - return received ? 'readonly' : 'editable'; - if (expression === 'to.be.empty') - return received ? 'empty' : 'not empty'; - if (expression === 'to.be.focused') - return received ? 'focused' : 'not focused'; if (expression === 'to.match.aria') return received ? received.raw : received; return received; diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 4042ab5b49..0acc9bc76c 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -41,8 +41,9 @@ import { parseYamlTemplate } from '@isomorphic/ariaSnapshot'; export type FrameExpectParams = Omit & { expectedValue?: any }; -export type ElementStateWithoutStable = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked' | 'unchecked'; -export type ElementState = ElementStateWithoutStable | 'stable'; +export type ElementState = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked' | 'unchecked' | 'mixed' | 'stable'; +export type ElementStateWithoutStable = Exclude; +export type ElementStateQueryResult = { matches: boolean, received?: string | 'error:notconnected' }; export type HitTargetInterceptionResult = { stop: () => 'done' | { hitTargetDescription: string }; @@ -545,15 +546,15 @@ export class InjectedScript { if (stableResult === false) return { missingState: 'stable' }; if (stableResult === 'error:notconnected') - return stableResult; + return 'error:notconnected'; } for (const state of states) { if (state !== 'stable') { const result = this.elementState(node, state); - if (result === false) + if (result.received === 'error:notconnected') + return 'error:notconnected'; + if (!result.matches) return { missingState: state }; - if (result === 'error:notconnected') - return result; } } } @@ -608,38 +609,50 @@ export class InjectedScript { return result; } - elementState(node: Node, state: ElementStateWithoutStable): boolean | 'error:notconnected' { - const element = this.retarget(node, ['stable', 'visible', 'hidden'].includes(state) ? 'none' : 'follow-label'); + elementState(node: Node, state: ElementStateWithoutStable): ElementStateQueryResult { + const element = this.retarget(node, ['visible', 'hidden'].includes(state) ? 'none' : 'follow-label'); if (!element || !element.isConnected) { if (state === 'hidden') - return true; - return 'error:notconnected'; + return { matches: true, received: 'hidden' }; + return { matches: false, received: 'error:notconnected' }; } - if (state === 'visible') - return isElementVisible(element); - if (state === 'hidden') - return !isElementVisible(element); + if (state === 'visible' || state === 'hidden') { + const visible = isElementVisible(element); + return { + matches: state === 'visible' ? visible : !visible, + received: visible ? 'visible' : 'hidden' + }; + } - const disabled = getAriaDisabled(element); - if (state === 'disabled') - return disabled; - if (state === 'enabled') - return !disabled; + if (state === 'disabled' || state === 'enabled') { + const disabled = getAriaDisabled(element); + return { + matches: state === 'disabled' ? disabled : !disabled, + received: disabled ? 'disabled' : 'enabled' + }; + } if (state === 'editable') { + const disabled = getAriaDisabled(element); const readonly = getReadonly(element); if (readonly === 'error') throw this.createStacklessError('Element is not an ,