From d6babae0c294d002567b5fb1d2105ad0af4c22ac Mon Sep 17 00:00:00 2001 From: Vincenzo Gasparo Date: Fri, 31 Oct 2025 12:17:28 +0100 Subject: [PATCH 01/10] feat(toMatchAriaSnapshot): add exact saving option Fixes #38074, references #38074. --- docs/src/api/class-locatorassertions.md | 7 +++ .../src/matchers/toMatchAriaSnapshot.ts | 4 +- packages/playwright/types/test.d.ts | 5 +++ .../aria-snapshot-file.spec.ts | 45 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index c2edbf7533c6c..298f6d2a01139 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2396,6 +2396,13 @@ await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'body.aria.yml' } Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not specified. +### option: LocatorAssertions.toMatchAriaSnapshot#2.exact +* since: v1.57 +* langs: js +- `exact` <[boolean]> + +When `true`, saves the raw accessibility tree content without using regexes. + ### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%% * since: v1.50 diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 1075d8d86d305..ad61b6a7f4300 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -35,6 +35,7 @@ type ToMatchAriaSnapshotExpected = { name?: string; path?: string; timeout?: number; + exact?: boolean; } | string; export async function toMatchAriaSnapshot( @@ -122,7 +123,8 @@ export async function toMatchAriaSnapshot( generateMissingBaseline) { if (expectedPath) { await fs.promises.mkdir(path.dirname(expectedPath), { recursive: true }); - await fs.promises.writeFile(expectedPath, typedReceived.regex, 'utf8'); + const contentToSave = (expectedParam && typeof expectedParam !== 'string' && expectedParam.exact) ? typedReceived.raw : typedReceived.regex; + await fs.promises.writeFile(expectedPath, contentToSave, 'utf8'); const relativePath = path.relative(process.cwd(), expectedPath); if (updateSnapshots === 'missing') { const message = `A snapshot doesn't exist at ${relativePath}, writing actual.`; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index b5babaa8d908e..95b9c709173d0 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9536,6 +9536,11 @@ interface LocatorAssertions { * @param options */ toMatchAriaSnapshot(options?: { + /** + * When `true`, saves the raw accessibility tree content without using regexes. + */ + exact?: boolean; + /** * Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not * specified. diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts index f568dc5c310d5..2909922c477c0 100644 --- a/tests/playwright-test/aria-snapshot-file.spec.ts +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -251,3 +251,48 @@ test('should respect config.expect.toMatchAriaSnapshot.pathTemplate', async ({ r expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); + +test('should save aria snapshot in raw mode', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world 123

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', exact: true }); + expect(fs.readFileSync('a.spec.ts-snapshots/test.aria.yml', { encoding: 'utf8' })).toEqual('- heading "hello world 123" [level=1]'); + }); + ` + }, { 'update-snapshots': 'all' }); + expect(result.exitCode).toBe(0); +}); + +test('should save aria snapshot in regex mode', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world 123

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', exact: false }); + expect(fs.readFileSync('a.spec.ts-snapshots/test.aria.yml', { encoding: 'utf8' })).toEqual('- heading /hello world \\\\d+/ [level=1]'); + }); + ` + }, { 'update-snapshots': 'all' }); + expect(result.exitCode).toBe(0); +}); + +test('should save aria snapshot in regex mode without exact argument', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + import fs from 'fs'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world 123

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml' }); + expect(fs.readFileSync('a.spec.ts-snapshots/test.aria.yml', { encoding: 'utf8' })).toEqual('- heading /hello world \\\\d+/ [level=1]'); + }); + ` + }, { 'update-snapshots': 'all' }); + expect(result.exitCode).toBe(0); +}); From ab829aa0a82417855ee50607e6887d1149ca3141 Mon Sep 17 00:00:00 2001 From: Vincenzo Gasparo Date: Mon, 3 Nov 2025 12:18:33 +0100 Subject: [PATCH 02/10] feat: change update type, support other overload --- docs/src/api/class-locatorassertions.md | 13 ++++++++++--- .../playwright/src/matchers/toMatchAriaSnapshot.ts | 13 +++++++++---- packages/playwright/types/test.d.ts | 12 +++++++----- tests/playwright-test/aria-snapshot-file.spec.ts | 4 ++-- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 298f6d2a01139..8d52f6933caaa 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2373,6 +2373,11 @@ assertThat(page.locator("body")).matchesAriaSnapshot(""" ### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%% * since: v1.49 +### option: LocatorAssertions.toMatchAriaSnapshot.update +* since: v1.57 +* langs: js +- `update` <["raw"]|["relaxed"]> + ## async method: LocatorAssertions.toMatchAriaSnapshot#2 * since: v1.50 * langs: js @@ -2396,12 +2401,14 @@ await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'body.aria.yml' } Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not specified. -### option: LocatorAssertions.toMatchAriaSnapshot#2.exact +### option: LocatorAssertions.toMatchAriaSnapshot#2.update * since: v1.57 * langs: js -- `exact` <[boolean]> +- `update` <["raw"]|["relaxed"]> -When `true`, saves the raw accessibility tree content without using regexes. +Controls how the snapshot is updated. +- `'raw'` saves the raw accessibility tree content without using regexes. +- `'relaxed'` (default) saves the accessibility tree content with regexes to make it more resilient to minor changes. ### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%% * since: v1.50 diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index ad61b6a7f4300..1a920a551d0a3 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -35,14 +35,14 @@ type ToMatchAriaSnapshotExpected = { name?: string; path?: string; timeout?: number; - exact?: boolean; + update?: 'raw' | 'relaxed'; } | string; export async function toMatchAriaSnapshot( this: ExpectMatcherState, locator: LocatorEx, expectedParam?: ToMatchAriaSnapshotExpected, - options: { timeout?: number } = {}, + options: { timeout?: number, update?: 'raw' | 'relaxed'; } = {}, ): Promise> { const matcherName = 'toMatchAriaSnapshot'; @@ -58,9 +58,12 @@ export async function toMatchAriaSnapshot( let expected: string; let timeout: number; let expectedPath: string | undefined; + let updateMode: 'raw' | 'relaxed' = 'relaxed'; + if (isString(expectedParam)) { expected = expectedParam; timeout = options.timeout ?? this.timeout; + updateMode = options.update ?? 'relaxed'; } else { const legacyPath = testInfo._resolveSnapshotPaths('aria', expectedParam?.name, 'dontUpdateSnapshotIndex', '.yml').absoluteSnapshotPath; expectedPath = testInfo._resolveSnapshotPaths('aria', expectedParam?.name, 'updateSnapshotIndex').absoluteSnapshotPath; @@ -70,6 +73,7 @@ export async function toMatchAriaSnapshot( expectedPath = legacyPath; expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => ''); timeout = expectedParam?.timeout ?? this.timeout; + updateMode = expectedParam?.update ?? 'relaxed'; } const generateMissingBaseline = updateSnapshots === 'missing' && !expected; @@ -123,7 +127,7 @@ export async function toMatchAriaSnapshot( generateMissingBaseline) { if (expectedPath) { await fs.promises.mkdir(path.dirname(expectedPath), { recursive: true }); - const contentToSave = (expectedParam && typeof expectedParam !== 'string' && expectedParam.exact) ? typedReceived.raw : typedReceived.regex; + const contentToSave = updateMode === 'raw' ? typedReceived.raw : typedReceived.regex; await fs.promises.writeFile(expectedPath, contentToSave, 'utf8'); const relativePath = path.relative(process.cwd(), expectedPath); if (updateSnapshots === 'missing') { @@ -137,7 +141,8 @@ export async function toMatchAriaSnapshot( } return { pass: true, message: () => '', name: 'toMatchAriaSnapshot' }; } else { - const suggestedRebaseline = `\`\n${escapeTemplateString(indent(typedReceived.regex, '{indent} '))}\n{indent}\``; + const contentToSave = updateMode === 'raw' ? typedReceived.raw : typedReceived.regex; + const suggestedRebaseline = `\`\n${escapeTemplateString(indent(contentToSave, '{indent} '))}\n{indent}\``; if (updateSnapshots === 'missing') { const message = 'A snapshot is not provided, generating new baseline.'; testInfo._hasNonRetriableError = true; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 95b9c709173d0..936b40fe65ec7 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9518,6 +9518,8 @@ interface LocatorAssertions { * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ timeout?: number; + + update?: "raw"|"relaxed"; }): Promise; /** @@ -9536,11 +9538,6 @@ interface LocatorAssertions { * @param options */ toMatchAriaSnapshot(options?: { - /** - * When `true`, saves the raw accessibility tree content without using regexes. - */ - exact?: boolean; - /** * Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not * specified. @@ -9551,6 +9548,11 @@ interface LocatorAssertions { * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ timeout?: number; + + /** + * Controls how the snapshot is updated. + */ + update?: "raw"|"relaxed"; }): Promise; /** diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts index 2909922c477c0..b6cabbdc43ea0 100644 --- a/tests/playwright-test/aria-snapshot-file.spec.ts +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -259,7 +259,7 @@ test('should save aria snapshot in raw mode', async ({ runInlineTest }) => { import fs from 'fs'; test('test', async ({ page }) => { await page.setContent(\`

hello world 123

\`); - await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', exact: true }); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', update: 'raw' }); expect(fs.readFileSync('a.spec.ts-snapshots/test.aria.yml', { encoding: 'utf8' })).toEqual('- heading "hello world 123" [level=1]'); }); ` @@ -274,7 +274,7 @@ test('should save aria snapshot in regex mode', async ({ runInlineTest }) => { import fs from 'fs'; test('test', async ({ page }) => { await page.setContent(\`

hello world 123

\`); - await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', exact: false }); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.aria.yml', update: 'relaxed' }); expect(fs.readFileSync('a.spec.ts-snapshots/test.aria.yml', { encoding: 'utf8' })).toEqual('- heading /hello world \\\\d+/ [level=1]'); }); ` From 2a880710ae1a6cc1454642ee1f09055bf925feed Mon Sep 17 00:00:00 2001 From: samueldebenedictis Date: Mon, 3 Nov 2025 13:06:45 +0100 Subject: [PATCH 03/10] feat(toMatchAriaSnapshot): add tests raw update --- tests/page/to-match-aria-snapshot.spec.ts | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/page/to-match-aria-snapshot.spec.ts b/tests/page/to-match-aria-snapshot.spec.ts index ebad90f49f104..c6c3db8f57d55 100644 --- a/tests/page/to-match-aria-snapshot.spec.ts +++ b/tests/page/to-match-aria-snapshot.spec.ts @@ -878,3 +878,30 @@ test('treat bad regex as a string', async ({ page }) => { expect(stripAnsi(error.message)).toContain('- - /url: /[a/'); expect(stripAnsi(error.message)).toContain('+ - /url: /foo'); }); + +test('should not match regex with raw update', async ({ page }) => { + { + await page.setContent(`

Issues 12

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 12" + `, { update: 'raw' }); + } + { + await page.setContent(`

Issues 1/2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1/2" + `, { update: 'raw' }); + } + { + await page.setContent(`

Issues 1[

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1[" + `, { update: 'raw' }); + } + { + await page.setContent(`

Issues 1]]2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1]]2" + `, { update: 'raw' }); + } +}); From 47691f4897b6a90edcaa11d5666b4c6ef56f49f5 Mon Sep 17 00:00:00 2001 From: samueldebenedictis Date: Mon, 3 Nov 2025 15:05:22 +0100 Subject: [PATCH 04/10] feat(toMatchAriaSnapshot): add tests inline update --- .../update-aria-snapshot.spec.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/playwright-test/update-aria-snapshot.spec.ts b/tests/playwright-test/update-aria-snapshot.spec.ts index 203b5c27e43a8..936e5fccd088c 100644 --- a/tests/playwright-test/update-aria-snapshot.spec.ts +++ b/tests/playwright-test/update-aria-snapshot.spec.ts @@ -706,4 +706,84 @@ test.describe('update-source-method', () => { const result2 = await runInlineTest({}); expect(result2.exitCode).toBe(0); }); + + test('should update when option raw is specified', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + '.git/marker': '', + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); + await expect(page.locator('body')).toMatchAriaSnapshot('', + { + timeout: 2500, + update: 'raw' + }); + }); + ` + }); + + expect(result.exitCode).toBe(1); + const patchPath = testInfo.outputPath('test-results/rebaselines.patch'); + const data = fs.readFileSync(patchPath, 'utf-8'); + expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts +--- a/a.spec.ts ++++ b/a.spec.ts +@@ -2,7 +2,9 @@ + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); +- await expect(page.locator('body')).toMatchAriaSnapshot('', ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - textbox: hello world 123 ++ \`, + { + timeout: 2500, + update: 'raw' +`); + + execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() }); + const result2 = await runInlineTest({}); + expect(result2.exitCode).toBe(0); + }); + + test('should update when option relaxed is specified', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + '.git/marker': '', + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); + await expect(page.locator('body')).toMatchAriaSnapshot('', + { + timeout: 2500, + update: 'relaxed' + }); + }); + ` + }); + + expect(result.exitCode).toBe(1); + const patchPath = testInfo.outputPath('test-results/rebaselines.patch'); + const data = fs.readFileSync(patchPath, 'utf-8'); + expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts +--- a/a.spec.ts ++++ b/a.spec.ts +@@ -2,7 +2,9 @@ + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`\`); +- await expect(page.locator('body')).toMatchAriaSnapshot('', ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - textbox: /hello world \\\\d+/ ++ \`, + { + timeout: 2500, + update: 'relaxed' +`); + + execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() }); + const result2 = await runInlineTest({}); + expect(result2.exitCode).toBe(0); + }); }); From 9d4cb75e0fc0d3391c74051c54d864498c4b8be1 Mon Sep 17 00:00:00 2001 From: samueldebenedictis Date: Mon, 3 Nov 2025 15:08:34 +0100 Subject: [PATCH 05/10] feat(toMatchAriaSnapshot): add tests update param --- tests/page/to-match-aria-snapshot.spec.ts | 50 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/page/to-match-aria-snapshot.spec.ts b/tests/page/to-match-aria-snapshot.spec.ts index c6c3db8f57d55..1783775ee18bc 100644 --- a/tests/page/to-match-aria-snapshot.spec.ts +++ b/tests/page/to-match-aria-snapshot.spec.ts @@ -879,7 +879,7 @@ test('treat bad regex as a string', async ({ page }) => { expect(stripAnsi(error.message)).toContain('+ - /url: /foo'); }); -test('should not match regex with raw update', async ({ page }) => { +test('should match with update param', async ({ page }) => { { await page.setContent(`

Issues 12

`); await expect(page.locator('body')).toMatchAriaSnapshot(` @@ -904,4 +904,52 @@ test('should not match regex with raw update', async ({ page }) => { - heading "Issues 1]]2" `, { update: 'raw' }); } + { + await page.setContent(`

Issues 12

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading ${/Issues \d+/} + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1/2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading ${/Issues 1[/]2/} + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1[

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading ${/Issues 1\[/} + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1]]2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading ${/Issues 1[\]]]2/} + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 12

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 12" + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1/2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1/2" + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1[

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1[" + `, { update: 'relaxed' }); + } + { + await page.setContent(`

Issues 1]]2

`); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "Issues 1]]2" + `, { update: 'relaxed' }); + } }); From b15dee6860d5596b3b61d29b602f1595840f3976 Mon Sep 17 00:00:00 2001 From: Vincenzo Gasparo Date: Mon, 3 Nov 2025 15:19:04 +0100 Subject: [PATCH 06/10] chore: add assertions-aria-snapshot-update param --- docs/src/api/class-locatorassertions.md | 14 ++------------ docs/src/api/params.md | 8 ++++++++ packages/playwright/types/test.d.ts | 3 +++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 8d52f6933caaa..9751ce6aeb419 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2373,10 +2373,7 @@ assertThat(page.locator("body")).matchesAriaSnapshot(""" ### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%% * since: v1.49 -### option: LocatorAssertions.toMatchAriaSnapshot.update -* since: v1.57 -* langs: js -- `update` <["raw"]|["relaxed"]> +### option: LocatorAssertions.toMatchAriaSnapshot.update = %%-assertions-aria-snapshot-update-%% ## async method: LocatorAssertions.toMatchAriaSnapshot#2 * since: v1.50 @@ -2401,14 +2398,7 @@ await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'body.aria.yml' } Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not specified. -### option: LocatorAssertions.toMatchAriaSnapshot#2.update -* since: v1.57 -* langs: js -- `update` <["raw"]|["relaxed"]> - -Controls how the snapshot is updated. -- `'raw'` saves the raw accessibility tree content without using regexes. -- `'relaxed'` (default) saves the accessibility tree content with regexes to make it more resilient to minor changes. +### option: LocatorAssertions.toMatchAriaSnapshot#2.update = %%-assertions-aria-snapshot-update-%% ### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%% * since: v1.50 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e0141bec4436b..93f6d23dadb22 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1893,3 +1893,11 @@ In this config: 1. Since `snapshotPathTemplate` resolves to relative path, it will be resolved relative to `configDir`. 1. Forward slashes `"/"` can be used as path separators on any platform. +## assertions-aria-snapshot-update +* langs: js +* since: v1.57.0 +- `update` <["raw"]|["relaxed"]> + +Controls how the snapshot is updated. +- `'raw'` saves the raw accessibility tree content without using regexes. +- `'relaxed'` (default) saves the accessibility tree content with regexes to make it more resilient to minor changes. diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 936b40fe65ec7..a622dee497427 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9519,6 +9519,9 @@ interface LocatorAssertions { */ timeout?: number; + /** + * Controls how the snapshot is updated. + */ update?: "raw"|"relaxed"; }): Promise; From 237abbfebeb706ffdc630ec94f6ca88a02c0031b Mon Sep 17 00:00:00 2001 From: Vincenzo Gasparo Date: Tue, 4 Nov 2025 09:55:22 +0100 Subject: [PATCH 07/10] chore: update assertions-aria-snapshot-update doc, edit toMatchAriaSnapshot, update tests --- docs/src/api/params.md | 6 +++--- .../src/matchers/toMatchAriaSnapshot.ts | 13 ++++++------- packages/playwright/types/test.d.ts | 16 ++++++++++++++-- tests/page/to-match-aria-snapshot.spec.ts | 14 +++++++------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 93f6d23dadb22..eb06d26d3ecd6 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1898,6 +1898,6 @@ In this config: * since: v1.57.0 - `update` <["raw"]|["relaxed"]> -Controls how the snapshot is updated. -- `'raw'` saves the raw accessibility tree content without using regexes. -- `'relaxed'` (default) saves the accessibility tree content with regexes to make it more resilient to minor changes. +Defines how snapshots are written when updating via [TestConfig.updateSnapshots](../test-api/class-testconfig.md#test-config-update-snapshots). Defaults to `'relaxed'`. +* `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for numbers) to reduce flakiness from minor changes. +* `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values change. diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 1a920a551d0a3..6c16649c8d59a 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -58,12 +58,11 @@ export async function toMatchAriaSnapshot( let expected: string; let timeout: number; let expectedPath: string | undefined; - let updateMode: 'raw' | 'relaxed' = 'relaxed'; + let updateMode = options.update ?? 'relaxed'; if (isString(expectedParam)) { expected = expectedParam; timeout = options.timeout ?? this.timeout; - updateMode = options.update ?? 'relaxed'; } else { const legacyPath = testInfo._resolveSnapshotPaths('aria', expectedParam?.name, 'dontUpdateSnapshotIndex', '.yml').absoluteSnapshotPath; expectedPath = testInfo._resolveSnapshotPaths('aria', expectedParam?.name, 'updateSnapshotIndex').absoluteSnapshotPath; @@ -73,7 +72,7 @@ export async function toMatchAriaSnapshot( expectedPath = legacyPath; expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => ''); timeout = expectedParam?.timeout ?? this.timeout; - updateMode = expectedParam?.update ?? 'relaxed'; + updateMode = expectedParam?.update ?? updateMode; } const generateMissingBaseline = updateSnapshots === 'missing' && !expected; @@ -121,14 +120,15 @@ export async function toMatchAriaSnapshot( if (errorMessage) return { pass: this.isNot, message, name: 'toMatchAriaSnapshot', expected }; + const newSnapshot = updateMode === 'raw' ? typedReceived.raw : typedReceived.regex; + if (!this.isNot) { if ((updateSnapshots === 'all') || (updateSnapshots === 'changed' && pass === this.isNot) || generateMissingBaseline) { if (expectedPath) { await fs.promises.mkdir(path.dirname(expectedPath), { recursive: true }); - const contentToSave = updateMode === 'raw' ? typedReceived.raw : typedReceived.regex; - await fs.promises.writeFile(expectedPath, contentToSave, 'utf8'); + await fs.promises.writeFile(expectedPath, newSnapshot, 'utf8'); const relativePath = path.relative(process.cwd(), expectedPath); if (updateSnapshots === 'missing') { const message = `A snapshot doesn't exist at ${relativePath}, writing actual.`; @@ -141,8 +141,7 @@ export async function toMatchAriaSnapshot( } return { pass: true, message: () => '', name: 'toMatchAriaSnapshot' }; } else { - const contentToSave = updateMode === 'raw' ? typedReceived.raw : typedReceived.regex; - const suggestedRebaseline = `\`\n${escapeTemplateString(indent(contentToSave, '{indent} '))}\n{indent}\``; + const suggestedRebaseline = `\`\n${escapeTemplateString(indent(newSnapshot, '{indent} '))}\n{indent}\``; if (updateSnapshots === 'missing') { const message = 'A snapshot is not provided, generating new baseline.'; testInfo._hasNonRetriableError = true; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index a622dee497427..22e63ced2f711 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9520,7 +9520,13 @@ interface LocatorAssertions { timeout?: number; /** - * Controls how the snapshot is updated. + * Defines how snapshots are written when updating via + * [TestConfig.updateSnapshots](https://playwright.dev/docs/test-api/class-testconfig#test-config-update-snapshots). Defaults to + * `'relaxed'`. + * - `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for + * numbers) to reduce flakiness from minor changes. + * - `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values + * change. */ update?: "raw"|"relaxed"; }): Promise; @@ -9553,7 +9559,13 @@ interface LocatorAssertions { timeout?: number; /** - * Controls how the snapshot is updated. + * Defines how snapshots are written when updating via + * [TestConfig.updateSnapshots](https://playwright.dev/docs/test-api/class-testconfig#test-config-update-snapshots). Defaults to + * `'relaxed'`. + * - `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for + * numbers) to reduce flakiness from minor changes. + * - `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values + * change. */ update?: "raw"|"relaxed"; }): Promise; diff --git a/tests/page/to-match-aria-snapshot.spec.ts b/tests/page/to-match-aria-snapshot.spec.ts index 1783775ee18bc..e041a489deec4 100644 --- a/tests/page/to-match-aria-snapshot.spec.ts +++ b/tests/page/to-match-aria-snapshot.spec.ts @@ -914,42 +914,42 @@ test('should match with update param', async ({ page }) => { await page.setContent(`

Issues 1/2

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading ${/Issues 1[/]2/} - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 1[

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading ${/Issues 1\[/} - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 1]]2

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading ${/Issues 1[\]]]2/} - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 12

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading "Issues 12" - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 1/2

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading "Issues 1/2" - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 1[

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading "Issues 1[" - `, { update: 'relaxed' }); + `); } { await page.setContent(`

Issues 1]]2

`); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading "Issues 1]]2" - `, { update: 'relaxed' }); + `); } }); From 69dbdc4f37471eca4c1e26e75b1069429fbfe716 Mon Sep 17 00:00:00 2001 From: Vincenzo Gasparo Date: Wed, 5 Nov 2025 09:51:12 +0100 Subject: [PATCH 08/10] chore: edit property: TestConfig.updateSnapshots in params.md --- docs/src/api/params.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index eb06d26d3ecd6..0894b1cfe7c13 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1894,10 +1894,9 @@ In this config: 1. Forward slashes `"/"` can be used as path separators on any platform. ## assertions-aria-snapshot-update -* langs: js -* since: v1.57.0 +* since: v1.57 - `update` <["raw"]|["relaxed"]> -Defines how snapshots are written when updating via [TestConfig.updateSnapshots](../test-api/class-testconfig.md#test-config-update-snapshots). Defaults to `'relaxed'`. +Defines how snapshots are written when updating via [`property: TestConfig.updateSnapshots`]. Defaults to `'relaxed'`. * `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for numbers) to reduce flakiness from minor changes. * `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values change. From 17ace9becf37fb1e3b34d0838ba6b407c7ea10a3 Mon Sep 17 00:00:00 2001 From: samueldebenedictis Date: Wed, 5 Nov 2025 10:54:33 +0100 Subject: [PATCH 09/10] chore(toMatchAriaSnapshot): remove reference in docs --- docs/src/api/params.md | 2 +- packages/playwright/types/test.d.ts | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 0894b1cfe7c13..ccd0d04353718 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1897,6 +1897,6 @@ In this config: * since: v1.57 - `update` <["raw"]|["relaxed"]> -Defines how snapshots are written when updating via [`property: TestConfig.updateSnapshots`]. Defaults to `'relaxed'`. +Defines how snapshots are written when updating. Defaults to `'relaxed'`. * `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for numbers) to reduce flakiness from minor changes. * `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values change. diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 22e63ced2f711..f6ffd37cfcd79 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9520,9 +9520,7 @@ interface LocatorAssertions { timeout?: number; /** - * Defines how snapshots are written when updating via - * [TestConfig.updateSnapshots](https://playwright.dev/docs/test-api/class-testconfig#test-config-update-snapshots). Defaults to - * `'relaxed'`. + * Defines how snapshots are written when updating. Defaults to `'relaxed'`. * - `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for * numbers) to reduce flakiness from minor changes. * - `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values @@ -9559,9 +9557,7 @@ interface LocatorAssertions { timeout?: number; /** - * Defines how snapshots are written when updating via - * [TestConfig.updateSnapshots](https://playwright.dev/docs/test-api/class-testconfig#test-config-update-snapshots). Defaults to - * `'relaxed'`. + * Defines how snapshots are written when updating. Defaults to `'relaxed'`. * - `'relaxed'` - Writes an ARIA snapshot that uses flexible patterns (for example, regular expressions for * numbers) to reduce flakiness from minor changes. * - `'raw'` - Writes the exact ARIA snapshot without transformations. This is strict and will fail if values From bb0c71a5a93b5707932a7ba7f6f6d5b5484bd5ba Mon Sep 17 00:00:00 2001 From: samueldebenedictis Date: Mon, 17 Nov 2025 15:41:47 +0100 Subject: [PATCH 10/10] chore: add langs js to toMatchAriaSnapshot doc --- docs/src/api/class-locatorassertions.md | 2 ++ docs/src/api/params.md | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 9751ce6aeb419..d08fc80345c0f 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2374,6 +2374,7 @@ assertThat(page.locator("body")).matchesAriaSnapshot(""" * since: v1.49 ### option: LocatorAssertions.toMatchAriaSnapshot.update = %%-assertions-aria-snapshot-update-%% +* langs: js ## async method: LocatorAssertions.toMatchAriaSnapshot#2 * since: v1.50 @@ -2399,6 +2400,7 @@ Name of the snapshot to store in the snapshot folder corresponding to this test. Generates sequential names if not specified. ### option: LocatorAssertions.toMatchAriaSnapshot#2.update = %%-assertions-aria-snapshot-update-%% +* langs: js ### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%% * since: v1.50 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index cdfd648f3446b..83ffb6a3d305e 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1900,6 +1900,7 @@ In this config: ## assertions-aria-snapshot-update * since: v1.57 +* langs: js - `update` <["raw"]|["relaxed"]> Defines how snapshots are written when updating. Defaults to `'relaxed'`.