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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -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,

View file

@ -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;

View file

@ -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();

View file

@ -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) {

View file

@ -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<MatcherResult<string | RegExp, string>> {
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;

View file

@ -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,

View file

@ -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

View file

@ -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';

View file

@ -50,7 +50,8 @@ class CsvReporter implements Reporter {
if (test.ok() && !fixme)
continue;
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.outcome());
if (fixme) {
@ -67,7 +68,7 @@ class CsvReporter implements Reporter {
const csv = rows.map(r => r.join(',')).join('\n');
const reportFile = path.resolve(this._options.configDir, this._options.outputFile || 'test-results.csv');
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);
})();
}

View file

@ -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();
});

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);
});
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.fixme(isAndroid);
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 };
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());
});

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');
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);
});
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);
});