diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index c285430616..17d9ea3f7b 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -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 diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 2b19dc53ee..9b0fb456ab 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -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 = ({ @@ -77,7 +76,7 @@ const playwrightFixtures: Fixtures = ({ 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 = ({ 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 = ({ }); 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); }); } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index d1a2c26166..efa19c1882 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -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 } : {}), diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index f44acbd015..ab23b480b2 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -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; + /** + * 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'; diff --git a/tests/playwright-test/playwright.artifacts.spec.ts b/tests/playwright-test/playwright.artifacts.spec.ts index 0360ca7bce..5a8a6fcbef 100644 --- a/tests/playwright-test/playwright.artifacts.spec.ts +++ b/tests/playwright-test/playwright.artifacts.spec.ts @@ -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 }); diff --git a/tests/playwright-test/playwright.spec.ts b/tests/playwright-test/playwright.spec.ts index 8cd83c40b1..fdf050680f 100644 --- a/tests/playwright-test/playwright.spec.ts +++ b/tests/playwright-test/playwright.spec.ts @@ -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'; diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 88995c6f1c..2024cb60eb 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -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({ diff --git a/tests/playwright-test/test-step.spec.ts b/tests/playwright-test/test-step.spec.ts index be72e51dd1..d35e9de57a 100644 --- a/tests/playwright-test/test-step.spec.ts +++ b/tests/playwright-test/test-step.spec.ts @@ -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': ` diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 65e348cc52..2e1c3a4585 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -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'); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 04121cb281..40c505950b 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -234,11 +234,13 @@ export interface PlaywrightWorkerOptions { launchOptions: Omit; connectOptions: ConnectOptions | undefined; screenshot: ScreenshotMode | { mode: ScreenshotMode } & Pick; + 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';