-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Clear and concise description of the problem
- Follow up to feat(experimental): support aria snapshot #9668
The toMatchAriaSnapshot PR implemented snapshot assertions that work with polling (expect.element in browser mode), including stability detection and match-aware polling. This is a natural extension of that work: make the same capability available for the standard toMatchSnapshot / toMatchInlineSnapshot matchers with expect.poll.
Currently expect.poll() explicitly blocks snapshot matchers:
// Throws: "expect.poll() is not supported in combination with .toMatchSnapshot()"
await expect.poll(() => fetchValue()).toMatchSnapshot()The docs suggest using vi.waitFor as a workaround:
const flakyValue = await vi.waitFor(() => getFlakyValue())
expect(flakyValue).toMatchSnapshot()This works but has limitations — vi.waitFor resolves on the first non-throwing call with no stability guarantee, requires splitting polling from assertion into two steps, and can't do match-aware polling against an existing snapshot reference.
Suggested solution
Allow snapshot matchers with expect.poll, using stability detection:
await expect.poll(() => fetchState()).toMatchSnapshot()
await expect.poll(() => fetchState()).toMatchInlineSnapshot(`
Object {
"status": "ready",
"count": 42,
}
`)Stability detection: Poll the callback repeatedly. When two consecutive calls produce the same serialized output, the value is considered stable. Then:
- No existing snapshot (first run or
--update): save the stable value. - Has existing snapshot: assert the stable value matches the stored snapshot.
Match-aware polling: When a snapshot already exists, the poll loop continues past stable-but-mismatched states. A status field might stabilize at "loading" before eventually stabilizing at "ready" — the poll continues through "loading" and succeeds when it stabilizes at "ready" (matching the stored snapshot).
Semantics:
| Scenario | Behavior |
|---|---|
| No reference, value stabilizes | Save stable value as new snapshot |
| No reference, timeout before stable | Fail: "did not produce stable snapshot within timeout" |
| Has reference, stabilizes & matches | Pass |
| Has reference, stabilizes but doesn't match | Keep polling (value may still be converging) |
| Has reference, timeout (never matches) | Fail with snapshot diff |
--update flag |
Treat as no-reference (re-save stable value) |
| Poll callback throws | Reset stability baseline, keep retrying until timeout |
Users can also enforce their own readiness conditions by throwing — the poll resets stability baseline on errors and surfaces the last thrown error on timeout:
await expect.poll(async () => {
const text = await fetchSomething()
if (!text.endsWith('done')) throw new Error('not done')
return text
}).toMatchInlineSnapshot()This case can be written with vi.waitFor + toMatchInlineSnapshot today, but expect.poll + snapshot is a natural ergonomic unification — one expression, one await, and the stability/retry semantics just work.
Alternative
The current workaround using vi.waitFor / vi.waitUntil to stabilize the value before asserting with a snapshot:
const value = await vi.waitFor(() => getFlakyValue())
expect(value).toMatchSnapshot()Additional context
The stability detection and match-aware polling mechanics are already implemented and proven in the toMatchAriaSnapshot work (browser mode). The implementation would generalize the existing pollAssertDomain / getStableSnapshot logic in @vitest/snapshot to work with standard snapshot serialization.
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that requests the same feature to avoid creating a duplicate.