feat(experimental): support aria snapshot#9668
feat(experimental): support aria snapshot#9668hi-ogawa wants to merge 199 commits intovitest-dev:mainfrom
Conversation
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
AriPerkkio
left a comment
There was a problem hiding this comment.
First review for docs only:
| expect(user).toMatchDomainInlineSnapshot(` | ||
| name=Alice | ||
| score=/\\d+/ | ||
| `, 'kv') | ||
| }) |
There was a problem hiding this comment.
What's the equivalent of current user flow of generating the first snapshot?
// User writes
expect(user).toMatchInlineSnapshot(); // note, no args
// Snapshot generates into
expect(user).toMatchInlineSnapshot(`
{
"name": ...
...
}
`);Do they pass empty string there?
expect(user).toMatchDomainInlineSnapshot("", "kv");Or does that run into comparison error "" does not match 'name=...?
There was a problem hiding this comment.
This is one of unpolished parts of toMatchDomainInlineSnapshot. It currently requires toMatchDomainInlineSnapshot("", "kv") as an initial state to work. Of course, toMatchAriaInlineSnapshot doesn't have this limitation, so I forgot about this.
It should be fixable but it's independent from shipping aria snapshot, so I'd like to deal with this in a follow-up.
Or actually this is also about the API of toMatchDomainInlineSnapshot, so we might change that whole thing basically. I thought simply swapping to toMatchDomainInlineSnapshot("kv", "...snapshot..."), but current inline snapshot logic didn't support, so I went with swapping two arguments for now.
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
| get snapshotUpdateState(): SnapshotUpdateState { | ||
| return this._updateSnapshot | ||
| } |
There was a problem hiding this comment.
I realized this public getter is what's requested by
| ## expect.addSnapshotDomain <Version type="experimental">4.1.1</Version> <Experimental /> {#expect-addsnapshotdomain} | ||
|
|
||
| - **Type:** `(adapter: DomainSnapshotAdapter) => void` | ||
|
|
||
| Registers a [domain snapshot adapter](/guide/snapshot#custom-snapshot-domain) for use with `toMatchDomainSnapshot` and `toMatchDomainInlineSnapshot`. Call this in [`setupFiles`](/config/setupfiles). | ||
|
|
||
| ```ts | ||
| import { expect } from 'vitest' | ||
| import { kvAdapter } from './kv-adapter' | ||
|
|
||
| expect.addSnapshotDomain(kvAdapter) | ||
| ``` |
There was a problem hiding this comment.
Maybe I'm way too late to the party, but would it be possible/worth considering adding this as a config entry rather than in a setup file?
Screenshot comparator algorithms are registered in browser.expect.toMatchScreenshot.comparators, this feels like a similar concept but it's done differently. I think it might be easier for users if something is done one way consistently.
I don't know what feels better to use or if it's possible unifying the behaviors at all, but I think it's something worth talking about.
There was a problem hiding this comment.
Domain snapshots run in test thread, browser comparators are running on the server, so I don't think it's possible to define them as functions, but they can follow the snapshotSerializers pattern, I think:
export default defineConfig({
test: {
snapshotDomains: ['./path-to-domain.js']
}
})|
|
||
| The same as [`toMatchInlineSnapshot`](#tomatchinlinesnapshot), but expects the same value as [`toThrow`](#tothrow). | ||
|
|
||
| ## toMatchAriaSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatcharisnapshot} |
There was a problem hiding this comment.
| ## toMatchAriaSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatcharisnapshot} | |
| ## toMatchAriaSnapshot <Version type="experimental">4.1.3</Version> <Experimental /> {#tomatcharisnapshot} |
(depends)
|
|
||
| See the [ARIA Snapshots guide](/guide/browser/aria-snapshots) for more details. | ||
|
|
||
| ## toMatchAriaInlineSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchariainlinesnapshot} |
There was a problem hiding this comment.
| ## toMatchAriaInlineSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchariainlinesnapshot} | |
| ## toMatchAriaInlineSnapshot <Version type="experimental">4.1.3</Version> <Experimental /> {#tomatchariainlinesnapshot} |
| }) | ||
| ``` | ||
|
|
||
| ## toMatchDomainSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchdomainsnapshot} |
There was a problem hiding this comment.
| ## toMatchDomainSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchdomainsnapshot} | |
| ## toMatchDomainSnapshot <Version type="experimental">4.1.3</Version> <Experimental /> {#tomatchdomainsnapshot} |
| expect(value).toMatchDomainSnapshot('my-domain') | ||
| ``` | ||
|
|
||
| ## toMatchDomainInlineSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchdomaininlinesnapshot} |
There was a problem hiding this comment.
| ## toMatchDomainInlineSnapshot <Version type="experimental">4.1.1</Version> <Experimental /> {#tomatchdomaininlinesnapshot} | |
| ## toMatchDomainInlineSnapshot <Version type="experimental">4.1.3</Version> <Experimental /> {#tomatchdomaininlinesnapshot} |
| If you previously used Vue CLI with Jest, you might want to install [jest-serializer-vue](https://npmx.dev/package/jest-serializer-vue). Otherwise, your snapshots will be wrapped in a string, which cases `"` to be escaped. | ||
| ::: | ||
|
|
||
| ## expect.addSnapshotDomain <Version type="experimental">4.1.1</Version> <Experimental /> {#expect-addsnapshotdomain} |
There was a problem hiding this comment.
| ## expect.addSnapshotDomain <Version type="experimental">4.1.1</Version> <Experimental /> {#expect-addsnapshotdomain} | |
| ## expect.addSnapshotDomain <Version type="experimental">4.1.3</Version> <Experimental /> {#expect-addsnapshotdomain} |
|
|
||
| ## ARIA Snapshots | ||
|
|
||
| ARIA snapshots capture the accessibility tree of a DOM element and compare it against a stored template. Inspired by [Playwright's ARIA snapshots](https://playwright.dev/docs/aria-snapshots), they provide a semantic alternative to visual regression testing — asserting structure and meaning rather than pixels. |
There was a problem hiding this comment.
| ARIA snapshots capture the accessibility tree of a DOM element and compare it against a stored template. Inspired by [Playwright's ARIA snapshots](https://playwright.dev/docs/aria-snapshots), they provide a semantic alternative to visual regression testing — asserting structure and meaning rather than pixels. | |
| ARIA snapshots capture the accessibility tree of a DOM element and compare it against a stored template. Based on [Playwright's ARIA snapshots](https://playwright.dev/docs/aria-snapshots), they provide a semantic alternative to visual regression testing — asserting structure and meaning rather than pixels. |
| const r = matchAriaTree(captured, expected) | ||
| return { | ||
| pass: r.pass, | ||
| message: r.pass ? undefined : 'Accessibility tree does not match expected template', |
There was a problem hiding this comment.
I think it's easier to just
if(r.pass) {
return { pass: true }
}
return {...}| * @see https://vitest.dev/guide/browser/aria-snapshots | ||
| * @see https://vitest.dev/api/expect#tomatchariasnapshot | ||
| */ | ||
| toMatchAriaSnapshot: () => void |
packages/browser/package.json
Outdated
| "birpc": "catalog:", | ||
| "flatted": "catalog:", | ||
| "ivya": "^1.7.1", | ||
| "ivya": "https://pkg.pr.new/vitest-dev/ivya/ivya@68f8735", |
| .toHaveTextContent('Diff') | ||
| await expect.element(page.getByRole('tabpanel').getByRole('img')) | ||
| .toBeInTheDocument() | ||
| expect(result.container).toMatchAriaInlineSnapshot(` |
There was a problem hiding this comment.
There is result.locator I think
Description
updateSnapshotviaSnapshotState#9481Adds
toMatchAriaSnapshot()andtoMatchAriaInlineSnapshot()matchers for asserting DOM accessibility trees, inspired by Playwright's aria snapshot feature. Core logic of aria tree generation, parsing, matching are entirely implemented onivyavitest-dev/ivya#8 as Aria related runtime utility lives there.Under the hood, this PR also introduces domain snapshot API in Vitest core to allow extending comparison mechanism beyond exact string equality. The simple example is added as a test case and also documented to illustrate the idea. However, I'd treat this API being "experimental" since I haven't too deeply thought through the API shape beyond what I minimally needed for implementing aria snapshot.
API
Example
Given HTML like
Initially generated snapshot looks like:
Snapshot can be manually edited to include regex pattern or remove some part and snapshot assertion continues to pass:
Now when actual HTML changes to:
Snapshot would now fail, but the error diff normalizes partially matching part:
When forcing the snapshot update via
--update, newly generated snapshot would reflect the same error diff, which means manually edited part is preserved:TODO
adapter.parseExpected)/childrendirective in aria tree template ivya#10Questions
--updatewait for stable snapshot?TODO: new heuristics and new option? employ similar strategy astoMatchScreenshot?Yes, it should. We can simplify pool more and wait until stable on
"new"(initial snaphsot) and"all"(--update) casesHow to pull in(fixed by feat: add minimal YAML parser for aria snapshot templates ivya#13)yaml?@vitest/browser/dist/expect-element.js. The packageyamlis huge around 100kb. This causedexpect-element.jsto go from23kbto140kb. Should we try code split and lazily load on runtime?toMatchAriaSnapshotspecifically asynchronous, which is technically possible. Or we can make explicit opt-in/out flag to lazy load early during runtime.feat: add minimal YAML parser for aria snapshot templates ivya#13
Follow-up considered
/childrenproperty (port https://playwright.dev/docs/aria-snapshots#strict-matching)/childrendirective in aria tree template ivya#10toMatchFileSnapshotsnapshotSerializersfeat(experimental): support aria snapshot #9668 (comment)TODO
expect.element + aria snapshotbecomes a special casedefineHelper. should be fixed on next playwright update, which has better async stack.Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yamlunless you introduce a new test example.Tests
pnpm test:ci.Documentation
pnpm run docscommand.Changesets
feat:,fix:,perf:,docs:, orchore:.