Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,13 @@ assertThat(page.locator("body")).matchesAriaSnapshot("""
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.49

### option: LocatorAssertions.toMatchAriaSnapshot.numberSubstitution
* since: v1.55
* langs: js
- `numberSubstitution` <["regex"|"static"]>

Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.

## async method: LocatorAssertions.toMatchAriaSnapshot#2
* since: v1.50
* langs: js
Expand Down Expand Up @@ -2411,3 +2418,10 @@ Generates sequential names if not specified.

### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.50

### option: LocatorAssertions.toMatchAriaSnapshot#2.numberSubstitution
* since: v1.55
* langs: js
- `numberSubstitution` <["regex"|"static"]>

Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
14 changes: 14 additions & 0 deletions docs/src/api/class-pageassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ assertThat(page).matchesAriaSnapshot("""
### option: PageAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.60

### option: PageAssertions.toMatchAriaSnapshot.numberSubstitution
* since: v1.55
* langs: js
- `numberSubstitution` <["regex"|"static"]>

Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.

## async method: PageAssertions.NotToMatchAriaSnapshot
* since: v1.60
* langs: python
Expand Down Expand Up @@ -225,6 +232,13 @@ Generates sequential names if not specified.
### option: PageAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%%
* since: v1.60

### option: PageAssertions.toMatchAriaSnapshot#2.numberSubstitution
* since: v1.55
* langs: js
- `numberSubstitution` <["regex"|"static"]>

Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.

## async method: PageAssertions.toHaveScreenshot#1
* since: v1.23
* langs: js
Expand Down
1 change: 1 addition & 0 deletions docs/src/test-api/class-testconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The structure of the git commit metadata is subject to change.
- `toMatchAriaSnapshot` ?<[Object]> Configuration for the [`method: LocatorAssertions.toMatchAriaSnapshot#2`] method.
- `pathTemplate` ?<[string]> A template controlling location of the aria snapshots. See [`property: TestConfig.snapshotPathTemplate`] for details.
- `children` ?<["contain" | "equal" | "deep-equal"]> Controls how children of the snapshot root are matched against the actual accessibility tree. This is equivalent to adding a `/children` property at the top of every aria snapshot template. Individual snapshots can override this by including an explicit `/children` property.
- `numberSubstitution` ?<["regex" | "static"]> Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data. Individual assertions can override this via the per-call option.
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
- `maxDiffPixels` ?<[int]> An acceptable amount of pixels that could be different, unset by default.
- `maxDiffPixelRatio` ?<[float]> An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
Expand Down
1 change: 1 addition & 0 deletions docs/src/test-api/class-testproject.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export default defineConfig({
- `toMatchAriaSnapshot` ?<[Object]> Configuration for the [`method: LocatorAssertions.toMatchAriaSnapshot#2`] method.
- `pathTemplate` ?<[string]> A template controlling location of the aria snapshots. See [`property: TestProject.snapshotPathTemplate`] for details.
- `children` ?<["contain" | "equal" | "deep-equal"]> Controls how children of the snapshot root are matched against the actual accessibility tree. This is equivalent to adding a `/children` property at the top of every aria snapshot template. Individual snapshots can override this by including an explicit `/children` property.
- `numberSubstitution` ?<["regex" | "static"]> Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data. Individual assertions can override this via the per-call option.
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
- `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.
Expand Down
12 changes: 8 additions & 4 deletions packages/injected/src/ariaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type AriaTreeOptions = {
doNotRenderActive?: boolean;
depth?: number;
boxes?: boolean;
numberSubstitution?: 'regex' | 'static';
};

type InternalOptions = {
Expand Down Expand Up @@ -74,8 +75,11 @@ function toInternalOptions(options: AriaTreeOptions): InternalOptions {
return { visibility: 'ariaAndVisible', refs: 'none', renderBoxes };
}
if (options.mode === 'codegen') {
// To generate aria assertion with regex heurisitcs.
return { visibility: 'aria', refs: 'none', renderStringsAsRegex: true, renderBoxes };
// To generate aria assertion with regex heuristics. The numberSubstitution
// option (default 'regex') lets callers opt out of auto-converting numbers
// and dates into \d+ regexes when the baseline values are stable.
const renderStringsAsRegex = options.numberSubstitution !== 'static';
return { visibility: 'aria', refs: 'none', renderStringsAsRegex, renderBoxes };
}
// To match aria snapshot.
return { visibility: 'aria', refs: 'none', renderBoxes };
Expand Down Expand Up @@ -390,14 +394,14 @@ export type MatcherReceived = {
regex: string;
};

export function matchesExpectAriaTemplate(rootElement: Element, template: aria.AriaTemplateNode): { matches: aria.AriaNode[], received: MatcherReceived } {
export function matchesExpectAriaTemplate(rootElement: Element, template: aria.AriaTemplateNode, options?: { numberSubstitution?: 'regex' | 'static' }): { matches: aria.AriaNode[], received: MatcherReceived } {
const snapshot = generateAriaTree(rootElement, { mode: 'default' });
const matches = matchesNodeDeep(snapshot.root, template, false, false);
return {
matches,
received: {
raw: renderAriaTree(snapshot, { mode: 'default' }).text,
regex: renderAriaTree(snapshot, { mode: 'codegen' }).text,
regex: renderAriaTree(snapshot, { mode: 'codegen', numberSubstitution: options?.numberSubstitution }).text,
}
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1509,7 +1509,7 @@ export class InjectedScript {
if (options.expression === 'to.match.aria' && !options.selector) {
if (!this.document.body)
return { matches: options.isNot, missingReceived: true };
const result = matchesExpectAriaTemplate(this.document.body, options.expectedValue);
const result = matchesExpectAriaTemplate(this.document.body, options.expectedValue, options.expressionArg);
return {
received: result.received,
matches: !!result.matches.length,
Expand Down Expand Up @@ -1627,7 +1627,7 @@ export class InjectedScript {

{
if (expression === 'to.match.aria') {
const result = matchesExpectAriaTemplate(element, options.expectedValue);
const result = matchesExpectAriaTemplate(element, options.expectedValue, options.expressionArg);
return {
received: result.received,
matches: !!result.matches.length,
Expand Down
1 change: 1 addition & 0 deletions packages/playwright/src/matchers/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export type ExpectConfig = {
toMatchAriaSnapshot?: {
pathTemplate?: string;
children?: 'contain' | 'equal' | 'deep-equal';
numberSubstitution?: 'regex' | 'static';
};
toPass?: { timeout?: number; intervals?: number[] };
};
Expand Down
6 changes: 4 additions & 2 deletions packages/playwright/src/matchers/toMatchAriaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function toMatchAriaSnapshot(
this: ExpectMatcherStateInternal,
receiver: LocatorEx | Page,
expectedParam?: ToMatchAriaSnapshotExpected,
options: { timeout?: number } = {},
options: { timeout?: number, numberSubstitution?: 'regex' | 'static' } = {},
): Promise<MatcherResult<string | RegExp, string>> {
const matcherName = 'toMatchAriaSnapshot';
expectTypes(receiver, ['Page', 'Locator'], matcherName);
Expand Down Expand Up @@ -91,7 +91,9 @@ export async function toMatchAriaSnapshot(
if (globalChildren && !expected.match(/^- \/children:/m))
expected = `- /children: ${globalChildren}\n` + expected;

const expectParams = { expectedValue: expected, isNot: this.isNot, timeout };
const numberSubstitution = options.numberSubstitution ?? expectConfig().toMatchAriaSnapshot?.numberSubstitution ?? 'regex';
const expressionArg = numberSubstitution === 'static' ? { numberSubstitution } : undefined;
const expectParams = { expectedValue: expected, expressionArg, isNot: this.isNot, timeout };
const { matches: pass, received, log, timedOut, errorMessage } = locator ?
await (locator as LocatorEx)._expect('to.match.aria', expectParams) :
await ((receiver as Page).mainFrame() as FrameEx)._expect('to.match.aria', expectParams);
Expand Down
44 changes: 44 additions & 0 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ interface TestProject<TestArgs = {}, WorkerArgs = {}> {
* including an explicit `/children` property.
*/
children?: "contain"|"equal"|"deep-equal";

/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
* Individual assertions can override this via the per-call option.
*/
numberSubstitution?: "regex"|"static";
};

/**
Expand Down Expand Up @@ -1253,6 +1261,14 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
* including an explicit `/children` property.
*/
children?: "contain"|"equal"|"deep-equal";

/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
* Individual assertions can override this via the per-call option.
*/
numberSubstitution?: "regex"|"static";
};

/**
Expand Down Expand Up @@ -9717,6 +9733,13 @@ interface LocatorAssertions {
* @param options
*/
toMatchAriaSnapshot(expected: string, options?: {
/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
*/
numberSubstitution?: "regex"|"static";

/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
Expand Down Expand Up @@ -9745,6 +9768,13 @@ interface LocatorAssertions {
*/
name?: string;

/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
*/
numberSubstitution?: "regex"|"static";

/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
Expand Down Expand Up @@ -9894,6 +9924,13 @@ interface PageAssertions {
* @param options
*/
toMatchAriaSnapshot(expected: string, options?: {
/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
*/
numberSubstitution?: "regex"|"static";

/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
Expand Down Expand Up @@ -9922,6 +9959,13 @@ interface PageAssertions {
*/
name?: string;

/**
* Controls how numbers and dates are rendered when generating or updating the aria-snapshot baseline. Defaults to
* `'regex'`, which rewrites numeric runs into `\d+` patterns so live UIs match across runs. Set to `'static'` to keep
* the actual values in the baseline, which is useful when the UI is driven by mocked or otherwise stable data.
*/
numberSubstitution?: "regex"|"static";

/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
Expand Down
139 changes: 139 additions & 0 deletions tests/playwright-test/update-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,145 @@ test('should generate baseline with regex', async ({ runInlineTest }, testInfo)
expect(result2.exitCode).toBe(0);
});

test('should generate baseline with static numbers when numberSubstitution is static (per-call)', 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(\`<ul>
<li>Item 1</li>
<li>Time 15:30</li>
<li>Year 2022</li>
<li>Duration 12ms</li>
<li>Total 22</li>
</ul>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { numberSubstitution: 'static' });
});
`
});

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
@@ -8,6 +8,13 @@
<li>Duration 12ms</li>
<li>Total 22</li>
</ul>\`);
- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { numberSubstitution: 'static' });
+ await expect(page.locator('body')).toMatchAriaSnapshot(\`
+ - list:
+ - listitem: Item 1
+ - listitem: Time 15:30
+ - listitem: Year 2022
+ - listitem: Duration 12ms
+ - listitem: Total 22
+ \`, { numberSubstitution: 'static' });
});

\\ No newline at end of file
`);

execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() });
const result2 = await runInlineTest({});
expect(result2.exitCode).toBe(0);
});

test('should generate baseline with static numbers when numberSubstitution is static (global config)', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'playwright.config.ts': `
export default {
expect: { toMatchAriaSnapshot: { numberSubstitution: 'static' } },
};
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<ul>
<li>Item 1</li>
<li>Time 15:30</li>
<li>Year 2022</li>
</ul>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`\`);
});
`
});

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
@@ -6,6 +6,11 @@
<li>Time 15:30</li>
<li>Year 2022</li>
</ul>\`);
- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`);
+ await expect(page.locator('body')).toMatchAriaSnapshot(\`
+ - list:
+ - listitem: Item 1
+ - listitem: Time 15:30
+ - listitem: Year 2022
+ \`);
});

\\ No newline at end of file
`);

execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() });
const result2 = await runInlineTest({});
expect(result2.exitCode).toBe(0);
});

test('per-call numberSubstitution should override the global setting', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'playwright.config.ts': `
export default {
expect: { toMatchAriaSnapshot: { numberSubstitution: 'static' } },
};
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.setContent(\`<ul>
<li>Total 22</li>
</ul>\`);
await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { numberSubstitution: 'regex' });
});
`
});

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
@@ -4,6 +4,9 @@
await page.setContent(\`<ul>
<li>Total 22</li>
</ul>\`);
- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`, { numberSubstitution: 'regex' });
+ await expect(page.locator('body')).toMatchAriaSnapshot(\`
+ - list:
+ - listitem: /Total \\\\d+/
+ \`, { numberSubstitution: 'regex' });
});

\\ No newline at end of file
`);

execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() });
const result2 = await runInlineTest({});
expect(result2.exitCode).toBe(0);
});

test('should generate baseline with special characters', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
Expand Down