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
4 changes: 4 additions & 0 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,8 @@ assertThat(page.locator("body")).matchesAriaSnapshot("""
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.49

### option: LocatorAssertions.toMatchAriaSnapshot.update = %%-assertions-aria-snapshot-update-%%

## async method: LocatorAssertions.toMatchAriaSnapshot#2
* since: v1.50
* langs: js
Expand All @@ -2396,6 +2398,8 @@ 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 = %%-assertions-aria-snapshot-update-%%

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

Expand Down
7 changes: 7 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -1898,3 +1898,10 @@ 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
* since: v1.57
- `update` <["raw"]|["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.
12 changes: 9 additions & 3 deletions packages/playwright/src/matchers/toMatchAriaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ type ToMatchAriaSnapshotExpected = {
name?: string;
path?: string;
timeout?: number;
update?: 'raw' | 'relaxed';
} | string;

export async function toMatchAriaSnapshot(
this: ExpectMatcherState,
locator: LocatorEx,
expectedParam?: ToMatchAriaSnapshotExpected,
options: { timeout?: number } = {},
options: { timeout?: number, update?: 'raw' | 'relaxed'; } = {},
): Promise<MatcherResult<string | RegExp, string>> {
const matcherName = 'toMatchAriaSnapshot';

Expand All @@ -57,6 +58,8 @@ export async function toMatchAriaSnapshot(
let expected: string;
let timeout: number;
let expectedPath: string | undefined;
let updateMode = options.update ?? 'relaxed';

if (isString(expectedParam)) {
expected = expectedParam;
timeout = options.timeout ?? this.timeout;
Expand All @@ -69,6 +72,7 @@ export async function toMatchAriaSnapshot(
expectedPath = legacyPath;
expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => '');
timeout = expectedParam?.timeout ?? this.timeout;
updateMode = expectedParam?.update ?? updateMode;
}

const generateMissingBaseline = updateSnapshots === 'missing' && !expected;
Expand Down Expand Up @@ -116,13 +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 });
await fs.promises.writeFile(expectedPath, typedReceived.regex, '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.`;
Expand All @@ -135,7 +141,7 @@ export async function toMatchAriaSnapshot(
}
return { pass: true, message: () => '', name: 'toMatchAriaSnapshot' };
} else {
const suggestedRebaseline = `\`\n${escapeTemplateString(indent(typedReceived.regex, '{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;
Expand Down
18 changes: 18 additions & 0 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9518,6 +9518,15 @@ interface LocatorAssertions {
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;

/**
* 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.
*/
update?: "raw"|"relaxed";
}): Promise<void>;

/**
Expand Down Expand Up @@ -9546,6 +9555,15 @@ interface LocatorAssertions {
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;

/**
* 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.
*/
update?: "raw"|"relaxed";
}): Promise<void>;

/**
Expand Down
75 changes: 75 additions & 0 deletions tests/page/to-match-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,3 +878,78 @@ 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 match with update param', async ({ page }) => {
{
await page.setContent(`<h1>Issues 12</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 12"
`, { update: 'raw' });
}
{
await page.setContent(`<h1>Issues 1/2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1/2"
`, { update: 'raw' });
}
{
await page.setContent(`<h1>Issues 1[</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1["
`, { update: 'raw' });
}
{
await page.setContent(`<h1>Issues 1]]2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1]]2"
`, { update: 'raw' });
}
{
await page.setContent(`<h1>Issues 12</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading ${/Issues \d+/}
`, { update: 'relaxed' });
}
{
await page.setContent(`<h1>Issues 1/2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading ${/Issues 1[/]2/}
`);
}
{
await page.setContent(`<h1>Issues 1[</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading ${/Issues 1\[/}
`);
}
{
await page.setContent(`<h1>Issues 1]]2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading ${/Issues 1[\]]]2/}
`);
}
{
await page.setContent(`<h1>Issues 12</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 12"
`);
}
{
await page.setContent(`<h1>Issues 1/2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1/2"
`);
}
{
await page.setContent(`<h1>Issues 1[</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1["
`);
}
{
await page.setContent(`<h1>Issues 1]]2</h1>`);
await expect(page.locator('body')).toMatchAriaSnapshot(`
- heading "Issues 1]]2"
`);
}
});
45 changes: 45 additions & 0 deletions tests/playwright-test/aria-snapshot-file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(\`<h1>hello world 123</h1>\`);
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]');
});
`
}, { '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(\`<h1>hello world 123</h1>\`);
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]');
});
`
}, { '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(\`<h1>hello world 123</h1>\`);
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);
});
80 changes: 80 additions & 0 deletions tests/playwright-test/update-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(\`<input value="hello world 123">\`);
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(\`<input value="hello world 123">\`);
- 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(\`<input value="hello world 123">\`);
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(\`<input value="hello world 123">\`);
- 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);
});
});