Skip to content

[Web] Flaky test: InvoiceWorkspace "holds a header Issue click…" intermittently fails on invoice-issue-saving-hint #3219

Description

@bdunncompany

Summary

InvoiceWorkspace.test.tsx"holds a header Issue click while the editor is dirty, then fires it when the save settles" fails intermittently in the web suite, with:

TestingLibraryElementError: Unable to find an element by: [data-testid="invoice-issue-saving-hint"]

It is one failing test out of 534 files, so it reddens the whole Test Web job and the CI Success aggregate gate with it.

Two confirmed occurrences, both on unrelated changes

  1. main @ 38150ac2f (2026-08-06). The merge that produced that commit was fix(web): stop dynamic device groups blanking the /devices/groups page #3179, which touches zero billing files. Re-running the identical commit with no code change passed, which is what established it as a flake rather than a regression.
  2. fix(api): honour the script's own timeoutSeconds in the stale reaper (#3190) #3213 (2026-08-07), an apps/api-only change that touches no web code at all. Same test, same assertion.

Occurrence 2 is the reason this is worth filing: the flake now blocks unrelated pull requests from going green.

Why it is timing-shaped

The assertion targets invoice-issue-saving-hint, which by design only exists while a save is in flight:

fireEvent.change(screen.getByTestId('invoice-notes'), { target: { value: 'Edited' } });
fireEvent.blur(screen.getByTestId('invoice-notes'));
await waitFor(() => expect(screen.getByTestId('invoice-issue-saving-hint')).toBeInTheDocument());

The PATCH is a promise deliberately left unresolved until later in the test, so the save should stay pending for the whole window — which is why the failure is surprising and why I have not guessed at a fix.

What I could not establish

It does not reproduce locally. 6/6 clean runs of that file on Node 22.23.2 on an idle machine. That is consistent with a load-sensitive race that only surfaces on a busy runner, but I could not prove the mechanism, so I have deliberately not sent a speculative patch to the save/hint state machine — whoever owns that flow will know in a minute what took me an hour of guessing.

Two plausible directions for whoever picks it up, neither verified:

  • the blur-triggered save has not been dispatched yet when waitFor gives up, so the hint has genuinely not rendered (a debounce or effect-ordering window);
  • the hint's visibility depends on a second piece of state that has already settled by the time the assertion runs.

Happy to build the fix if you can point at which of those it is, or if you would rather I dig in properly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    tech-debtRefactor / cleanup / internal quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions