Skip to content

Fix flaky ComparisonPage.test.tsx timeout under full-suite parallelism #896

Description

@AlexAxthelm

Problem

src/pages/ComparisonPage.test.tsx intermittently fails when the whole suite runs, and
passes when run alone. The standing workaround is "re-run that file by itself", which means
a red CI run is routinely dismissed by hand — the exact habit that lets a real regression
through unnoticed.

What is actually known

Verified:

  • Both tests await screen.findByText(...) with no explicit timeout, so they use RTL's
    default budget of 1000 ms (ComparisonPage.test.tsx:95 and :112).
  • The work inside that budget is unusually heavy. mountWithFixtures calls
    vi.resetModules(), installs two vi.doMocks, then does
    await Promise.all([import("./ComparisonPage"), import("../context/ComparisonContext")])
    a cold dynamic import and transform of the page module and its dependency graph, followed
    by a full render. Under CPU contention that can plausibly exceed 1 s.
  • vitest.config.ts sets no testTimeout, so the default 5000 ms applies. When a query
    budget sits at or above that, a genuine failure surfaces as Test timed out in 5000ms
    instead of the query's own "Unable to find..." error, hiding which assertion failed.
  • The file does not use fireEvent, userEvent, focus, or tooltips. This is not the
    passive-effect listener race fixed in feat(ui): display region's countries in pill tooltip #895 — that fix does not apply here.

Not verified:

  • The precise mechanism. I could not reproduce this on demand. It only failed for me under
    pathological CPU load, and in that state entire test files were dropped and unrelated
    pure-function tests (loadData.test.tsx) failed too — an environment too degraded to
    diagnose anything. The 1000 ms-budget explanation fits the reported symptom (a timeout)
    and the code, but it is a hypothesis, not a proven root cause.

Proposed fix

Give the awaits an explicit budget and the tests a matching per-test timeout, mirroring what
src/pages/PathwayDetailPage.test.tsx does on feat/geography-ui (PR #895):

const WAIT = { timeout: 10_000 };
const TEST_TIMEOUT = 20_000;

it("renders structured geography ...", async () => {
  await mountWithFixtures("cmp-a,cmp-b");
  expect(
    await screen.findByText("PubA: Comparison Pathway A", undefined, WAIT),
  ).toBeInTheDocument();
  // ...
}, TEST_TIMEOUT);

Keep the query budget strictly below the per-test timeout, so a real failure reports the
query's error rather than a bare "test timed out".

If it still flakes after this, the budget hypothesis is wrong and the next step is to capture
the actual error (set the query budget below vitest's testTimeout and read what the query
reports) rather than raising numbers again.

Acceptance criteria

  • ComparisonPage.test.tsx has explicit query budgets and per-test timeouts.
  • Full suite (npm test -- --run) green for 10 consecutive runs on a loaded machine.
  • No remaining instruction anywhere to "re-run this file alone" as a workaround.

Also worth a look

src/pages/PathwaySearch.test.tsx uses the same vi.resetModules() + dynamic-import mount
pattern with 3 findBy* calls and only 2 explicit timeouts, so at least one await is on the
1 s default. It has not been reported as flaky, but it is the same shape and would fail the
same way. Consider auditing it in the same pass.

Discovered while fixing a genuinely different flake in #895 (part of #783 / epic #860).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions