Merge branch 'main' into aria-snapshots

This commit is contained in:
Debbie O'Brien 2025-01-27 22:14:31 +01:00
commit c2b4cdc665
34 changed files with 215 additions and 99 deletions

View file

@ -7,7 +7,8 @@ on:
- main - main
paths: paths:
- .github/workflows/tests_bidi.yml - .github/workflows/tests_bidi.yml
- packages/playwright-core/src/server/bidi/* - packages/playwright-core/src/server/bidi/**
- tests/bidi/**
schedule: schedule:
# Run every day at midnight # Run every day at midnight
- cron: '0 0 * * *' - cron: '0 0 * * *'
@ -18,7 +19,6 @@ env:
jobs: jobs:
test_bidi: test_bidi:
name: BiDi name: BiDi
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
permissions: permissions:

View file

@ -20,7 +20,6 @@ env:
jobs: jobs:
test_components: test_components:
name: ${{ matrix.os }} - Node.js ${{ matrix.node-version }} name: ${{ matrix.os }} - Node.js ${{ matrix.node-version }}
if: github.repository == 'microsoft/playwright'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View file

@ -21,7 +21,6 @@ env:
jobs: jobs:
test_stress: test_stress:
name: Stress - ${{ matrix.os }} name: Stress - ${{ matrix.os }}
if: github.repository == 'microsoft/playwright'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -58,7 +57,6 @@ jobs:
test_webview2: test_webview2:
name: WebView2 name: WebView2
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: windows-2022 runs-on: windows-2022
permissions: permissions:
@ -89,7 +87,6 @@ jobs:
test_clock_frozen_time_linux: test_clock_frozen_time_linux:
name: time library - ${{ matrix.clock }} name: time library - ${{ matrix.clock }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
@ -115,7 +112,6 @@ jobs:
test_clock_frozen_time_test_runner: test_clock_frozen_time_test_runner:
name: time test runner - ${{ matrix.clock }} name: time test runner - ${{ matrix.clock }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
@ -140,7 +136,6 @@ jobs:
test_electron: test_electron:
name: Electron - ${{ matrix.os }} name: Electron - ${{ matrix.os }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false

View file

@ -27,7 +27,6 @@ env:
jobs: jobs:
test_linux: test_linux:
name: ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) name: ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }})
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -60,7 +59,6 @@ jobs:
test_linux_chromium_tot: test_linux_chromium_tot:
name: ${{ matrix.os }} (chromium tip-of-tree) name: ${{ matrix.os }} (chromium tip-of-tree)
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -85,7 +83,6 @@ jobs:
test_test_runner: test_test_runner:
name: Test Runner name: Test Runner
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -130,7 +127,6 @@ jobs:
test_web_components: test_web_components:
name: Web Components name: Web Components
if: github.repository == 'microsoft/playwright'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -166,7 +162,6 @@ jobs:
test_vscode_extension: test_vscode_extension:
name: VSCode Extension name: VSCode Extension
if: github.repository == 'microsoft/playwright'
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
PWTEST_BOT_NAME: "vscode-extension" PWTEST_BOT_NAME: "vscode-extension"
@ -203,7 +198,6 @@ jobs:
test_package_installations: test_package_installations:
name: "Installation Test ${{ matrix.os }}" name: "Installation Test ${{ matrix.os }}"
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false

View file

@ -26,7 +26,6 @@ permissions:
jobs: jobs:
test_linux: test_linux:
name: ${{ matrix.os }} (${{ matrix.browser }}) name: ${{ matrix.os }} (${{ matrix.browser }})
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -47,7 +46,6 @@ jobs:
test_mac: test_mac:
name: ${{ matrix.os }} (${{ matrix.browser }}) name: ${{ matrix.os }} (${{ matrix.browser }})
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -75,7 +73,6 @@ jobs:
test_win: test_win:
name: "Windows" name: "Windows"
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -95,7 +92,6 @@ jobs:
test-package-installations-other-node-versions: test-package-installations-other-node-versions:
name: "Installation Test ${{ matrix.os }} (${{ matrix.node_version }})" name: "Installation Test ${{ matrix.os }} (${{ matrix.node_version }})"
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@ -129,7 +125,6 @@ jobs:
headed_tests: headed_tests:
name: "headed ${{ matrix.browser }} (${{ matrix.os }})" name: "headed ${{ matrix.browser }} (${{ matrix.os }})"
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -156,7 +151,6 @@ jobs:
transport_linux: transport_linux:
name: "Transport" name: "Transport"
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -178,7 +172,6 @@ jobs:
tracing_linux: tracing_linux:
name: Tracing ${{ matrix.browser }} ${{ matrix.channel }} name: Tracing ${{ matrix.browser }} ${{ matrix.channel }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -206,7 +199,6 @@ jobs:
test_chromium_channels: test_chromium_channels:
name: Test ${{ matrix.channel }} on ${{ matrix.runs-on }} name: Test ${{ matrix.channel }} on ${{ matrix.runs-on }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
strategy: strategy:
@ -229,7 +221,6 @@ jobs:
chromium_tot: chromium_tot:
name: Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} name: Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@ -252,7 +243,6 @@ jobs:
chromium_tot_headless_shell: chromium_tot_headless_shell:
name: Chromium tip-of-tree headless-shell-${{ matrix.os }} name: Chromium tip-of-tree headless-shell-${{ matrix.os }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@ -274,7 +264,6 @@ jobs:
firefox_beta: firefox_beta:
name: Firefox Beta ${{ matrix.os }} name: Firefox Beta ${{ matrix.os }}
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@ -296,7 +285,6 @@ jobs:
build-playwright-driver: build-playwright-driver:
name: "build-playwright-driver" name: "build-playwright-driver"
if: github.repository == 'microsoft/playwright'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -310,7 +298,6 @@ jobs:
test_channel_chromium: test_channel_chromium:
name: Test channel=chromium name: Test channel=chromium
if: github.repository == 'microsoft/playwright'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy: strategy:
fail-fast: false fail-fast: false

View file

@ -10,7 +10,6 @@ env:
jobs: jobs:
test: test:
name: "Service" name: "Service"
if: github.repository == 'microsoft/playwright'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View file

@ -14,7 +14,6 @@ env:
jobs: jobs:
video_linux: video_linux:
name: "Video Linux" name: "Video Linux"
if: github.repository == 'microsoft/playwright'
environment: allow-uploading-flakiness-results environment: allow-uploading-flakiness-results
strategy: strategy:
fail-fast: false fail-fast: false

View file

@ -9,7 +9,6 @@ on:
jobs: jobs:
trigger: trigger:
name: "trigger" name: "trigger"
if: github.repository == 'microsoft/playwright'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- run: | - run: |

View file

@ -2250,7 +2250,6 @@ Asserts that the target element matches the given [accessibility snapshot](../ar
```js ```js
await expect(page.locator('body')).toMatchAriaSnapshot(); await expect(page.locator('body')).toMatchAriaSnapshot();
await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' });
await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' });
``` ```
```python async ```python async

View file

@ -15,13 +15,13 @@
}, },
{ {
"name": "firefox", "name": "firefox",
"revision": "1471", "revision": "1472",
"installByDefault": true, "installByDefault": true,
"browserVersion": "134.0" "browserVersion": "134.0"
}, },
{ {
"name": "firefox-beta", "name": "firefox-beta",
"revision": "1467", "revision": "1468",
"installByDefault": false, "installByDefault": false,
"browserVersion": "133.0b9" "browserVersion": "133.0b9"
}, },

View file

@ -139,6 +139,9 @@ function defaultProfilePreferences(
'dom.min_background_timeout_value_without_budget_throttling': 0, 'dom.min_background_timeout_value_without_budget_throttling': 0,
'dom.timeout.enable_budget_timer_throttling': false, '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 // Only load extensions from the application and user profile
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
'extensions.autoDisableScopes': 0, 'extensions.autoDisableScopes': 0,

View file

@ -301,6 +301,11 @@ export module Protocol {
forcedColors: ("active"|"none")|null; forcedColors: ("active"|"none")|null;
}; };
export type setForcedColorsReturnValue = void; export type setForcedColorsReturnValue = void;
export type setContrastParameters = {
browserContextId?: string;
contrast: ("less"|"more"|"custom"|"no-preference")|null;
};
export type setContrastReturnValue = void;
export type setVideoRecordingOptionsParameters = { export type setVideoRecordingOptionsParameters = {
browserContextId?: string; browserContextId?: string;
options?: { options?: {
@ -530,6 +535,7 @@ export module Protocol {
colorScheme?: ("dark"|"light"|"no-preference"); colorScheme?: ("dark"|"light"|"no-preference");
reducedMotion?: ("reduce"|"no-preference"); reducedMotion?: ("reduce"|"no-preference");
forcedColors?: ("active"|"none"); forcedColors?: ("active"|"none");
contrast?: ("less"|"more"|"custom"|"no-preference");
}; };
export type setEmulatedMediaReturnValue = void; export type setEmulatedMediaReturnValue = void;
export type setCacheDisabledParameters = { export type setCacheDisabledParameters = {
@ -1131,6 +1137,7 @@ export module Protocol {
"Browser.setColorScheme": Browser.setColorSchemeParameters; "Browser.setColorScheme": Browser.setColorSchemeParameters;
"Browser.setReducedMotion": Browser.setReducedMotionParameters; "Browser.setReducedMotion": Browser.setReducedMotionParameters;
"Browser.setForcedColors": Browser.setForcedColorsParameters; "Browser.setForcedColors": Browser.setForcedColorsParameters;
"Browser.setContrast": Browser.setContrastParameters;
"Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsParameters; "Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsParameters;
"Browser.cancelDownload": Browser.cancelDownloadParameters; "Browser.cancelDownload": Browser.cancelDownloadParameters;
"Heap.collectGarbage": Heap.collectGarbageParameters; "Heap.collectGarbage": Heap.collectGarbageParameters;
@ -1213,6 +1220,7 @@ export module Protocol {
"Browser.setColorScheme": Browser.setColorSchemeReturnValue; "Browser.setColorScheme": Browser.setColorSchemeReturnValue;
"Browser.setReducedMotion": Browser.setReducedMotionReturnValue; "Browser.setReducedMotion": Browser.setReducedMotionReturnValue;
"Browser.setForcedColors": Browser.setForcedColorsReturnValue; "Browser.setForcedColors": Browser.setForcedColorsReturnValue;
"Browser.setContrast": Browser.setContrastReturnValue;
"Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsReturnValue; "Browser.setVideoRecordingOptions": Browser.setVideoRecordingOptionsReturnValue;
"Browser.cancelDownload": Browser.cancelDownloadReturnValue; "Browser.cancelDownload": Browser.cancelDownloadReturnValue;
"Heap.collectGarbage": Heap.collectGarbageReturnValue; "Heap.collectGarbage": Heap.collectGarbageReturnValue;

View file

@ -214,12 +214,6 @@ export class HttpServer {
} }
private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { 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') { if (request.method === 'OPTIONS') {
response.writeHead(200); response.writeHead(200);
response.end(); response.end();

View file

@ -240,8 +240,8 @@ function validateConfig(file: string, config: Config) {
} }
if ('updateSnapshots' in config && config.updateSnapshots !== undefined) { if ('updateSnapshots' in config && config.updateSnapshots !== undefined) {
if (typeof config.updateSnapshots !== 'string' || !['all', 'none', 'missing'].includes(config.updateSnapshots)) if (typeof config.updateSnapshots !== 'string' || !['all', 'changed', 'missing', 'none'].includes(config.updateSnapshots))
throw errorWithFile(file, `config.updateSnapshots must be one of "all", "none" or "missing"`); throw errorWithFile(file, `config.updateSnapshots must be one of "all", "changed", "missing" or "none"`);
} }
if ('workers' in config && config.workers !== undefined) { if ('workers' in config && config.workers !== undefined) {

View file

@ -30,12 +30,13 @@ import path from 'path';
type ToMatchAriaSnapshotExpected = { type ToMatchAriaSnapshotExpected = {
name?: string; name?: string;
path?: string; path?: string;
timeout?: number;
} | string; } | string;
export async function toMatchAriaSnapshot( export async function toMatchAriaSnapshot(
this: ExpectMatcherState, this: ExpectMatcherState,
receiver: LocatorEx, receiver: LocatorEx,
expectedParam: ToMatchAriaSnapshotExpected, expectedParam?: ToMatchAriaSnapshotExpected,
options: { timeout?: number } = {}, options: { timeout?: number } = {},
): Promise<MatcherResult<string | RegExp, string>> { ): Promise<MatcherResult<string | RegExp, string>> {
const matcherName = 'toMatchAriaSnapshot'; const matcherName = 'toMatchAriaSnapshot';
@ -55,9 +56,11 @@ export async function toMatchAriaSnapshot(
}; };
let expected: string; let expected: string;
let timeout: number;
let expectedPath: string | undefined; let expectedPath: string | undefined;
if (isString(expectedParam)) { if (isString(expectedParam)) {
expected = expectedParam; expected = expectedParam;
timeout = options.timeout ?? this.timeout;
} else { } else {
if (expectedParam?.name) { if (expectedParam?.name) {
expectedPath = testInfo.snapshotPath(sanitizeFilePathBeforeExtension(expectedParam.name)); expectedPath = testInfo.snapshotPath(sanitizeFilePathBeforeExtension(expectedParam.name));
@ -71,6 +74,7 @@ export async function toMatchAriaSnapshot(
expectedPath = testInfo.snapshotPath(sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.yml'); expectedPath = testInfo.snapshotPath(sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.yml');
} }
expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => ''); expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => '');
timeout = expectedParam?.timeout ?? this.timeout;
} }
const generateMissingBaseline = updateSnapshots === 'missing' && !expected; const generateMissingBaseline = updateSnapshots === 'missing' && !expected;
@ -84,7 +88,6 @@ export async function toMatchAriaSnapshot(
} }
} }
const timeout = options.timeout ?? this.timeout;
expected = unshift(expected); expected = unshift(expected);
const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: expected, isNot: this.isNot, timeout }); 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; const typedReceived = received as MatcherReceived | typeof kNoElementsFoundError;

View file

@ -286,7 +286,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
if (['all', 'changed', 'missing', 'none'].includes(options.updateSnapshots)) if (['all', 'changed', 'missing', 'none'].includes(options.updateSnapshots))
updateSnapshots = options.updateSnapshots; updateSnapshots = options.updateSnapshots;
else else
updateSnapshots = 'updateSnapshots' in options ? 'changed' : 'missing'; updateSnapshots = 'updateSnapshots' in options ? 'changed' : undefined;
const overrides: ConfigCLIOverrides = { const overrides: ConfigCLIOverrides = {
forbidOnly: options.forbidOnly ? true : undefined, forbidOnly: options.forbidOnly ? true : undefined,

View file

@ -8690,7 +8690,6 @@ interface LocatorAssertions {
* ```js * ```js
* await expect(page.locator('body')).toMatchAriaSnapshot(); * await expect(page.locator('body')).toMatchAriaSnapshot();
* await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); * await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' });
* await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' });
* ``` * ```
* *
* @param options * @param options

View file

@ -10,13 +10,13 @@
let textarea = document.querySelector('textarea'); let textarea = document.querySelector('textarea');
textarea.focus(); textarea.focus();
textarea.addEventListener('keydown', event => { 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 => { 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 => { 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) { function modifiers(event) {
let m = []; let m = [];
@ -28,6 +28,15 @@
m.push('Shift') m.push('Shift')
return '[' + m.join(' ') + ']'; 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) { function log(...args) {
console.log.apply(console, args); console.log.apply(console, args);
result += args.join(' ') + '\n'; result += args.join(' ') + '\n';

View file

@ -50,7 +50,8 @@ class CsvReporter implements Reporter {
if (test.ok() && !fixme) if (test.ok() && !fixme)
continue; continue;
const row = []; const row = [];
row.push(csvEscape(`${file.title} :: ${test.title}`)); const [, , , ...titles] = test.titlePath();
row.push(csvEscape(`${file.title} :: ${titles.join(' ')}`));
row.push(test.expectedStatus); row.push(test.expectedStatus);
row.push(test.outcome()); row.push(test.outcome());
if (fixme) { if (fixme) {
@ -67,7 +68,7 @@ class CsvReporter implements Reporter {
const csv = rows.map(r => r.join(',')).join('\n'); const csv = rows.map(r => r.join(',')).join('\n');
const reportFile = path.resolve(this._options.configDir, this._options.outputFile || 'test-results.csv'); const reportFile = path.resolve(this._options.configDir, this._options.outputFile || 'test-results.csv');
this._pendingWrite = (async () => { this._pendingWrite = (async () => {
await fs.mkdirSync(path.dirname(reportFile), { recursive: true }); await fs.promises.mkdir(path.dirname(reportFile), { recursive: true });
await fs.promises.writeFile(reportFile, csv); await fs.promises.writeFile(reportFile, csv);
})(); })();
} }

View file

@ -459,8 +459,13 @@ await page1.GotoAsync("about:blank?foo");`);
const cli = runCLI([`--save-storage=${storageFileName}`, `--save-har=${harFileName}`]); const cli = runCLI([`--save-storage=${storageFileName}`, `--save-har=${harFileName}`]);
await cli.waitFor(`import { test, expect } from '@playwright/test'`); await cli.waitFor(`import { test, expect } from '@playwright/test'`);
await cli.process.kill('SIGINT'); await cli.process.kill('SIGINT');
const { exitCode } = await cli.process.exited; const { exitCode, signal } = await cli.process.exited;
expect(exitCode).toBe(130); 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(storageFileName)).toBeTruthy();
expect(fs.existsSync(harFileName)).toBeTruthy(); expect(fs.existsSync(harFileName)).toBeTruthy();
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -41,6 +41,20 @@ it('should fire for fetches', async ({ page, server }) => {
expect(requests.length).toBe(2); expect(requests.length).toBe(2);
}); });
it('should fire for fetches with keepalive: true', {
annotation: {
type: 'issue',
description: 'https://github.com/microsoft/playwright/issues/34497'
}
}, async ({ page, server, browserName }) => {
it.fixme(browserName === 'firefox');
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html', { keepalive: true }));
expect(requests.length).toBe(2);
});
it('should report requests and responses handled by service worker', async ({ page, server, isAndroid, isElectron }) => { it('should report requests and responses handled by service worker', async ({ page, server, isAndroid, isElectron }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
it.fixme(isElectron); it.fixme(isElectron);

View file

@ -93,18 +93,18 @@ it('should report shiftKey', async ({ page, server, browserName, platform }) =>
const codeForKey = { 'Shift': 16, 'Alt': 18, 'Control': 17 }; const codeForKey = { 'Shift': 16, 'Alt': 18, 'Control': 17 };
for (const modifierKey in codeForKey) { for (const modifierKey in codeForKey) {
await keyboard.down(modifierKey); 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('!'); await keyboard.down('!');
// Shift+! will generate a keypress // Shift+! will generate a keypress
if (modifierKey === 'Shift') 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 else
expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); expect(await page.evaluate('getResult()')).toBe('Keydown: ! Digit1 STANDARD [' + modifierKey + ']');
await keyboard.up('!'); 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); 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'); await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard; const keyboard = page.keyboard;
await keyboard.down('Control'); 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'); 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(';'); 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(';'); 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'); 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'); 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 }) => { it('should send proper codes while typing', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.type('!'); await page.keyboard.type('!');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: ! Digit1 49 []', ['Keydown: ! Digit1 STANDARD []',
'Keypress: ! Digit1 33 33 []', 'Keypress: ! Digit1 STANDARD 33 []',
'Keyup: ! Digit1 49 []'].join('\n')); 'Keyup: ! Digit1 STANDARD []'].join('\n'));
await page.keyboard.type('^'); await page.keyboard.type('^');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: ^ Digit6 54 []', ['Keydown: ^ Digit6 STANDARD []',
'Keypress: ^ Digit6 94 94 []', 'Keypress: ^ Digit6 STANDARD 94 []',
'Keyup: ^ Digit6 54 []'].join('\n')); 'Keyup: ^ Digit6 STANDARD []'].join('\n'));
}); });
it('should send proper codes while typing with shift', async ({ page, server }) => { 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 keyboard.down('Shift');
await page.keyboard.type('~'); await page.keyboard.type('~');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: Shift ShiftLeft 16 [Shift]', ['Keydown: Shift ShiftLeft LEFT [Shift]',
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode 'Keydown: ~ Backquote STANDARD [Shift]',
'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode 'Keypress: ~ Backquote STANDARD 126 [Shift]',
'Keyup: ~ Backquote 192 [Shift]'].join('\n')); 'Keyup: ~ Backquote STANDARD [Shift]'].join('\n'));
await keyboard.up('Shift'); 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.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('+'); await page.keyboard.press('+');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: + Equal 187 []', // 192 is ` keyCode ['Keydown: + Equal STANDARD []',
'Keypress: + Equal 43 43 []', // 126 is ~ charCode 'Keypress: + Equal STANDARD 43 []',
'Keyup: + Equal 187 []'].join('\n')); 'Keyup: + Equal STANDARD []'].join('\n'));
}); });
it('should press shift plus', async ({ page, server }) => { it('should press shift plus', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('Shift++'); await page.keyboard.press('Shift++');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: Shift ShiftLeft 16 [Shift]', ['Keydown: Shift ShiftLeft LEFT [Shift]',
'Keydown: + Equal 187 [Shift]', // 192 is ` keyCode 'Keydown: + Equal STANDARD [Shift]',
'Keypress: + Equal 43 43 [Shift]', // 126 is ~ charCode 'Keypress: + Equal STANDARD 43 [Shift]',
'Keyup: + Equal 187 [Shift]', 'Keyup: + Equal STANDARD [Shift]',
'Keyup: Shift ShiftLeft 16 []'].join('\n')); 'Keyup: Shift ShiftLeft LEFT []'].join('\n'));
}); });
it('should support plus-separated modifiers', async ({ page, server }) => { it('should support plus-separated modifiers', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('Shift+~'); await page.keyboard.press('Shift+~');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: Shift ShiftLeft 16 [Shift]', ['Keydown: Shift ShiftLeft LEFT [Shift]',
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode 'Keydown: ~ Backquote STANDARD [Shift]',
'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode 'Keypress: ~ Backquote STANDARD 126 [Shift]',
'Keyup: ~ Backquote 192 [Shift]', 'Keyup: ~ Backquote STANDARD [Shift]',
'Keyup: Shift ShiftLeft 16 []'].join('\n')); 'Keyup: Shift ShiftLeft LEFT []'].join('\n'));
}); });
it('should support multiple plus-separated modifiers', async ({ page, server }) => { it('should support multiple plus-separated modifiers', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('Control+Shift+~'); await page.keyboard.press('Control+Shift+~');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: Control ControlLeft 17 [Control]', ['Keydown: Control ControlLeft LEFT [Control]',
'Keydown: Shift ShiftLeft 16 [Control Shift]', 'Keydown: Shift ShiftLeft LEFT [Control Shift]',
'Keydown: ~ Backquote 192 [Control Shift]', // 192 is ` keyCode 'Keydown: ~ Backquote STANDARD [Control Shift]',
'Keyup: ~ Backquote 192 [Control Shift]', 'Keyup: ~ Backquote STANDARD [Control Shift]',
'Keyup: Shift ShiftLeft 16 [Control]', 'Keyup: Shift ShiftLeft LEFT [Control]',
'Keyup: Control ControlLeft 17 []'].join('\n')); 'Keyup: Control ControlLeft LEFT []'].join('\n'));
}); });
it('should shift raw codes', async ({ page, server }) => { it('should shift raw codes', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/keyboard.html'); await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('Shift+Digit3'); await page.keyboard.press('Shift+Digit3');
expect(await page.evaluate('getResult()')).toBe( expect(await page.evaluate('getResult()')).toBe(
['Keydown: Shift ShiftLeft 16 [Shift]', ['Keydown: Shift ShiftLeft LEFT [Shift]',
'Keydown: # Digit3 51 [Shift]', // 51 is # keyCode 'Keydown: # Digit3 STANDARD [Shift]',
'Keypress: # Digit3 35 35 [Shift]', // 35 is # charCode 'Keypress: # Digit3 STANDARD 35 [Shift]',
'Keyup: # Digit3 51 [Shift]', 'Keyup: # Digit3 STANDARD [Shift]',
'Keyup: Shift ShiftLeft 16 []'].join('\n')); 'Keyup: Shift ShiftLeft LEFT []'].join('\n'));
}); });
it('should specify repeat property', async ({ page, server }) => { 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.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
expect(await page.evaluate('getResult()')).toBe(` expect(await page.evaluate('getResult()')).toBe(`
Keydown: Escape Escape 27 [] Keydown: Escape Escape STANDARD []
Keyup: Escape Escape 27 [] Keyup: Escape Escape STANDARD []
`.trim()); `.trim());
}); });

View file

@ -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'); 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]'); 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(\`<h1>New content</h1>\`);
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(\`<h1>hello world</h1>\`);
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`);
});

View file

@ -341,6 +341,36 @@ test('should update snapshot with the update-snapshots flag', async ({ runInline
expect(data.toString()).toBe(ACTUAL_SNAPSHOT); 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) => { test('should ignore text snapshot with the ignore-snapshots flag', async ({ runInlineTest }, testInfo) => {
const EXPECTED_SNAPSHOT = 'Hello world'; const EXPECTED_SNAPSHOT = 'Hello world';
const ACTUAL_SNAPSHOT = 'Hello world updated'; 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.exitCode).toBe(0);
expect(result.passed).toBe(1); 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);
});