chore: add pageSnapshot option (#34669)

This commit is contained in:
Simon Knott 2025-02-11 20:04:57 +01:00 committed by GitHub
parent d2e7e8acdb
commit 8ed2f4319e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 101 additions and 29 deletions

View File

@ -507,6 +507,27 @@ export default defineConfig({
Learn more about [automatic screenshots](../test-use-options.md#recording-options).
## property: TestOptions.pageSnapshot
* since: v1.51
- type: <[PageSnapshotMode]<"off"|"on"|"only-on-failure">>
Whether to automatically capture a ARIA snapshot of the page after each test. Defaults to `'only-on-failure'`.
* `'off'`: Do not capture page snapshots.
* `'on'`: Capture page snapshot after each test.
* `'only-on-failure'`: Capture page snapshot after each test failure.
**Usage**
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
pageSnapshot: 'on',
},
});
```
## property: TestOptions.storageState = %%-js-python-context-option-storage-state-%%
* since: v1.10

View File

@ -23,7 +23,7 @@ import { addInternalStackPrefix, asLocator, createGuid, debugMode, isString, jso
import { currentTestInfo } from './common/globals';
import { rootTestType } from './common/testType';
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
import type { Fixtures, PageSnapshotMode, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
import type { ContextReuseMode } from './common/config';
import type { TestInfoImpl, TestStepInternal } from './worker/testInfo';
import type { ApiCallData, ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
@ -59,7 +59,6 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
_optionContextReuseMode: ContextReuseMode,
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
_reuseContext: boolean,
_pageSnapshot: PageSnapshotOption,
};
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
@ -77,7 +76,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
screenshot: ['off', { scope: 'worker', option: true }],
video: ['off', { scope: 'worker', option: true }],
trace: ['off', { scope: 'worker', option: true }],
_pageSnapshot: ['off', { scope: 'worker', option: true }],
pageSnapshot: ['only-on-failure', { scope: 'worker', option: true }],
_browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
const options: LaunchOptions = {
@ -245,13 +244,13 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
playwright._defaultContextNavigationTimeout = undefined;
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
_setupArtifacts: [async ({ playwright, screenshot, _pageSnapshot }, use, testInfo) => {
_setupArtifacts: [async ({ playwright, screenshot, pageSnapshot }, use, testInfo) => {
// This fixture has a separate zero-timeout slot to ensure that artifact collection
// happens even after some fixtures or hooks time out.
// Now that default test timeout is known, we can replace zero with an actual value.
testInfo.setTimeout(testInfo.project.timeout);
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _pageSnapshot);
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, pageSnapshot);
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
const tracingGroupSteps: TestStepInternal[] = [];
@ -449,7 +448,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
});
type ScreenshotOption = PlaywrightWorkerOptions['screenshot'] | undefined;
type PageSnapshotOption = 'off' | 'on' | 'only-on-failure';
function normalizeVideoMode(video: VideoMode | 'retry-with-video' | { mode: VideoMode } | undefined): VideoMode {
if (!video)
@ -528,7 +526,7 @@ class SnapshotRecorder {
constructor(
private _artifactsRecorder: ArtifactsRecorder,
private _mode: ScreenshotMode | PageSnapshotOption,
private _mode: ScreenshotMode | PageSnapshotMode,
private _name: string,
private _contentType: string,
private _extension: string,
@ -620,7 +618,7 @@ class ArtifactsRecorder {
private _pageSnapshotRecorder: SnapshotRecorder;
private _screenshotRecorder: SnapshotRecorder;
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, pageSnapshot: PageSnapshotOption) {
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, pageSnapshot: PageSnapshotMode) {
this._playwright = playwright;
this._artifactsDir = artifactsDir;
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
@ -631,7 +629,7 @@ class ArtifactsRecorder {
});
this._pageSnapshotRecorder = new SnapshotRecorder(this, pageSnapshot, 'pageSnapshot', 'text/plain', '.ariasnapshot', async (page, path) => {
const ariaSnapshot = await page.locator('body').ariaSnapshot();
const ariaSnapshot = await page.locator('body').ariaSnapshot({ timeout: 5000 });
await fs.promises.writeFile(path, ariaSnapshot);
});
}

View File

@ -312,9 +312,9 @@ export class TestServerDispatcher implements TestServerInterface {
...(params.trace === 'off' ? { trace: 'off' } : {}),
...(params.video === 'on' || params.video === 'off' ? { video: params.video } : {}),
...(params.headed !== undefined ? { headless: !params.headed } : {}),
...(params.pageSnapshot ? { pageSnapshot: params.pageSnapshot } : undefined),
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
_pageSnapshot: params.pageSnapshot,
},
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),

View File

@ -6187,6 +6187,27 @@ export interface PlaywrightWorkerOptions {
* Learn more about [automatic screenshots](https://playwright.dev/docs/test-use-options#recording-options).
*/
screenshot: ScreenshotMode | { mode: ScreenshotMode } & Pick<PageScreenshotOptions, 'fullPage' | 'omitBackground'>;
/**
* Whether to automatically capture a ARIA snapshot of the page after each test. Defaults to `'only-on-failure'`.
* - `'off'`: Do not capture page snapshots.
* - `'on'`: Capture page snapshot after each test.
* - `'only-on-failure'`: Capture page snapshot after each test failure.
*
* **Usage**
*
* ```js
* // playwright.config.ts
* import { defineConfig } from '@playwright/test';
*
* export default defineConfig({
* use: {
* pageSnapshot: 'on',
* },
* });
* ```
*
*/
pageSnapshot: PageSnapshotMode;
/**
* Whether to record trace for each test. Defaults to `'off'`.
* - `'off'`: Do not record trace.
@ -6246,6 +6267,7 @@ export interface PlaywrightWorkerOptions {
}
export type ScreenshotMode = 'off' | 'on' | 'only-on-failure' | 'on-first-failure';
export type PageSnapshotMode = 'off' | 'on' | 'only-on-failure';
export type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';
export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';

View File

@ -128,7 +128,7 @@ test('should work with screenshot: on', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { screenshot: 'on' } };
module.exports = { use: { screenshot: 'on', pageSnapshot: 'off' } };
`,
}, { workers: 1 });
@ -168,7 +168,7 @@ test('should work with screenshot: only-on-failure', async ({ runInlineTest }, t
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { screenshot: 'only-on-failure' } };
module.exports = { use: { screenshot: 'only-on-failure', pageSnapshot: 'off' } };
`,
}, { workers: 1 });
@ -204,7 +204,7 @@ test('should work with screenshot: on-first-failure', async ({ runInlineTest },
'playwright.config.ts': `
module.exports = {
retries: 1,
use: { screenshot: 'on-first-failure' }
use: { screenshot: 'on-first-failure', pageSnapshot: 'off' }
};
`,
}, { workers: 1 });
@ -231,7 +231,7 @@ test('should work with screenshot: only-on-failure & fullPage', async ({ runInli
});
`,
'playwright.config.ts': `
module.exports = { use: { screenshot: { mode: 'only-on-failure', fullPage: true } } };
module.exports = { use: { screenshot: { mode: 'only-on-failure', fullPage: true }, pageSnapshot: 'off' } };
`,
}, { workers: 1 });
expect(result.exitCode).toBe(1);
@ -252,7 +252,7 @@ test('should work with trace: on', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { trace: 'on' } };
module.exports = { use: { trace: 'on', pageSnapshot: 'off' } };
`,
}, { workers: 1 });
@ -288,7 +288,7 @@ test('should work with trace: retain-on-failure', async ({ runInlineTest }, test
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-failure' } };
module.exports = { use: { trace: 'retain-on-failure', pageSnapshot: 'off' } };
`,
}, { workers: 1 });
@ -314,7 +314,7 @@ test('should work with trace: on-first-retry', async ({ runInlineTest }, testInf
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { trace: 'on-first-retry' } };
module.exports = { use: { trace: 'on-first-retry', pageSnapshot: 'off' } };
`,
}, { workers: 1, retries: 1 });
@ -340,7 +340,7 @@ test('should work with trace: on-all-retries', async ({ runInlineTest }, testInf
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { trace: 'on-all-retries' } };
module.exports = { use: { trace: 'on-all-retries', pageSnapshot: 'off' } };
`,
}, { workers: 1, retries: 2 });
@ -376,7 +376,7 @@ test('should work with trace: retain-on-first-failure', async ({ runInlineTest }
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-first-failure' } };
module.exports = { use: { trace: 'retain-on-first-failure', pageSnapshot: 'off' } };
`,
}, { workers: 1, retries: 2 });
@ -421,11 +421,11 @@ test('should take screenshot when page is closed in afterEach', async ({ runInli
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-fails', 'test-failed-1.png'))).toBeTruthy();
});
test('should work with _pageSnapshot: on', async ({ runInlineTest }, testInfo) => {
test('should work with pageSnapshot: on', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { _pageSnapshot: 'on' } };
module.exports = { use: { pageSnapshot: 'on' } };
`,
}, { workers: 1 });
@ -461,11 +461,11 @@ test('should work with _pageSnapshot: on', async ({ runInlineTest }, testInfo) =
]);
});
test('should work with _pageSnapshot: only-on-failure', async ({ runInlineTest }, testInfo) => {
test('should work with pageSnapshot: only-on-failure', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...testFiles,
'playwright.config.ts': `
module.exports = { use: { _pageSnapshot: 'only-on-failure' } };
module.exports = { use: { pageSnapshot: 'only-on-failure' } };
`,
}, { workers: 1 });

View File

@ -485,7 +485,7 @@ test('should work with video: retain-on-failure', async ({ runInlineTest }) => {
test('should work with video: on-first-retry', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { video: 'on-first-retry' }, retries: 1, name: 'chromium' };
module.exports = { use: { video: 'on-first-retry', pageSnapshot: 'off' }, retries: 1, name: 'chromium' };
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';

View File

@ -59,7 +59,7 @@ test('should stop tracing with trace: on-first-retry, when not retrying', async
test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: 'on' } };
module.exports = { use: { trace: 'on', pageSnapshot: 'off' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
@ -290,7 +290,7 @@ test('should work in serial mode', async ({ runInlineTest }, testInfo) => {
test('should not override trace file in afterAll', async ({ runInlineTest, server }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-failure' } };
module.exports = { use: { trace: 'retain-on-failure', pageSnapshot: 'off' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
@ -642,7 +642,7 @@ test('should expand expect.toPass', async ({ runInlineTest }, testInfo) => {
test('should show non-expect error in trace', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: { mode: 'on' } } };
module.exports = { use: { trace: { mode: 'on' }, pageSnapshot: 'off' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
@ -850,7 +850,7 @@ test('should record nested steps, even after timeout', async ({ runInlineTest },
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: { trace: { mode: 'on' } },
use: { trace: { mode: 'on' }, pageSnapshot: 'off' },
timeout: 5000,
};
`,
@ -1156,6 +1156,9 @@ test('should record trace for manually created context in a failed test', async
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31541' });
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { pageSnapshot: 'off' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('fail', async ({ browser }) => {
@ -1234,6 +1237,9 @@ test('should record trace after fixture teardown timeout', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30718' },
}, async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { pageSnapshot: 'off' } };
`,
'a.spec.ts': `
import { test as base, expect } from '@playwright/test';
const test = base.extend({

View File

@ -192,6 +192,9 @@ test('should not report nested after hooks', async ({ runInlineTest }) => {
'playwright.config.ts': `
module.exports = {
reporter: './reporter',
use: {
pageSnapshot: 'off',
}
};
`,
'a.test.ts': `
@ -577,6 +580,9 @@ test('should not mark page.close as failed when page.click fails', async ({ runI
'playwright.config.ts': `
module.exports = {
reporter: './reporter',
use: {
pageSnapshot: 'off',
}
};
`,
'a.test.ts': `
@ -1230,6 +1236,9 @@ test('should report api step failure', async ({ runInlineTest }) => {
'playwright.config.ts': `
module.exports = {
reporter: './reporter',
use: {
pageSnapshot: 'off',
}
};
`,
'a.test.ts': `

View File

@ -619,6 +619,9 @@ test('should write missing expectations locally twice and attach them', async ({
const result = await runInlineTest({
...playwrightConfig({
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
use: {
pageSnapshot: 'off',
},
}),
'a.spec.js': `
const { test, expect } = require('@playwright/test');
@ -688,7 +691,12 @@ test('should attach missing expectations to right step', async ({ runInlineTest
}
module.exports = Reporter;
`,
...playwrightConfig({ reporter: [['dot'], ['./reporter']] }),
...playwrightConfig({
reporter: [['dot'], ['./reporter']],
use: {
pageSnapshot: 'off',
}
}),
'a.spec.js': `
const { test, expect } = require('@playwright/test');
test('is a test', async ({ page }) => {
@ -1118,6 +1126,9 @@ test('should attach expected/actual/diff when sizes are different', async ({ run
const result = await runInlineTest({
...playwrightConfig({
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
use: {
pageSnapshot: 'off',
},
}),
'__screenshots__/a.spec.js/snapshot.png': createImage(2, 2),
'a.spec.js': `
@ -1376,6 +1387,9 @@ test('should trim+sanitize attachment names and paths', async ({ runInlineTest }
const result = await runInlineTest({
...playwrightConfig({
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
use: {
pageSnapshot: 'off',
}
}),
'a.spec.js': `
const { test, expect } = require('@playwright/test');

View File

@ -234,11 +234,13 @@ export interface PlaywrightWorkerOptions {
launchOptions: Omit<LaunchOptions, 'tracesDir'>;
connectOptions: ConnectOptions | undefined;
screenshot: ScreenshotMode | { mode: ScreenshotMode } & Pick<PageScreenshotOptions, 'fullPage' | 'omitBackground'>;
pageSnapshot: PageSnapshotMode;
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean, screenshots?: boolean, sources?: boolean, attachments?: boolean };
video: VideoMode | /** deprecated */ 'retry-with-video' | { mode: VideoMode, size?: ViewportSize };
}
export type ScreenshotMode = 'off' | 'on' | 'only-on-failure' | 'on-first-failure';
export type PageSnapshotMode = 'off' | 'on' | 'only-on-failure';
export type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';
export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';