Snapshot Inside A Polled Callback

ID

javascript.snapshot_in_async_utility

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

async, reliability, snapshot, testing

Description

Reports a snapshot assertion inside a waitFor callback.

waitFor runs its callback again and again until it stops throwing. A snapshot matcher is the one assertion that must not be run that way, because it does not just compare — it also records.

The triage wrote this rule as "inside a waitFor or findBy* callback". The findBy* half does not exist: those queries take a matcher and an options object, never a callback. What is checked is the polling utilities, which are the ones that run a callback repeatedly.

Rationale

// Bad
await waitFor(() => {
  expect(container).toMatchSnapshot();      // written once per poll
});
  • Snapshots are matched positionally within a test, so each poll asks for a different one: the first attempt writes snapshot 1, the second writes snapshot 2, and a test that polls five times leaves five entries where the author wrote one.

  • On the next run the counter lines up differently and the test fails for reasons unrelated to the component.

  • Where nothing is written, the first mismatch throws, waitFor reads that as "not ready yet" and retries until the timeout — so a real snapshot difference is reported as a timeout, with the diff that would have explained it never shown.

  • An inline snapshot is worse still: the update pass rewrites the literal in the source from whichever retry happened to be last.

Remediation

Wait for the state the snapshot is meant to capture, then take the snapshot once.

// Good
await waitFor(() => expect(screen.getByRole('table')).toBeVisible());
expect(container).toMatchSnapshot();

© 2026 Xygeni. All rights reserved