From e20b6d1617354191c635ece258b346290a25ce03 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 20 Jan 2025 16:28:41 +0000 Subject: [PATCH 01/37] feat(chromium-tip-of-tree): roll to r1295 (#34372) --- packages/playwright-core/browsers.json | 4 ++-- .../playwright-core/src/server/chromium/chromiumSwitches.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 0895f005b1..54d42fe041 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1293", + "revision": "1295", "installByDefault": false, - "browserVersion": "133.0.6943.0" + "browserVersion": "134.0.6960.0" }, { "name": "firefox", diff --git a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts index 4774c13ed7..9d64d58f1c 100644 --- a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts +++ b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts @@ -38,7 +38,10 @@ export const chromiumSwitches = [ // ThirdPartyStoragePartitioning - https://github.com/microsoft/playwright/issues/32230 // LensOverlay - Hides the Lens feature in the URL address bar. Its not working in unofficial builds. // PlzDedicatedWorker - https://github.com/microsoft/playwright/issues/31747 - '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding,ThirdPartyStoragePartitioning,LensOverlay,PlzDedicatedWorker', + // DeferRendererTasksAfterInput - this makes Page.frameScheduledNavigation arrive much later after a click, + // making our navigation auto-wait after click not working. Can be removed once we deperecate noWaitAfter. + // See https://github.com/microsoft/playwright/pull/34372. + '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding,ThirdPartyStoragePartitioning,LensOverlay,PlzDedicatedWorker,DeferRendererTasksAfterInput', '--allow-pre-commit-input', '--disable-hang-monitor', '--disable-ipc-flooding-protection', From 333e994e7dccd079f4a01fbb3c189a05a04ae182 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 21 Jan 2025 18:28:41 +0000 Subject: [PATCH 02/37] fix(step.skip): show a skipped indicator in UI mode (#34407) --- packages/playwright/src/worker/testInfo.ts | 4 +-- packages/playwright/src/worker/testTracing.ts | 4 +-- packages/trace-viewer/src/ui/actionList.css | 4 +++ packages/trace-viewer/src/ui/actionList.tsx | 9 +++-- tests/playwright-test/ui-mode-trace.spec.ts | 33 +++++++++++++++++-- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 3eba797853..6e6ab4660c 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -322,7 +322,7 @@ export class TestInfoImpl implements TestInfo { location: data.location, }; this._onStepBegin(payload); - this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, data.apiName || data.title, data.params, data.location ? [data.location] : []); + this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, data.category, data.apiName || data.title, data.params, data.location ? [data.location] : []); return step; } @@ -421,7 +421,7 @@ export class TestInfoImpl implements TestInfo { } 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.appendBeforeActionForStep(callId, this._findLastStageStep(this._steps)?.stepId, 'attach', `attach "${attachment.name}"`, undefined, []); this._tracing.appendAfterActionForStep(callId, undefined, [attachment]); } diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index eb0ce9d807..fde5ea5f3f 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -245,14 +245,14 @@ export class TestTracing { }); } - appendBeforeActionForStep(callId: string, parentId: string | undefined, apiName: string, params: Record | undefined, stack: StackFrame[]) { + appendBeforeActionForStep(callId: string, parentId: string | undefined, category: string, apiName: string, params: Record | undefined, stack: StackFrame[]) { this._appendTraceEvent({ type: 'before', callId, parentId, startTime: monotonicTime(), class: 'Test', - method: 'step', + method: category, apiName, params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])), stack, diff --git a/packages/trace-viewer/src/ui/actionList.css b/packages/trace-viewer/src/ui/actionList.css index 0cb3cb5f54..f167352593 100644 --- a/packages/trace-viewer/src/ui/actionList.css +++ b/packages/trace-viewer/src/ui/actionList.css @@ -41,6 +41,10 @@ color: var(--vscode-editorCodeLens-foreground); } +.action-skipped { + margin-right: 4px; +} + .action-icon { flex: none; display: flex; diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 87e78416b0..2c6932bd45 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -15,7 +15,7 @@ */ import type { ActionTraceEvent, AfterActionTraceEventAttachment } from '@trace/trace'; -import { msToString } from '@web/uiUtils'; +import { clsx, msToString } from '@web/uiUtils'; import * as React from 'react'; import './actionList.css'; import * as modelUtil from './modelUtil'; @@ -25,6 +25,7 @@ import { TreeView } from '@web/components/treeView'; import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil'; import type { Boundaries } from './geometry'; import { ToolbarButton } from '@web/components/toolbarButton'; +import { testStatusIcon } from './testUtils'; export interface ActionListProps { actions: ActionTraceEventInContext[], @@ -119,6 +120,7 @@ export const renderAction = ( const parameterString = actionParameterDisplayString(action, sdkLanguage || 'javascript'); + const isSkipped = action.class === 'Test' && action.method === 'test.step.skip'; let time: string = ''; if (action.endTime) time = msToString(action.endTime - action.startTime); @@ -149,9 +151,10 @@ export const renderAction = ( {action.method === 'goto' && action.params.url &&
{action.params.url}
} {action.class === 'APIRequestContext' && action.params.url &&
{excludeOrigin(action.params.url)}
} - {(showDuration || showBadges || showAttachments) &&
} + {(showDuration || showBadges || showAttachments || isSkipped) &&
} {showAttachments && revealAttachment(action.attachments![0])} />} - {showDuration &&
{time || }
} + {showDuration && !isSkipped &&
{time || }
} + {isSkipped && } {showBadges &&
revealConsole?.()}> {!!errors &&
{errors}
} {!!warnings &&
{warnings}
} diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index 23a321338b..8001623721 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -398,7 +398,7 @@ test('should show custom fixture titles in actions tree', async ({ runUITest }) const { page } = await runUITest({ 'a.test.ts': ` import { test as base, expect } from '@playwright/test'; - + const test = base.extend({ fixture1: [async ({}, use) => { await use(); @@ -457,7 +457,7 @@ test('attachments tab shows all but top-level .push attachments', async ({ runUI - tree: - treeitem /step/: - group: - - treeitem /attach \\"foo-attach\\"/ + - treeitem /attach \\"foo-attach\\"/ - treeitem /attach \\"bar-push\\"/ - treeitem /attach \\"bar-attach\\"/ `); @@ -470,3 +470,32 @@ test('attachments tab shows all but top-level .push attachments', async ({ runUI - button /bar-attach/ `); }); + +test('skipped steps should have an indicator', async ({ runUITest }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('test with steps', async ({}) => { + await test.step('outer', async () => { + await test.step.skip('skipped1', () => {}); + }); + await test.step.skip('skipped2', () => {}); + }); + `, + }); + + await page.getByRole('treeitem', { name: 'test with steps' }).dblclick(); + const actionsTree = page.getByTestId('actions-tree'); + await actionsTree.getByRole('treeitem', { name: 'outer' }).click(); + await page.keyboard.press('ArrowRight'); + await expect(actionsTree).toMatchAriaSnapshot(` + - tree: + - treeitem /outer/ [expanded]: + - group: + - treeitem /skipped1/ + - treeitem /skipped2/ + `); + const skippedMarker = actionsTree.getByRole('treeitem', { name: 'skipped1' }).locator('.action-skipped'); + await expect(skippedMarker).toBeVisible(); + await expect(skippedMarker).toHaveAccessibleName('skipped'); +}); From 888feb06bea8116e0eb2364d49901f75ea118f3c Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 21 Jan 2025 18:30:09 +0000 Subject: [PATCH 03/37] fix(list reporter): do not break after output without trailing eol (#34410) --- packages/playwright/src/reporters/list.ts | 3 ++ packages/playwright/src/worker/workerMain.ts | 8 +++- .../playwright-test-fixtures.ts | 1 + tests/playwright-test/reporter-list.spec.ts | 45 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index 4f885946e5..aef9fcbe2f 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -129,6 +129,8 @@ class ListReporter extends TerminalReporter { if (this._needNewLine) { this._needNewLine = false; process.stdout.write('\n'); + ++this._lastRow; + this._lastColumn = 0; } } @@ -210,6 +212,7 @@ class ListReporter extends TerminalReporter { process.stdout.write('\n'); } ++this._lastRow; + this._lastColumn = 0; } private _updateLine(row: number, text: string, prefix: string) { diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index ed323a701b..b4878ff659 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -75,16 +75,20 @@ export class WorkerMain extends ProcessRunner { process.on('unhandledRejection', reason => this.unhandledError(reason)); process.on('uncaughtException', error => this.unhandledError(error)); - process.stdout.write = (chunk: string | Buffer) => { + process.stdout.write = (chunk: string | Buffer, cb?: any) => { this.dispatchEvent('stdOut', stdioChunkToParams(chunk)); this._currentTest?._tracing.appendStdioToTrace('stdout', chunk); + if (typeof cb === 'function') + process.nextTick(cb); return true; }; if (!process.env.PW_RUNNER_DEBUG) { - process.stderr.write = (chunk: string | Buffer) => { + process.stderr.write = (chunk: string | Buffer, cb?: any) => { this.dispatchEvent('stdErr', stdioChunkToParams(chunk)); this._currentTest?._tracing.appendStdioToTrace('stderr', chunk); + if (typeof cb === 'function') + process.nextTick(cb); return true; }; } diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts index 23c26a3e3c..5d6aab5b8e 100644 --- a/tests/playwright-test/playwright-test-fixtures.ts +++ b/tests/playwright-test/playwright-test-fixtures.ts @@ -229,6 +229,7 @@ export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { // END: Reserved CI PW_TEST_HTML_REPORT_OPEN: undefined, PLAYWRIGHT_HTML_OPEN: undefined, + PW_TEST_DEBUG_REPORTERS: undefined, PW_TEST_REPORTER: undefined, PW_TEST_REPORTER_WS_ENDPOINT: undefined, PW_TEST_SOURCE_TRANSFORM: undefined, diff --git a/tests/playwright-test/reporter-list.spec.ts b/tests/playwright-test/reporter-list.spec.ts index 1d488cc253..8e6128065a 100644 --- a/tests/playwright-test/reporter-list.spec.ts +++ b/tests/playwright-test/reporter-list.spec.ts @@ -258,6 +258,51 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(text).toContain('1) a.test.ts:3:15 › passes › outer 1.0 › inner 1.1 ──'); expect(result.exitCode).toBe(1); }); + + test('print stdio', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('passes', async ({}) => { + await new Promise(resolve => process.stdout.write('line1', () => resolve())); + await new Promise(resolve => process.stdout.write('line2\\n', () => resolve())); + await new Promise(resolve => process.stderr.write(Buffer.from(''), () => resolve())); + }); + + test('passes 2', async ({}) => { + await new Promise(resolve => process.stdout.write('partial', () => resolve())); + }); + + test('passes 3', async ({}) => { + await new Promise(resolve => process.stdout.write('full\\n', () => resolve())); + }); + + test('passes 4', async ({}) => { + }); + `, + }, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(4); + const expected = [ + '#0 : 1 a.test.ts:3:15 › passes', + 'line1line2', + `#0 : ${POSITIVE_STATUS_MARK} 1 a.test.ts:3:15 › passes`, + '', + '#3 : 2 a.test.ts:9:15 › passes 2', + `partial#3 : ${POSITIVE_STATUS_MARK} 2 a.test.ts:9:15 › passes 2`, + '', + '#5 : 3 a.test.ts:13:15 › passes 3', + 'full', + `#5 : ${POSITIVE_STATUS_MARK} 3 a.test.ts:13:15 › passes 3`, + '#7 : 4 a.test.ts:17:15 › passes 4', + `#7 : ${POSITIVE_STATUS_MARK} 4 a.test.ts:17:15 › passes 4`, + ]; + const lines = result.output.split('\n'); + const firstIndex = lines.indexOf(expected[0]); + expect(firstIndex, 'first line should be there').not.toBe(-1); + for (let i = 0; i < expected.length; ++i) + expect(lines[firstIndex + i]).toContain(expected[i]); + }); }); } From 6234c3b15e1147a87f29555596a58881b4e3ed9b Mon Sep 17 00:00:00 2001 From: Tasawar Hussain <31658686+tasawar-hussain@users.noreply.github.com> Date: Wed, 22 Jan 2025 02:50:33 +0500 Subject: [PATCH 04/37] docs: update test-fixtures-js.md (#34399) --- 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 9bdd4391ad..1f806d06ab 100644 --- a/docs/src/test-fixtures-js.md +++ b/docs/src/test-fixtures-js.md @@ -407,7 +407,7 @@ Automatic fixtures are set up for each test/worker, even when the test does not Here is an example fixture that automatically attaches debug logs when the test fails, so we can later review the logs in the reporter. Note how it uses [TestInfo] object that is available in each test/fixture to retrieve metadata about the test being run. ```js title="my-test.ts" -import * as debug from 'debug'; +import debug from 'debug'; import * as fs from 'fs'; import { test as base } from '@playwright/test'; From cf3bcd7d4a32c81e7e96ccfd948877bfe927573a Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:21:47 -0800 Subject: [PATCH 05/37] feat(chromium-tip-of-tree): roll to r1296 (#34414) 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 54d42fe041..384b2f3ea6 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1295", + "revision": "1296", "installByDefault": false, - "browserVersion": "134.0.6960.0" + "browserVersion": "134.0.6970.0" }, { "name": "firefox", From cf90c0f122578141e8c7a961133c2c652a0285db Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 22 Jan 2025 07:53:53 +0000 Subject: [PATCH 06/37] fix(aria snapshot): make rebase work when options are specified (#34409) --- .../src/matchers/toMatchAriaSnapshot.ts | 2 +- packages/playwright/src/runner/rebase.ts | 17 ++++--- .../update-aria-snapshot.spec.ts | 44 +++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index c291770fd5..d379d8610c 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -134,7 +134,7 @@ export async function toMatchAriaSnapshot( } return { pass: true, message: () => '', name: 'toMatchAriaSnapshot' }; } else { - const suggestedRebaseline = `toMatchAriaSnapshot(\`\n${escapeTemplateString(indent(typedReceived.regex, '{indent} '))}\n{indent}\`)`; + const suggestedRebaseline = `\`\n${escapeTemplateString(indent(typedReceived.regex, '{indent} '))}\n{indent}\``; return { pass: false, message: () => '', name: 'toMatchAriaSnapshot', suggestedRebaseline }; } } diff --git a/packages/playwright/src/runner/rebase.ts b/packages/playwright/src/runner/rebase.ts index de18df465b..196944fd88 100644 --- a/packages/playwright/src/runner/rebase.ts +++ b/packages/playwright/src/runner/rebase.ts @@ -68,24 +68,27 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo traverse(fileNode, { CallExpression: path => { const node = path.node; - if (node.arguments.length !== 1) + if (node.arguments.length < 1) return; if (!t.isMemberExpression(node.callee)) return; const argument = node.arguments[0]; if (!t.isStringLiteral(argument) && !t.isTemplateLiteral(argument)) return; - - const matcher = node.callee.property; + const prop = node.callee.property; + if (!prop.loc || !argument.start || !argument.end) + return; + // Replacements are anchored by the location of the call expression. + // However, replacement text is meant to only replace the first argument. for (const replacement of replacements) { // In Babel, rows are 1-based, columns are 0-based. - if (matcher.loc!.start.line !== replacement.location.line) + if (prop.loc.start.line !== replacement.location.line) continue; - if (matcher.loc!.start.column + 1 !== replacement.location.column) + if (prop.loc.start.column + 1 !== replacement.location.column) continue; - const indent = lines[matcher.loc!.start.line - 1].match(/^\s*/)![0]; + const indent = lines[prop.loc.start.line - 1].match(/^\s*/)![0]; const newText = replacement.code.replace(/\{indent\}/g, indent); - ranges.push({ start: matcher.start!, end: node.end!, oldText: source.substring(matcher.start!, node.end!), newText }); + ranges.push({ start: argument.start, end: argument.end, oldText: source.substring(argument.start, argument.end), newText }); // We can have multiple, hopefully equal, replacements for the same location, // for example when a single test runs multiple times because of projects or retries. // Do not apply multiple replacements for the same assertion. diff --git a/tests/playwright-test/update-aria-snapshot.spec.ts b/tests/playwright-test/update-aria-snapshot.spec.ts index 15afff31e2..0052d3d3d9 100644 --- a/tests/playwright-test/update-aria-snapshot.spec.ts +++ b/tests/playwright-test/update-aria-snapshot.spec.ts @@ -454,6 +454,50 @@ test('should generate baseline for input values', async ({ runInlineTest }, test expect(result2.exitCode).toBe(0); }); +test('should update when options are specified', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + '.git/marker': '', + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); + await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { timeout: 2500 }); + await expect(page.locator('body')).toMatchAriaSnapshot('', + { + timeout: 2500 + }); + }); + ` + }); + + expect(result.exitCode).toBe(0); + const patchPath = testInfo.outputPath('test-results/rebaselines.patch'); + const data = fs.readFileSync(patchPath, 'utf-8'); + expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts +--- a/a.spec.ts ++++ b/a.spec.ts +@@ -2,8 +2,12 @@ + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); +- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { timeout: 2500 }); +- await expect(page.locator('body')).toMatchAriaSnapshot('', ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - textbox: hello world ++ \`, { timeout: 2500 }); ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - textbox: hello world ++ \`, + { + timeout: 2500 + }); +`); + + execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() }); + const result2 = await runInlineTest({}); + expect(result2.exitCode).toBe(0); +}); + test('should not update snapshots when locator did not match', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ '.git/marker': '', From 214b103b462c6777c29155e07cd0b2778c0746b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:52:00 +0300 Subject: [PATCH 07/37] chore(deps-dev): bump undici from 5.28.4 to 5.28.5 (#34419) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c242d4bb9..67b1fe085d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6931,10 +6931,11 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, From a689e534ac402c5efb53c38fd659da2abb64ddd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:23:05 +0100 Subject: [PATCH 08/37] chore(deps): bump vite from 5.4.6 to 5.4.14 (#34420) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 11 ++++++----- package.json | 2 +- packages/playwright-ct-core/package.json | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67b1fe085d..d7eae56cba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "react-dom": "^18.1.0", "ssim.js": "^3.5.0", "typescript": "^5.7.2", - "vite": "^5.4.6", + "vite": "^5.4.14", "ws": "^8.17.1", "xml2js": "^0.5.0", "yaml": "^2.6.0" @@ -7028,9 +7028,10 @@ } }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -7834,7 +7835,7 @@ "dependencies": { "playwright": "1.51.0-next", "playwright-core": "1.51.0-next", - "vite": "^5.2.8" + "vite": "^5.4.14" }, "engines": { "node": ">=18" diff --git a/package.json b/package.json index 76a2b426f7..0bde8c158c 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "react-dom": "^18.1.0", "ssim.js": "^3.5.0", "typescript": "^5.7.2", - "vite": "^5.4.6", + "vite": "^5.4.14", "ws": "^8.17.1", "xml2js": "^0.5.0", "yaml": "^2.6.0" diff --git a/packages/playwright-ct-core/package.json b/packages/playwright-ct-core/package.json index e9376d1963..d97a0f56f2 100644 --- a/packages/playwright-ct-core/package.json +++ b/packages/playwright-ct-core/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "playwright-core": "1.51.0-next", - "vite": "^5.2.8", + "vite": "^5.4.14", "playwright": "1.51.0-next" } } From 49b3bbb9209de304082f67f56e2b1a950fbcbde0 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 22 Jan 2025 06:17:54 -0800 Subject: [PATCH 09/37] docs: release notes for v1.50 js (#34380) --- docs/src/release-notes-js.md | 68 ++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 9d26d41551..d3760a7112 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -6,6 +6,74 @@ toc_max_heading_level: 2 import LiteYouTube from '@site/src/components/LiteYouTube'; +## Version 1.50 + +### Test runner + +* New option [`option: Test.step.timeout`] allows specifying a maximum run time for an individual test step. A timed-out step will fail the execution of the test. + + ```js + test('some test', async ({ page }) => { + await test.step('a step', async () => { + // This step can time out separately from the test + }, { timeout: 1000 }); + }); + ``` + +* New method [`method: Test.step.skip`] to disable execution of a test step. + + ```js + test('some test', async ({ page }) => { + await test.step('before running step', async () => { + // Normal step + }); + + await test.step.skip('not yet ready', async () => { + // This step is skipped + }); + + await test.step('after running step', async () => { + // This step still runs even though the previous one was skipped + }); + }); + ``` + +* Expanded [`method: LocatorAssertions.toMatchAriaSnapshot#2`] to allow storing of aria snapshots in separate YAML files. +* Added method [`method: LocatorAssertions.toHaveAccessibleErrorMessage`] to assert the Locator points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage). +* Option [`property: TestConfig.updateSnapshots`] added the configuration enum `changed`. `changed` updates only the snapshots that have changed, whereas `all` now updates all snapshots, regardless of whether there are any differences. +* New option [`property: TestConfig.updateSourceMethod`] defines the way source code is updated when [`property: TestConfig.updateSnapshots`] is configured. Added `overwrite` and `3-way` modes that write the changes into source code, on top of existing `patch` mode that creates a patch file. + + ```bash + npx playwright test --update-snapshots=changed --update-source-method=3way + ``` + +* Option [`property: TestConfig.webServer`] added a `gracefulShutdown` field for specifying a process kill signal other than the default `SIGKILL`. +* Exposed [`property: TestStep.attachments`] from the reporter API to allow retrieval of all attachments created by that step. + +### UI updates + +* Updated default HTML reporter to improve display of attachments. +* New button for picking elements to produce aria snapshots. +* Additional details (such as keys pressed) are now displayed alongside action API calls in traces. +* Display of `canvas` content in traces is error-prone. Display is now disabled by default, and can be enabled via the `Display canvas content` UI setting. +* `Call` and `Network` panels now display additional time information. + +### Breaking + +* [`method: LocatorAssertions.toBeEditable`] and [`method: Locator.isEditable`] now throw if the target element is not ``, ``); + await page.check('input'); + expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true); }); it('should not check the checked box', async ({ page }) => { From f65dc0cee48ea97ed02c7441634b793c3a9e698e Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 24 Jan 2025 06:00:17 -0800 Subject: [PATCH 25/37] feat: toHaveURL predicate matcher (#34413) --- docs/src/api/class-pageassertions.md | 9 +- .../src/server/injected/injectedScript.ts | 2 - packages/playwright/src/matchers/matchers.ts | 15 +- packages/playwright/src/matchers/toHaveURL.ts | 153 ++++++++++++++++++ packages/playwright/types/test.d.ts | 10 +- tests/page/expect-misc.spec.ts | 42 ++++- tests/playwright-test/expect.spec.ts | 22 ++- 7 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 packages/playwright/src/matchers/toHaveURL.ts diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index 8eefd41f02..38a05b9df8 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -323,17 +323,18 @@ expect(page).to_have_url(re.compile(".*checkout")) await Expect(Page).ToHaveURLAsync(new Regex(".*checkout")); ``` -### param: PageAssertions.toHaveURL.urlOrRegExp +### param: PageAssertions.toHaveURL.url * since: v1.18 -- `urlOrRegExp` <[string]|[RegExp]> +- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> -Expected URL string or RegExp. +Expected URL string, RegExp, or predicate receiving [URL] to match. +When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. ### option: PageAssertions.toHaveURL.ignoreCase * since: v1.44 - `ignoreCase` <[boolean]> -Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified. +Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression parameter if specified. A provided predicate ignores this flag. ### option: PageAssertions.toHaveURL.timeout = %%-js-assertions-timeout-%% * since: v1.18 diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 17380db170..43988d4c28 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1404,8 +1404,6 @@ export class InjectedScript { received = getAriaRole(element) || ''; } else if (expression === 'to.have.title') { received = this.document.title; - } else if (expression === 'to.have.url') { - received = this.document.location.href; } else if (expression === 'to.have.value') { element = this.retarget(element, 'follow-label')!; if (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT') diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 9b43999cf9..e426cf9948 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -21,11 +21,12 @@ import { expectTypes, callLogText } from '../util'; import { toBeTruthy } from './toBeTruthy'; import { toEqual } from './toEqual'; import { toMatchText } from './toMatchText'; -import { constructURLBasedOnBaseURL, isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils'; +import { isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils'; import { currentTestInfo } from '../common/globals'; import { TestInfoImpl } from '../worker/testInfo'; import type { ExpectMatcherState } from '../../types/test'; import { takeFirst } from '../common/config'; +import { toHaveURL as toHaveURLExternal } from './toHaveURL'; export interface LocatorEx extends Locator { _expect(expression: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }>; @@ -382,16 +383,10 @@ export function toHaveTitle( export function toHaveURL( this: ExpectMatcherState, page: Page, - expected: string | RegExp, - options?: { ignoreCase?: boolean, timeout?: number }, + expected: string | RegExp | ((url: URL) => boolean), + options?: { ignoreCase?: boolean; timeout?: number }, ) { - const baseURL = (page.context() as any)._options.baseURL; - expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected; - const locator = page.locator(':root') as LocatorEx; - return toMatchText.call(this, 'toHaveURL', locator, 'Locator', async (isNot, timeout) => { - const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); - return await locator._expect('to.have.url', { expectedText, isNot, timeout }); - }, expected, options); + return toHaveURLExternal.call(this, page, expected, options); } export async function toBeOK( diff --git a/packages/playwright/src/matchers/toHaveURL.ts b/packages/playwright/src/matchers/toHaveURL.ts new file mode 100644 index 0000000000..df09833f84 --- /dev/null +++ b/packages/playwright/src/matchers/toHaveURL.ts @@ -0,0 +1,153 @@ +/** + * 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 { Page } from 'playwright-core'; +import type { ExpectMatcherState } from '../../types/test'; +import { EXPECTED_COLOR, printReceived } from '../common/expectBundle'; +import { matcherHint, type MatcherResult } from './matcherHint'; +import { constructURLBasedOnBaseURL, urlMatches } from 'playwright-core/lib/utils'; +import { colors } from 'playwright-core/lib/utilsBundle'; +import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring } from './expect'; + +export async function toHaveURL( + this: ExpectMatcherState, + page: Page, + expected: string | RegExp | ((url: URL) => boolean), + options?: { ignoreCase?: boolean; timeout?: number }, +): Promise> { + const matcherName = 'toHaveURL'; + const expression = 'page'; + const matcherOptions = { + isNot: this.isNot, + promise: this.promise, + }; + + if ( + !(typeof expected === 'string') && + !(expected && 'test' in expected && typeof expected.test === 'function') && + !(typeof expected === 'function') + ) { + throw new Error( + [ + // Always display `expected` in expectation place + matcherHint(this, undefined, matcherName, expression, undefined, matcherOptions), + `${colors.bold('Matcher error')}: ${EXPECTED_COLOR('expected')} value must be a string, regular expression, or predicate`, + this.utils.printWithType('Expected', expected, this.utils.printExpected,), + ].join('\n\n'), + ); + } + + const timeout = options?.timeout ?? this.timeout; + const baseURL: string | undefined = (page.context() as any)._options.baseURL; + let conditionSucceeded = false; + let lastCheckedURLString: string | undefined = undefined; + try { + await page.mainFrame().waitForURL( + url => { + lastCheckedURLString = url.toString(); + + if (options?.ignoreCase) { + return ( + !this.isNot === + urlMatches( + baseURL?.toLocaleLowerCase(), + lastCheckedURLString.toLocaleLowerCase(), + typeof expected === 'string' + ? expected.toLocaleLowerCase() + : expected, + ) + ); + } + + return ( + !this.isNot === urlMatches(baseURL, lastCheckedURLString, expected) + ); + }, + { timeout }, + ); + + conditionSucceeded = true; + } catch (e) { + conditionSucceeded = false; + } + + if (conditionSucceeded) + return { name: matcherName, pass: !this.isNot, message: () => '' }; + + return { + name: matcherName, + pass: this.isNot, + message: () => + toHaveURLMessage( + this, + matcherName, + expression, + typeof expected === 'string' + ? constructURLBasedOnBaseURL(baseURL, expected) + : expected, + lastCheckedURLString, + this.isNot, + true, + timeout, + ), + actual: lastCheckedURLString, + timeout, + }; +} + +function toHaveURLMessage( + state: ExpectMatcherState, + matcherName: string, + expression: string, + expected: string | RegExp | Function, + received: string | undefined, + pass: boolean, + didTimeout: boolean, + timeout: number, +): string { + const matcherOptions = { + isNot: state.isNot, + promise: state.promise, + }; + const receivedString = received || ''; + const messagePrefix = matcherHint(state, undefined, matcherName, expression, undefined, matcherOptions, didTimeout ? timeout : undefined); + + let printedReceived: string | undefined; + let printedExpected: string | undefined; + let printedDiff: string | undefined; + if (typeof expected === 'function') { + printedExpected = `Expected predicate to ${!state.isNot ? 'succeed' : 'fail'}`; + printedReceived = `Received string: ${printReceived(receivedString)}`; + } else { + if (pass) { + if (typeof expected === 'string') { + printedExpected = `Expected string: not ${state.utils.printExpected(expected)}`; + const formattedReceived = printReceivedStringContainExpectedSubstring(receivedString, receivedString.indexOf(expected), expected.length); + printedReceived = `Received string: ${formattedReceived}`; + } else { + printedExpected = `Expected pattern: not ${state.utils.printExpected(expected)}`; + const formattedReceived = printReceivedStringContainExpectedResult(receivedString, typeof expected.exec === 'function' ? expected.exec(receivedString) : null); + printedReceived = `Received string: ${formattedReceived}`; + } + } else { + const labelExpected = `Expected ${typeof expected === 'string' ? 'string' : 'pattern'}`; + printedDiff = state.utils.printDiffOrStringify(expected, receivedString, labelExpected, 'Received string', false); + } + } + + const resultDetails = printedDiff ? printedDiff : printedExpected + '\n' + printedReceived; + return messagePrefix + resultDetails; +} diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 26c713949a..1ac35f6201 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -8795,14 +8795,18 @@ interface PageAssertions { * await expect(page).toHaveURL(/.*checkout/); * ``` * - * @param urlOrRegExp Expected URL string or RegExp. + * @param url Expected URL string, RegExp, or predicate receiving [URL] to match. When a + * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) via the context + * options was provided and the passed URL is a path, it gets merged via the + * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. * @param options */ - toHaveURL(urlOrRegExp: string|RegExp, options?: { + toHaveURL(url: string|RegExp|((url: URL) => boolean), options?: { /** * Whether to perform case-insensitive match. * [`ignoreCase`](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-url-option-ignore-case) - * option takes precedence over the corresponding regular expression flag if specified. + * option takes precedence over the corresponding regular expression parameter if specified. A provided predicate + * ignores this flag. */ ignoreCase?: boolean; diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index 8bd668dfd7..aca3c6d3dd 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { stripVTControlCharacters } from 'node:util'; import { stripAnsi } from '../config/utils'; import { test, expect } from './pageTest'; @@ -240,10 +241,45 @@ test.describe('toHaveURL', () => { await expect(page).toHaveURL('data:text/html,
A
'); }); - test('fail', async ({ page }) => { - await page.goto('data:text/html,
B
'); + test('fail string', async ({ page }) => { + await page.goto('data:text/html,
A
'); const error = await expect(page).toHaveURL('wrong', { timeout: 1000 }).catch(e => e); - expect(error.message).toContain('expect.toHaveURL with timeout 1000ms'); + expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)'); + expect(stripVTControlCharacters(error.message)).toContain('Expected string: "wrong"\nReceived string: "data:text/html,
A
"'); + }); + + test('fail with invalid argument', async ({ page }) => { + await page.goto('data:text/html,
A
'); + // @ts-expect-error + const error = await expect(page).toHaveURL({}).catch(e => e); + expect(stripVTControlCharacters(error.message)).toContain('expect(page).toHaveURL(expected)\n\n\n\nMatcher error: expected value must be a string, regular expression, or predicate'); + expect(stripVTControlCharacters(error.message)).toContain('Expected has type: object\nExpected has value: {}'); + }); + + test('fail with positive predicate', async ({ page }) => { + await page.goto('data:text/html,
A
'); + const error = await expect(page).toHaveURL(_url => false).catch(e => e); + expect(stripVTControlCharacters(error.message)).toContain('expect(page).toHaveURL(expected)'); + expect(stripVTControlCharacters(error.message)).toContain('Expected predicate to succeed\nReceived string: "data:text/html,
A
"'); + }); + + test('fail with negative predicate', async ({ page }) => { + await page.goto('data:text/html,
A
'); + const error = await expect(page).not.toHaveURL(_url => true).catch(e => e); + expect(stripVTControlCharacters(error.message)).toContain('expect(page).not.toHaveURL(expected)'); + expect(stripVTControlCharacters(error.message)).toContain('Expected predicate to fail\nReceived string: "data:text/html,
A
"'); + }); + + test('resolve predicate on initial call', async ({ page }) => { + await page.goto('data:text/html,
A
'); + await expect(page).toHaveURL(url => url.href === 'data:text/html,
A
', { timeout: 1000 }); + }); + + test('resolve predicate after retries', async ({ page }) => { + await page.goto('data:text/html,
A
'); + const expectPromise = expect(page).toHaveURL(url => url.href === 'data:text/html,
B
', { timeout: 1000 }); + setTimeout(() => page.goto('data:text/html,
B
'), 500); + await expectPromise; }); test('support ignoreCase', async ({ page }) => { diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index c35cdd7c09..9e47f5282f 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -543,11 +543,31 @@ test('should respect expect.timeout', async ({ runInlineTest }) => { 'playwright.config.js': `module.exports = { expect: { timeout: 1000 } }`, 'a.test.ts': ` import { test, expect } from '@playwright/test'; + import { stripVTControlCharacters } from 'node:util'; test('timeout', async ({ page }) => { await page.goto('data:text/html,
A
'); const error = await expect(page).toHaveURL('data:text/html,
B
').catch(e => e); - expect(error.message).toContain('expect.toHaveURL with timeout 1000ms'); + expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)'); + expect(error.message).toContain('data:text/html,
'); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); + +test('should support toHaveURL predicate', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.js': `module.exports = { expect: { timeout: 1000 } }`, + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + import { stripVTControlCharacters } from 'node:util'; + + test('predicate', async ({ page }) => { + await page.goto('data:text/html,
A
'); + const error = await expect(page).toHaveURL('data:text/html,
B
').catch(e => e); + expect(stripVTControlCharacters(error.message)).toContain('Timed out 1000ms waiting for expect(page).toHaveURL(expected)'); expect(error.message).toContain('data:text/html,
'); }); `, From bbd55587e4a962d8166709cf2f0fe9e43bbc600f Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 24 Jan 2025 06:40:46 -0800 Subject: [PATCH 26/37] feat(chromium): roll to r1156 (#34464) 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 013ac177cf..e052c8064b 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-133.0.6943.16-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-134.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-133.0.6943.27-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-134.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 133.0.6943.16 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 133.0.6943.27 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 134.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 8c9a555e12..e20c621556 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,9 +3,9 @@ "browsers": [ { "name": "chromium", - "revision": "1155", + "revision": "1156", "installByDefault": true, - "browserVersion": "133.0.6943.16" + "browserVersion": "133.0.6943.27" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 4a08f9d687..055d3c5944 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 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/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36 Edg/133.0.6943.16", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 Safari/537.36 Edg/133.0.6943.27", "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/133.0.6943.16 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 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/133.0.6943.16 Safari/537.36 Edg/133.0.6943.16", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.27 Safari/537.36 Edg/133.0.6943.27", "screen": { "width": 1920, "height": 1080 From c44590aa5bbb6dd98f22d046d50e21afe2943468 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 24 Jan 2025 08:27:06 -0800 Subject: [PATCH 27/37] chore: disable popover test on Darwin 13.7 WebKit (#34466) --- tests/library/trace-viewer.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 9bb67884f5..c2da84a4cf 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -1676,7 +1676,8 @@ test('should show only one pointer with multilevel iframes', async ({ page, runA await expect.soft(snapshotFrame.frameLocator('iframe').frameLocator('iframe').locator('x-pw-pointer')).toBeVisible(); }); -test('should show a popover', async ({ runAndTrace, page, server }) => { +test('should show a popover', async ({ runAndTrace, page, server, platform, browserName, macVersion }) => { + test.skip(platform === 'darwin' && macVersion === 13 && browserName === 'webkit', 'WebKit on macOS 13.7 reliably fails on this test for some reason'); const traceViewer = await runAndTrace(async () => { await page.setContent(` From dcff914040d563cfcfffffcdb40b13d2525e48db Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Jan 2025 09:09:57 -0800 Subject: [PATCH 28/37] chore(bidi): make browserType.connect work (#34461) --- .gitignore | 1 + .../src/remote/playwrightConnection.ts | 9 ++++- tests/bidi/expectationReporter.ts | 2 ++ .../bidi-firefox-nightly-library.txt | 35 ------------------- 4 files changed, 11 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index aadc481067..8e41e31810 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ test-results .cache/ .eslintcache playwright.env +/firefox/ diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index ea607bdb17..5f39be5671 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -118,7 +118,14 @@ export class PlaywrightConnection { const playwright = createPlaywright({ sdkLanguage: options.sdkLanguage, isServer: true }); const ownedSocksProxy = await this._createOwnedSocksProxy(playwright); - const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); + let browserName = this._options.browserName; + if ('bidi' === browserName) { + if (this._options.launchOptions?.channel?.toLocaleLowerCase().includes('firefox')) + browserName = 'bidiFirefox'; + else + browserName = 'bidiChromium'; + } + const browser = await playwright[browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); this._cleanups.push(async () => { for (const browser of playwright.allBrowsers()) diff --git a/tests/bidi/expectationReporter.ts b/tests/bidi/expectationReporter.ts index c31cb74aaf..d88875f031 100644 --- a/tests/bidi/expectationReporter.ts +++ b/tests/bidi/expectationReporter.ts @@ -58,6 +58,8 @@ class ExpectationReporter implements Reporter { const key = test.titlePath().slice(2).join(' › '); if (outcome === 'timeout') expectations.set(key, outcome); + else if (expectations.has(key) && test.outcome() !== 'skipped') + expectations.delete(key); // Remove tests that no longer timeout. } const keys = Array.from(expectations.keys()); keys.sort(); diff --git a/tests/bidi/expectations/bidi-firefox-nightly-library.txt b/tests/bidi/expectations/bidi-firefox-nightly-library.txt index f527d5fb61..1db4abda86 100644 --- a/tests/bidi/expectations/bidi-firefox-nightly-library.txt +++ b/tests/bidi/expectations/bidi-firefox-nightly-library.txt @@ -15,43 +15,8 @@ library/browsercontext-page-event.spec.ts › should have about:blank for empty library/browsercontext-proxy.spec.ts › should use proxy for https urls [timeout] library/browsercontext-service-worker-policy.spec.ts › block › blocks service worker registration [timeout] library/browsercontext-timezone-id.spec.ts › should work for multiple pages sharing same process [timeout] -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] -library/browsertype-connect.spec.ts › launchServer › disconnected event should be emitted when browser is closed or server is closed [timeout] -library/browsertype-connect.spec.ts › launchServer › disconnected event should have browser as argument [timeout] -library/browsertype-connect.spec.ts › launchServer › setInputFiles should preserve lastModified timestamp [timeout] library/browsertype-connect.spec.ts › launchServer › should be able to connect 20 times to a single server without warnings [timeout] -library/browsertype-connect.spec.ts › launchServer › should be able to connect two browsers at the same time [timeout] -library/browsertype-connect.spec.ts › launchServer › should be able to connect when the wsEndpoint is passed as an option [timeout] -library/browsertype-connect.spec.ts › launchServer › should be able to reconnect to a browser [timeout] -library/browsertype-connect.spec.ts › launchServer › should be able to visit ipv6 [timeout] -library/browsertype-connect.spec.ts › launchServer › should be able to visit ipv6 through localhost [timeout] -library/browsertype-connect.spec.ts › launchServer › should connect over http [timeout] -library/browsertype-connect.spec.ts › launchServer › should connect over wss [timeout] -library/browsertype-connect.spec.ts › launchServer › should emit close events on pages and contexts [timeout] -library/browsertype-connect.spec.ts › launchServer › should error when saving download after deletion [timeout] -library/browsertype-connect.spec.ts › launchServer › should filter launch options [timeout] -library/browsertype-connect.spec.ts › launchServer › should fulfill with global fetch result [timeout] -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 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] -library/browsertype-connect.spec.ts › launchServer › should reject waitForEvent before browser.close finishes [timeout] -library/browsertype-connect.spec.ts › launchServer › should reject waitForEvent before browser.onDisconnect fires [timeout] -library/browsertype-connect.spec.ts › launchServer › should reject waitForSelector when browser closes [timeout] -library/browsertype-connect.spec.ts › launchServer › should respect selectors [timeout] -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 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 upload large file [timeout] library/channels.spec.ts › should work with the domain module [timeout] 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] From b39c29d0964972da3992f9fc86a18447a1284372 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 24 Jan 2025 12:16:58 -0800 Subject: [PATCH 29/37] docs: remove toMatchAriaSnapshot path feature (#34471) --- docs/src/api/class-locatorassertions.md | 1 - packages/playwright/types/test.d.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 35f64ea711..8f8233fae4 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2250,7 +2250,6 @@ Asserts that the target element matches the given [accessibility snapshot](../ar ```js await expect(page.locator('body')).toMatchAriaSnapshot(); await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); -await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' }); ``` ```python async diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 1ac35f6201..fd42612df8 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -8690,7 +8690,6 @@ interface LocatorAssertions { * ```js * await expect(page.locator('body')).toMatchAriaSnapshot(); * await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); - * await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' }); * ``` * * @param options From 9d91d7a1e9323d66c85ee36c4814ca4aec4b6c09 Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Fri, 24 Jan 2025 22:36:23 +0100 Subject: [PATCH 30/37] chore(firefox): Don't upgrade HTTP requests to HTTPS (#34465) --- .github/workflows/tests_bidi.yml | 2 +- .../src/server/bidi/third_party/firefoxPrefs.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index 99b756c2b3..c7c4b6b54c 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -7,7 +7,7 @@ on: - main paths: - .github/workflows/tests_bidi.yml - - packages/playwright-core/src/server/bidi/* + - packages/playwright-core/src/server/bidi/** schedule: # Run every day at midnight - cron: '0 0 * * *' diff --git a/packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts b/packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts index d59ac0bc13..bf082793b6 100644 --- a/packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts +++ b/packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts @@ -139,6 +139,9 @@ function defaultProfilePreferences( 'dom.min_background_timeout_value_without_budget_throttling': 0, 'dom.timeout.enable_budget_timer_throttling': false, + // Disable HTTPS-First upgrades + 'dom.security.https_first': false, + // Only load extensions from the application and user profile // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION 'extensions.autoDisableScopes': 0, From fccb2b078423b9edf09f86ff8afe0c43df64900a Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 24 Jan 2025 14:21:42 -0800 Subject: [PATCH 31/37] chore: fix codegen SIGINT test (#34468) --- tests/library/inspector/cli-codegen-2.spec.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/library/inspector/cli-codegen-2.spec.ts b/tests/library/inspector/cli-codegen-2.spec.ts index 205bbbae5e..6419a6999c 100644 --- a/tests/library/inspector/cli-codegen-2.spec.ts +++ b/tests/library/inspector/cli-codegen-2.spec.ts @@ -459,8 +459,13 @@ await page1.GotoAsync("about:blank?foo");`); const cli = runCLI([`--save-storage=${storageFileName}`, `--save-har=${harFileName}`]); await cli.waitFor(`import { test, expect } from '@playwright/test'`); await cli.process.kill('SIGINT'); - const { exitCode } = await cli.process.exited; - expect(exitCode).toBe(130); + const { exitCode, signal } = await cli.process.exited; + if (exitCode !== null) { + expect(exitCode).toBe(130); + } else { + // If the runner is slow enough, the process will be forcibly terminated by the signal + expect(signal).toBe('SIGINT'); + } expect(fs.existsSync(storageFileName)).toBeTruthy(); expect(fs.existsSync(harFileName)).toBeTruthy(); }); From fb145d5ebdc217052933327608ef7dfa537a0511 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 24 Jan 2025 15:09:39 -0800 Subject: [PATCH 32/37] test: do not check deprecated KeyboardEvent.keyCode property (#34472) --- tests/assets/input/keyboard.html | 15 ++++- tests/page/page-keyboard.spec.ts | 94 ++++++++++++++++---------------- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/tests/assets/input/keyboard.html b/tests/assets/input/keyboard.html index fd962c7518..049ed439be 100644 --- a/tests/assets/input/keyboard.html +++ b/tests/assets/input/keyboard.html @@ -10,13 +10,13 @@ let textarea = document.querySelector('textarea'); textarea.focus(); textarea.addEventListener('keydown', event => { - log('Keydown:', event.key, event.code, event.which, modifiers(event)); + log('Keydown:', event.key, event.code, getLocation(event), modifiers(event)); }); textarea.addEventListener('keypress', event => { - log('Keypress:', event.key, event.code, event.which, event.charCode, modifiers(event)); + log('Keypress:', event.key, event.code, getLocation(event), event.charCode, modifiers(event)); }); textarea.addEventListener('keyup', event => { - log('Keyup:', event.key, event.code, event.which, modifiers(event)); + log('Keyup:', event.key, event.code, getLocation(event), modifiers(event)); }); function modifiers(event) { let m = []; @@ -28,6 +28,15 @@ m.push('Shift') return '[' + m.join(' ') + ']'; } + function getLocation(event) { + switch (event.location) { + case KeyboardEvent.DOM_KEY_LOCATION_STANDARD: return 'STANDARD'; + case KeyboardEvent.DOM_KEY_LOCATION_LEFT: return 'LEFT'; + case KeyboardEvent.DOM_KEY_LOCATION_RIGHT: return 'RIGHT'; + case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD: return 'NUMPAD'; + default: return 'Unknown: ' + event.location; + }; + } function log(...args) { console.log.apply(console, args); result += args.join(' ') + '\n'; diff --git a/tests/page/page-keyboard.spec.ts b/tests/page/page-keyboard.spec.ts index 0d922584f4..86ff5508b6 100644 --- a/tests/page/page-keyboard.spec.ts +++ b/tests/page/page-keyboard.spec.ts @@ -93,18 +93,18 @@ it('should report shiftKey', async ({ page, server, browserName, platform }) => const codeForKey = { 'Shift': 16, 'Alt': 18, 'Control': 17 }; for (const modifierKey in codeForKey) { await keyboard.down(modifierKey); - expect(await page.evaluate('getResult()')).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']'); + expect(await page.evaluate('getResult()')).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left LEFT [' + modifierKey + ']'); await keyboard.down('!'); // Shift+! will generate a keypress if (modifierKey === 'Shift') - expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 [' + modifierKey + ']'); + expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 STANDARD [' + modifierKey + ']\nKeypress: ! Digit1 STANDARD 33 [' + modifierKey + ']'); else - expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); + expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 STANDARD [' + modifierKey + ']'); await keyboard.up('!'); - expect(await page.evaluate('getResult()')).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); + expect(await page.evaluate('getResult()')).toBe('Keyup: ! Digit1 STANDARD [' + modifierKey + ']'); await keyboard.up(modifierKey); - expect(await page.evaluate('getResult()')).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []'); + expect(await page.evaluate('getResult()')).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left LEFT []'); } }); @@ -112,31 +112,31 @@ it('should report multiple modifiers', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); const keyboard = page.keyboard; await keyboard.down('Control'); - expect(await page.evaluate('getResult()')).toBe('Keydown: Control ControlLeft 17 [Control]'); + expect(await page.evaluate('getResult()')).toBe('Keydown: Control ControlLeft LEFT [Control]'); await keyboard.down('Alt'); - expect(await page.evaluate('getResult()')).toBe('Keydown: Alt AltLeft 18 [Alt Control]'); + expect(await page.evaluate('getResult()')).toBe('Keydown: Alt AltLeft LEFT [Alt Control]'); await keyboard.down(';'); - expect(await page.evaluate('getResult()')).toBe('Keydown: ; Semicolon 186 [Alt Control]'); + expect(await page.evaluate('getResult()')).toBe('Keydown: ; Semicolon STANDARD [Alt Control]'); await keyboard.up(';'); - expect(await page.evaluate('getResult()')).toBe('Keyup: ; Semicolon 186 [Alt Control]'); + expect(await page.evaluate('getResult()')).toBe('Keyup: ; Semicolon STANDARD [Alt Control]'); await keyboard.up('Control'); - expect(await page.evaluate('getResult()')).toBe('Keyup: Control ControlLeft 17 [Alt]'); + expect(await page.evaluate('getResult()')).toBe('Keyup: Control ControlLeft LEFT [Alt]'); await keyboard.up('Alt'); - expect(await page.evaluate('getResult()')).toBe('Keyup: Alt AltLeft 18 []'); + expect(await page.evaluate('getResult()')).toBe('Keyup: Alt AltLeft LEFT []'); }); it('should send proper codes while typing', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.type('!'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: ! Digit1 49 []', - 'Keypress: ! Digit1 33 33 []', - 'Keyup: ! Digit1 49 []'].join('\n')); + ['Keydown: ! Digit1 STANDARD []', + 'Keypress: ! Digit1 STANDARD 33 []', + 'Keyup: ! Digit1 STANDARD []'].join('\n')); await page.keyboard.type('^'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: ^ Digit6 54 []', - 'Keypress: ^ Digit6 94 94 []', - 'Keyup: ^ Digit6 54 []'].join('\n')); + ['Keydown: ^ Digit6 STANDARD []', + 'Keypress: ^ Digit6 STANDARD 94 []', + 'Keyup: ^ Digit6 STANDARD []'].join('\n')); }); it('should send proper codes while typing with shift', async ({ page, server }) => { @@ -145,10 +145,10 @@ it('should send proper codes while typing with shift', async ({ page, server }) await keyboard.down('Shift'); await page.keyboard.type('~'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode - 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode - 'Keyup: ~ Backquote 192 [Shift]'].join('\n')); + ['Keydown: Shift ShiftLeft LEFT [Shift]', + 'Keydown: ~ Backquote STANDARD [Shift]', + 'Keypress: ~ Backquote STANDARD 126 [Shift]', + 'Keyup: ~ Backquote STANDARD [Shift]'].join('\n')); await keyboard.up('Shift'); }); @@ -173,54 +173,54 @@ it('should press plus', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('+'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: + Equal 187 []', // 192 is ` keyCode - 'Keypress: + Equal 43 43 []', // 126 is ~ charCode - 'Keyup: + Equal 187 []'].join('\n')); + ['Keydown: + Equal STANDARD []', + 'Keypress: + Equal STANDARD 43 []', + 'Keyup: + Equal STANDARD []'].join('\n')); }); it('should press shift plus', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('Shift++'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: + Equal 187 [Shift]', // 192 is ` keyCode - 'Keypress: + Equal 43 43 [Shift]', // 126 is ~ charCode - 'Keyup: + Equal 187 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); + ['Keydown: Shift ShiftLeft LEFT [Shift]', + 'Keydown: + Equal STANDARD [Shift]', + 'Keypress: + Equal STANDARD 43 [Shift]', + 'Keyup: + Equal STANDARD [Shift]', + 'Keyup: Shift ShiftLeft LEFT []'].join('\n')); }); it('should support plus-separated modifiers', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('Shift+~'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode - 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode - 'Keyup: ~ Backquote 192 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); + ['Keydown: Shift ShiftLeft LEFT [Shift]', + 'Keydown: ~ Backquote STANDARD [Shift]', + 'Keypress: ~ Backquote STANDARD 126 [Shift]', + 'Keyup: ~ Backquote STANDARD [Shift]', + 'Keyup: Shift ShiftLeft LEFT []'].join('\n')); }); it('should support multiple plus-separated modifiers', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('Control+Shift+~'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: Control ControlLeft 17 [Control]', - 'Keydown: Shift ShiftLeft 16 [Control Shift]', - 'Keydown: ~ Backquote 192 [Control Shift]', // 192 is ` keyCode - 'Keyup: ~ Backquote 192 [Control Shift]', - 'Keyup: Shift ShiftLeft 16 [Control]', - 'Keyup: Control ControlLeft 17 []'].join('\n')); + ['Keydown: Control ControlLeft LEFT [Control]', + 'Keydown: Shift ShiftLeft LEFT [Control Shift]', + 'Keydown: ~ Backquote STANDARD [Control Shift]', + 'Keyup: ~ Backquote STANDARD [Control Shift]', + 'Keyup: Shift ShiftLeft LEFT [Control]', + 'Keyup: Control ControlLeft LEFT []'].join('\n')); }); it('should shift raw codes', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('Shift+Digit3'); expect(await page.evaluate('getResult()')).toBe( - ['Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: # Digit3 51 [Shift]', // 51 is # keyCode - 'Keypress: # Digit3 35 35 [Shift]', // 35 is # charCode - 'Keyup: # Digit3 51 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); + ['Keydown: Shift ShiftLeft LEFT [Shift]', + 'Keydown: # Digit3 STANDARD [Shift]', + 'Keypress: # Digit3 STANDARD 35 [Shift]', + 'Keyup: # Digit3 STANDARD [Shift]', + 'Keyup: Shift ShiftLeft LEFT []'].join('\n')); }); it('should specify repeat property', async ({ page, server }) => { @@ -710,7 +710,7 @@ it('should have correct Keydown/Keyup order when pressing Escape key', async ({ await page.goto(server.PREFIX + '/input/keyboard.html'); await page.keyboard.press('Escape'); expect(await page.evaluate('getResult()')).toBe(` -Keydown: Escape Escape 27 [] -Keyup: Escape Escape 27 [] +Keydown: Escape Escape STANDARD [] +Keyup: Escape Escape STANDARD [] `.trim()); }); From 2afe287a817e04fee46b4c0d6e3e758ee32da40d Mon Sep 17 00:00:00 2001 From: Alexandra Borovova Date: Sat, 25 Jan 2025 00:15:24 +0100 Subject: [PATCH 33/37] Add canonical screenshots for firefox with webdriver bidi for screenshot element tests (#34289) --- ...lement-bounding-box-bidi-firefox-nightly.png | Bin 0 -> 474 bytes ...-element-fractional-bidi-firefox-nightly.png | Bin 0 -> 138 bytes ...t-fractional-offset-bidi-firefox-nightly.png | Bin 0 -> 113 bytes ...arger-than-viewport-bidi-firefox-nightly.png | Bin 0 -> 2797 bytes ...ment-padding-border-bidi-firefox-nightly.png | Bin 0 -> 181 bytes ...shot-element-rotate-bidi-firefox-nightly.png | Bin 0 -> 2377 bytes ...-scrolled-into-view-bidi-firefox-nightly.png | Bin 0 -> 181 bytes .../screenshot-sanity-bidi-firefox-nightly.png | Bin 0 -> 36296 bytes ...lement-bounding-box-bidi-firefox-nightly.png | Bin 0 -> 311 bytes .../screenshot-grid-bidi-firefox-nightly.png | Bin 0 -> 26202 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-bounding-box-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-offset-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-larger-than-viewport-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-padding-border-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-rotate-bidi-firefox-nightly.png create mode 100644 tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-scrolled-into-view-bidi-firefox-nightly.png create mode 100644 tests/page/expect-matcher-result.spec.ts-snapshots/screenshot-sanity-bidi-firefox-nightly.png create mode 100644 tests/page/locator-misc-2.spec.ts-snapshots/screenshot-element-bounding-box-bidi-firefox-nightly.png create mode 100644 tests/page/page-add-locator-handler.spec.ts-snapshots/screenshot-grid-bidi-firefox-nightly.png diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-bounding-box-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-bounding-box-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c3ddca298aba5c502f56e6656fd9330220b327 GIT binary patch literal 474 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^TK3NDj4wz}vwUf`e5)Ukjg=c4B?ZY6Q{gD+<_wP}}l z6nSQ36>Zn#-f|$iNz7fE$&pKHm-_rCzvuN??>N7ATC?@xO>6j=CvYp+J%0ba_|K}- z?e1;A{tEc13fvXy$m4X`&ax<)>7s7qi)jue-U{}mHHEE$Jc%sMXWq*OW$s-$i%G;W zZF||t6r&}VGkq>ExtSx>>!xYDf7J|T5r=j2<5pbF65(PsvM%I1RJ=tim8p?oS*Dfk z^|OT?x8ELn{&}OJ?ag94p-v0itCJs3c=3Z{t=G@fKZ902`4Zw^|LI!P4gZAOW-CLy zZnhPjNK*3L8l^h>>?RwlH95zZzB$)Wuj{urPJRCQ;w>U!mP`4PSezL|x?Qg=R|`2? z63iqS9eMQe#}9S&%O8Ea|IFgamuF(Pw=sImn|nEL`|j&|;`G&T&-U|Y;!@~!TUip(bP*}7q&3SPEgSM@h9ZZ`&x!)qR}^g{rtKT7+DOSu6{1- HoD!M<45`mj literal 0 HcmV?d00001 diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..35c53377f942c843cab3060143ae94c6fd0dd213 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^#y~8_!3HFA4=h;^q!^2X+?^QKos)S9D6u`Nc+rW&s8 l$xQXpPL7hitfgaR5|>%Ts?Gg~>mblP22WQ%mvv4FO#tsSEqDL` literal 0 HcmV?d00001 diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-offset-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-fractional-offset-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..f554b1d62c4ab19291c200519abdf2b7d12ac1f8 GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^ra&yt!3HE(H?jKyDGN^*$B>BDx91&s84Ltk9RAGS z_wQ9B?_>pr2I-#YOH*8f#e5@8QZJ$~>a*6e8_l=hFd<5IBhU;6Pgg&ebxsLQ0B}kn Ay#N3J literal 0 HcmV?d00001 diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-larger-than-viewport-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-larger-than-viewport-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..6d28cddcea336612c0e4ad4df15d34c91b7d0418 GIT binary patch literal 2797 zcmeAS@N?(olHy`uVBq!ia0y~yV2T1^4mP03_vn|e7#O&=c)B=-RLpsM&5)PDfak!5 zPwIDToiEgE6XBaVGuCsGis)G;hJrI0j0`S4Vhj#PMj4}lFq#rZbHQjX7|jKvxnMLG pjOK#TTtL5E@Gk?{B7M1!{a6RZ#^NA%Cx&(BWL^R}`JOJ0AsLNt zZy9nm7zi*Nbg8MRTi5!4TO@Mk%Ak9bwDbE4WgfS0ThCr`x^DlqKLtJPYqJ^aeS};s`jax+T2B}0-!UC6l3(jyZ5@*X)afq%4TFv0;>gTe~DWM4fsoOvN literal 0 HcmV?d00001 diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-rotate-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-rotate-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..5aab14c4b911d75804359a79699ccbd676fce212 GIT binary patch literal 2377 zcmYLLc{r5o8&(YlgHcrWA+n?>O=KuziI5mGG#F84LJQfKoH@qWi5STe24ib1$(H5F z7KzMMmLiRjY+1^FY`<@uKYo9F-*><7bwBrWKhO0(@Abu4S(*s)OYn1XaS2{EH9`Sj z66XQ)0PomC&>i4&zz1bwz*YW1YKDu8@6c5vBswr_{u|CBb4Z$Jsq3+enq^g?z#%Ga z)85APO(6^W1^-wCQ zYRazpfSre2M5LAk!{;^cL|qrgv*_1`Z;(mgd_~20gn+slai?2YHcYIR!5j{o$Bc+A z&~@F8kbho2fBVP#CzuMm2c;b^w-V(J$FeqdCyGbWCC&k~>v6F7zIf_*LZo2J;@IL? z+*T=C8nCc67CL=z)+oS7YmG>3$$g$GJc;y@ z;te6T9IJcqU3EiTctDSAs!aM&X!&cnDSTp~C+ZK(@gG0&#C5$7lh^}9E|Ya>V+B#< z-Oy8|2QiM>4nh=OYDlGI=!wwLoW^;?2*W>D(6+mcii={cyZhJ-CVzzWp6;Wr)zv)P zBREa#_&LmZRsB|I$ZTY8gkPcFO*eCg-g}DBC22oKW&P)aMB;st79kaz_y^@7VVmq z21m1pw~VmaN)+y}GK$}Lwi3NE!t2_NRsRC(qvTap{PWL?#Zh8afY`6mTANjWGU_mS z!&gdp_c!v(`=h=JJAl;5rfrj!(z+%Gr5Q<6vl@ZI8~Ex=>kpD!3t|v2rR~ zue2^jYX%9$sLXYAP$f!`NdRgq;2)xbt28`lBqaRtEL!{@49#Iq8Y!X9qQEb(%jTsZJ z#9H3$0LfjFF6tM90Fnpv8S0;66>rPSm}?6Rk(rJo5Pf~9R99>IsXKv}pMn|RMw_?q zEFh0(9M1zHpi8`rFP~p;!`ER9IVQwpO$zB(8N7M79Qn*4qy5dmf$T&DQhk)su^|}5 zQ973sK?mo}>yN|^)$@cs5FofA7?Gz_9c-K4o(7j}ik?Zh$zIi#Pg<{Ku{l-rRc>W} zbNez-HS6~PnL9!fA2mzwR0N|{GQ*os*Vlu>gklIwwmtN`aQ)P(i-S4}4Y5K8F+Bw5 zQ+Qh@GS@IqeAp}lik}nlv)kXnOH67% zr4y}JRU7QHLbS? z0roXL-wsSaz6@Y+?=yk{h8Twdl+6ZigrJ@0P5X&|pj2LY|Ce~Gmeq5SQi!npoVi9VRk@Pp@SR=yZShDTJN7j zKqtuS_gg1?3L>)|EGNhjhH9{WkB`7MBS*v2m03XgN(QqB_ROk;H=QX$c}+J_V6d((IiK`p0F) z=e&eTc7I+dj7fza*~#D8VBfqMZC_Y`+vbK^2`V@;qHUBG-%_j=$ypcLeRv`Q*^6Ul8!e9jU9i4(z zlm)4O>pyZ17$YoqYwCxB->nY28sdWqGa7@iBfC8sxp|H4M*pf zq6pSdZDm676%4I659WpLPAV@oOE*SBUC%a0FX!a+>!9B?o_|rC341T&$M&wxdFE4yH8V--zp~v87SDfivlUBG-1w literal 0 HcmV?d00001 diff --git a/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-scrolled-into-view-bidi-firefox-nightly.png b/tests/page/elementhandle-screenshot.spec.ts-snapshots/screenshot-element-scrolled-into-view-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..fc34319896c1dd550b7c34700502b6ff76f399d3 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL1|)l2v+e>Z#^NA%Cx&(BWL^R}`JOJ0AsLNt zZy9nm7zi*Nbg8MRTi5!4TO@Mk%Ak9bwDbE4WgfS0ThCr`x^DlqKLtJPYqJ^aeS};s`jax+T2B}0-!UC6l3(jyZ5@*X)afq%4TFv0;>gTe~DWM4fsoOvN literal 0 HcmV?d00001 diff --git a/tests/page/expect-matcher-result.spec.ts-snapshots/screenshot-sanity-bidi-firefox-nightly.png b/tests/page/expect-matcher-result.spec.ts-snapshots/screenshot-sanity-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..122a4f0ae04d939518343abfbe203d9c719612bb GIT binary patch literal 36296 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 literal 0 HcmV?d00001 diff --git a/tests/page/page-add-locator-handler.spec.ts-snapshots/screenshot-grid-bidi-firefox-nightly.png b/tests/page/page-add-locator-handler.spec.ts-snapshots/screenshot-grid-bidi-firefox-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..7b164933553a5e4b99acc947fd5ffa6866c58b96 GIT binary patch literal 26202 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 F{~tmaMuz|Z literal 0 HcmV?d00001 From c815d090bc842a613194f5cbbd994c5d39ff58c6 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:16:18 -0800 Subject: [PATCH 34/37] feat(firefox-beta): roll to r1468 (#34454) --- 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 e20c621556..78194fd606 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -21,7 +21,7 @@ }, { "name": "firefox-beta", - "revision": "1467", + "revision": "1468", "installByDefault": false, "browserVersion": "133.0b9" }, From 3d9a9d2405a141bcb79be9cb08393a6ab833fd84 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:17:14 -0800 Subject: [PATCH 35/37] feat(firefox): roll to r1472 (#34455) --- packages/playwright-core/browsers.json | 2 +- packages/playwright-core/src/server/firefox/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 78194fd606..1826120327 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,7 +15,7 @@ }, { "name": "firefox", - "revision": "1471", + "revision": "1472", "installByDefault": true, "browserVersion": "134.0" }, diff --git a/packages/playwright-core/src/server/firefox/protocol.d.ts b/packages/playwright-core/src/server/firefox/protocol.d.ts index 3b661cb6cb..ccf81fd171 100644 --- a/packages/playwright-core/src/server/firefox/protocol.d.ts +++ b/packages/playwright-core/src/server/firefox/protocol.d.ts @@ -301,6 +301,11 @@ export module Protocol { forcedColors: ("active"|"none")|null; }; export type setForcedColorsReturnValue = void; + export type setContrastParameters = { + browserContextId?: string; + contrast: ("less"|"more"|"custom"|"no-preference")|null; + }; + export type setContrastReturnValue = void; export type setVideoRecordingOptionsParameters = { browserContextId?: string; options?: { @@ -530,6 +535,7 @@ export module Protocol { colorScheme?: ("dark"|"light"|"no-preference"); reducedMotion?: ("reduce"|"no-preference"); forcedColors?: ("active"|"none"); + contrast?: ("less"|"more"|"custom"|"no-preference"); }; export type setEmulatedMediaReturnValue = void; export type setCacheDisabledParameters = { @@ -1131,6 +1137,7 @@ export module Protocol { "Browser.setColorScheme": Browser.setColorSchemeParameters; "Browser.setReducedMotion": Browser.setReducedMotionParameters; "Browser.setForcedColors": Browser.setForcedColorsParameters; + "Browser.setContrast": Browser.setContrastParameters; "Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsParameters; "Browser.cancelDownload": Browser.cancelDownloadParameters; "Heap.collectGarbage": Heap.collectGarbageParameters; @@ -1213,6 +1220,7 @@ export module Protocol { "Browser.setColorScheme": Browser.setColorSchemeReturnValue; "Browser.setReducedMotion": Browser.setReducedMotionReturnValue; "Browser.setForcedColors": Browser.setForcedColorsReturnValue; + "Browser.setContrast": Browser.setContrastReturnValue; "Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsReturnValue; "Browser.cancelDownload": Browser.cancelDownloadReturnValue; "Heap.collectGarbage": Heap.collectGarbageReturnValue; From 245f4b5b8630d054fb3472097f2777df761e6089 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 24 Jan 2025 15:42:58 -0800 Subject: [PATCH 36/37] fix: allow changed to be passed in config (#34473) --- .../playwright/src/common/configLoader.ts | 4 +- .../src/matchers/toMatchAriaSnapshot.ts | 7 ++- packages/playwright/src/program.ts | 2 +- .../aria-snapshot-file.spec.ts | 57 +++++++++++++++++++ tests/playwright-test/golden.spec.ts | 52 +++++++++++++++++ 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 13d7eac187..b9f24b929a 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -240,8 +240,8 @@ function validateConfig(file: string, config: Config) { } if ('updateSnapshots' in config && config.updateSnapshots !== undefined) { - if (typeof config.updateSnapshots !== 'string' || !['all', 'none', 'missing'].includes(config.updateSnapshots)) - throw errorWithFile(file, `config.updateSnapshots must be one of "all", "none" or "missing"`); + if (typeof config.updateSnapshots !== 'string' || !['all', 'changed', 'missing', 'none'].includes(config.updateSnapshots)) + throw errorWithFile(file, `config.updateSnapshots must be one of "all", "changed", "missing" or "none"`); } if ('workers' in config && config.workers !== undefined) { diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index d379d8610c..e4944fbfc1 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -30,12 +30,13 @@ import path from 'path'; type ToMatchAriaSnapshotExpected = { name?: string; path?: string; + timeout?: number; } | string; export async function toMatchAriaSnapshot( this: ExpectMatcherState, receiver: LocatorEx, - expectedParam: ToMatchAriaSnapshotExpected, + expectedParam?: ToMatchAriaSnapshotExpected, options: { timeout?: number } = {}, ): Promise> { const matcherName = 'toMatchAriaSnapshot'; @@ -55,9 +56,11 @@ export async function toMatchAriaSnapshot( }; let expected: string; + let timeout: number; let expectedPath: string | undefined; if (isString(expectedParam)) { expected = expectedParam; + timeout = options.timeout ?? this.timeout; } else { if (expectedParam?.name) { expectedPath = testInfo.snapshotPath(sanitizeFilePathBeforeExtension(expectedParam.name)); @@ -71,6 +74,7 @@ export async function toMatchAriaSnapshot( expectedPath = testInfo.snapshotPath(sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.yml'); } expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => ''); + timeout = expectedParam?.timeout ?? this.timeout; } const generateMissingBaseline = updateSnapshots === 'missing' && !expected; @@ -84,7 +88,6 @@ export async function toMatchAriaSnapshot( } } - const timeout = options.timeout ?? this.timeout; expected = unshift(expected); const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: expected, isNot: this.isNot, timeout }); const typedReceived = received as MatcherReceived | typeof kNoElementsFoundError; diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 93969662bb..ebedca536b 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -286,7 +286,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid if (['all', 'changed', 'missing', 'none'].includes(options.updateSnapshots)) updateSnapshots = options.updateSnapshots; else - updateSnapshots = 'updateSnapshots' in options ? 'changed' : 'missing'; + updateSnapshots = 'updateSnapshots' in options ? 'changed' : undefined; const overrides: ConfigCLIOverrides = { forbidOnly: options.forbidOnly ? true : undefined, diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts index 18ebc072cc..f9c2563f37 100644 --- a/tests/playwright-test/aria-snapshot-file.spec.ts +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -152,3 +152,60 @@ test('should generate snapshot name', async ({ runInlineTest }, testInfo) => { const snapshot2 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-name-2.yml'), 'utf8'); expect(snapshot2).toBe('- heading "hello world 2" [level=1]'); }); + +for (const updateSnapshots of ['all', 'changed', 'missing', 'none']) { + test(`should update snapshot with the update-snapshots=${updateSnapshots} (config)`, async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + updateSnapshots: '${updateSnapshots}', + }; + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

New content

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ timeout: 1 }); + }); + `, + '__snapshots__/a.spec.ts/test-1.yml': '- heading "Old content" [level=1]', + }); + + const rebase = updateSnapshots === 'all' || updateSnapshots === 'changed'; + expect(result.exitCode).toBe(rebase ? 0 : 1); + if (rebase) { + const snapshotOutputPath = testInfo.outputPath('__snapshots__/a.spec.ts/test-1.yml'); + expect(result.output).toContain(`A snapshot is generated at`); + const data = fs.readFileSync(snapshotOutputPath); + expect(data.toString()).toBe('- heading "New content" [level=1]'); + } else { + expect(result.output).toContain(`expect.toMatchAriaSnapshot`); + } + }); +} + +test('should respect timeout', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + 'test.yml': ` + - heading "hello world" + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + import path from 'path'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ timeout: 1 }); + }); + `, + '__snapshots__/a.spec.ts/test-1.yml': '- heading "new world" [level=1]', + }); + + expect(result.exitCode).toBe(1); + expect(result.output).toContain(`Timed out 1ms waiting for`); +}); diff --git a/tests/playwright-test/golden.spec.ts b/tests/playwright-test/golden.spec.ts index bf3dbb8880..83ae3442c7 100644 --- a/tests/playwright-test/golden.spec.ts +++ b/tests/playwright-test/golden.spec.ts @@ -341,6 +341,36 @@ test('should update snapshot with the update-snapshots flag', async ({ runInline expect(data.toString()).toBe(ACTUAL_SNAPSHOT); }); +for (const updateSnapshots of ['all', 'changed', 'missing', 'none']) { + test(`should update snapshot with the update-snapshots=${updateSnapshots} (config)`, async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': `export default { updateSnapshots: '${updateSnapshots}' };`, + ...files, + 'a.spec.js-snapshots/snapshot.txt': 'Hello world', + 'a.spec.js': ` + const { test, expect } = require('./helper'); + test('is a test', ({}) => { + expect('Hello world updated').toMatchSnapshot('snapshot.txt'); + }); + ` + }); + + const rebase = updateSnapshots === 'all' || updateSnapshots === 'changed'; + expect(result.exitCode).toBe(rebase ? 0 : 1); + if (rebase) { + const snapshotOutputPath = testInfo.outputPath('a.spec.js-snapshots/snapshot.txt'); + if (updateSnapshots === 'all') + expect(result.output).toContain(`${snapshotOutputPath} is not the same, writing actual.`); + if (updateSnapshots === 'changed') + expect(result.output).toContain(`${snapshotOutputPath} does not match, writing actual.`); + const data = fs.readFileSync(snapshotOutputPath); + expect(data.toString()).toBe('Hello world updated'); + } else { + expect(result.output).toContain(`toMatchSnapshot`); + } + }); +} + test('should ignore text snapshot with the ignore-snapshots flag', async ({ runInlineTest }, testInfo) => { const EXPECTED_SNAPSHOT = 'Hello world'; const ACTUAL_SNAPSHOT = 'Hello world updated'; @@ -1140,3 +1170,25 @@ test('should throw if a Promise was passed to toMatchSnapshot', async ({ runInli expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); + + +test('should respect update snapshot option from config', async ({ runInlineTest }, testInfo) => { + const EXPECTED_SNAPSHOT = 'Hello world'; + const ACTUAL_SNAPSHOT = 'Hello world updated'; + const result = await runInlineTest({ + ...files, + 'a.spec.js-snapshots/snapshot.txt': EXPECTED_SNAPSHOT, + 'a.spec.js': ` + const { test, expect } = require('./helper'); + test('is a test', ({}) => { + expect('${ACTUAL_SNAPSHOT}').toMatchSnapshot('snapshot.txt'); + }); + ` + }, { 'update-snapshots': true }); + + expect(result.exitCode).toBe(0); + const snapshotOutputPath = testInfo.outputPath('a.spec.js-snapshots/snapshot.txt'); + expect(result.output).toContain(`${snapshotOutputPath} does not match, writing actual.`); + const data = fs.readFileSync(snapshotOutputPath); + expect(data.toString()).toBe(ACTUAL_SNAPSHOT); +}); From 640e6a8aa75a23a31e7591f34176a32ed73ba022 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 27 Jan 2025 09:58:19 +0000 Subject: [PATCH 37/37] chore: remove unused headers (#34491) --- packages/playwright-core/src/utils/httpServer.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/playwright-core/src/utils/httpServer.ts b/packages/playwright-core/src/utils/httpServer.ts index 8da2a0e0d0..1d78df4659 100644 --- a/packages/playwright-core/src/utils/httpServer.ts +++ b/packages/playwright-core/src/utils/httpServer.ts @@ -214,12 +214,6 @@ export class HttpServer { } private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { - response.setHeader('Access-Control-Allow-Origin', '*'); - response.setHeader('Access-Control-Request-Method', '*'); - response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET'); - if (request.headers.origin) - response.setHeader('Access-Control-Allow-Headers', request.headers.origin); - if (request.method === 'OPTIONS') { response.writeHead(200); response.end();