Skip to content

Support snapshot assertions with expect.poll #9946

@hi-ogawa

Description

@hi-ogawa

Clear and concise description of the problem

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions