Skip to content

RouteAnnouncer shared-title test fails intermittently under the full suite #119

Description

@georgekaran

What happens

RouteAnnouncer > announces again when two paths share a title fails intermittently, and only when the whole web suite runs.

Measured on feat/next-audit-time (a31b235 plus that branch), on an 8-core machine:

how it was run failures
pnpm --filter tabstop-web test (full suite) 1 in 6
the same, five further consecutive runs 0 in 5
vitest run src/screens/components/RouteAnnouncer alone 0 in 25

So it needs the parallel workers and the CPU contention that come with the full suite. Nothing reproduces it in isolation.

What is known and what is not

The test asserts on MutationObserver records rather than on final text — deliberately, and the comment above it explains why: two paths that share a title would otherwise be announced by silence, and the final text is identical whether or not the region was cleared in between.

expect(seen).toContain('');
expect(seen.at(-1)).toContain('example.com');

The assertion message was not captured. The run that failed was summarised to a FAIL line and the detail is gone, so it is unknown which of the two assertions broke — whether the clear was never observed, or the refill never landed inside the window. That is the first thing to record next time it happens.

One hypothesis, tested and rejected

The announcer subscribes to onDocumentTitleSet inside its own effect, while the screen fires documentTitleSet() from useDocumentTitle, deeper in the tree. React flushes passive effects child-first, so it looked like the screen's first signal after a navigation must arrive before the announcer has re-subscribed — leaving the announcement dependent on the screen re-rendering again, which for /pages/:id is driven by query timing.

That is wrong. A probe with a screen that renders exactly once — no state, no queries, nothing to trigger a second pass — is announced correctly. The first signal is received. So whatever the race is, it is not that.

Why this is worth fixing rather than retrying

The test guards a real defect that has been reintroduced before: setting the announcement to the string it already holds is a no-op to React, so the region never mutates and the second navigation is announced by silence. A flaky guard on that is one bad CI day away from being marked skip.

Suggested first steps

  • Keep the full failure output in CI rather than the summary, so the failing assertion is known the next time it happens.
  • Consider whether the test can assert the same guarantee without racing a real MutationObserver across two navigations — for instance by observing the announcer's state transitions directly, or by driving the two navigations through a router of its own the way the neighbouring "names itself late" test already does to avoid exactly this kind of interference.
  • If it stays unreproducible, vitest --sequence.shuffle or a pinned worker count may say whether it is contention or cross-file interference.

Acceptance criteria

  • The failing assertion is identified from a captured run.
  • The test passes reliably under the full suite — 50 consecutive runs without a failure.
  • The guarantee it protects is still covered: two paths sharing a title produce a real DOM mutation, not a silent no-op.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:webFrontend under web/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions