Commit Graph
23 Commits
Author SHA1 Message Date
Marc-André Bombeck 352ff617da Stop the switched-payload spec failing on its own reload
The scoped-sharing spec routes `/api/auth/me`, and the account switch it
drives ends in a full reload. The reload cancels whatever call is in
flight, Playwright marks the route handled, and the handler's `fulfill`
then lands on it with "Route is already handled!". With
`failOnFlakyTests` on CI that turns the whole shard red over a teardown
race rather than over anything the test is checking.

Same guard the record-session-fence spec and this file's own
accessibility sibling already use. The `fetch` is guarded too, because a
request cancelled mid-flight rejects there rather than at the fulfill,
and the earlier of the two is the one that decides which error the run
reports.
2026-08-22 10:42:32 +02:00
Marc-André Bombeck 34be5b4554 test: wait for a record switch to land before navigating away from it
Entering or leaving a record is a server write followed by
window.location.assign, so the click that starts it returns long before
the document it produces exists. The switcher helper returned there, and
everything a test did next raced the app's own navigation.

Both ways of losing that race are silent. A goto issued into the gap is
cancelled when the assign commits, which Chromium reports as
net::ERR_ABORTED and which is what CI saw twice. Or the goto wins, and
the page it lands on belongs to the caller's own record, where a route
that is ungranted in somebody else's record is simply granted: /coach
opened in that gap issued all four of the protected reads the test
asserts never happen. The second is the worse outcome, because the
assertion was reading the wrong record and could as easily have passed
there.

Neither is visible in the DOM the click leaves behind, which is why
waiting on the banner cannot settle it: the document on its way out
already shows whatever the next assertion looks for, so the wait returns
at once and proves nothing. A marker on window can settle it, because a
document replacement is the one thing that destroys it. If the navigation
never happens the wait fails, which is what a switch that did not land
should do.

Both helpers use it now, so the retry that was added downstream of
leaving a record has nothing left to absorb; it stays, because the claim
there is that the record survives the round trip rather than that one
navigation attempt wins.

Reproduced before fixing, under CPU contention at CI's worker count:
three of six runs flaked, showing both faces. Six of six clean at the
same contention afterwards, then twenty consecutive runs of the file and
the whole sharing set, none flaky.
2026-08-07 16:15:54 +02:00
Marc-André Bombeck 53ffbfdec4 test: give the two sharing probes instruments that cannot fail by themselves
Both failed on CI and neither failure was about the product.

The loading-state scan held /api/auth/me open with a fixed sleep in the
route handler. Three things follow from that and all three are wrong: the
hold ends 1500 ms after the first request whatever the axe scan is doing,
so the state being scanned is not the state the test names; every refetch
queues another sleeping handler, so how many are live is a property of
the machine; and a handler still sleeping when its request is cancelled
answers route.continue with "Route is already handled", which is what the
runner reported. It now holds on an explicit gate released at a point the
test chooses, counts what it held so a page that was merely slow cannot
pass for a page it held, and asserts the gate goes away on release, which
is the exit control the entry assertion never had.

The labs doorway proved that no OCR probe fires by waiting for that
request with a 500 ms timeout and expecting the rejection. An instrument
whose pass condition is its own failure has to be armed early and awaited
late, and in between it is an unhandled rejection that lands on whichever
test is finishing. Its window was wrong too: the timer started before the
switch was clicked, so on a slow runner it could expire before the labs
page existed. A request listener replaces it, covering everything from
before the click to after the record read landed, with the domain's own
read asserted in the same set so an absence cannot be a statement about a
matcher that stopped matching.
2026-08-07 10:26:41 +02:00
Marc-André Bombeck c043a77429 test: wire every release journey to the tests that carry it
The eight journeys were enumerated and one of them was wired. The other
seven existed as claims in a fixture file, which is the gap the enumeration
was written to close: a browser suite reports what it ran, never what it did
not, and a journey nobody wired reads exactly like a journey that passed.

All eight are wired now, and the list is checked in both directions — an
enumerated journey missing from the wiring fails, not just a wiring entry
naming a journey nobody declared. Before this, a subset was legal, so a
dropped journey and a not-yet-written one looked the same.

A journey names more than one file where more than one file carries it. J1
claims a grant runs through invite, accept, switch, expiry, revoke and a
direct URL; the browser proves the switch, what each level opens and the
direct-URL refusal, and the transitions either side are database facts
proved against real Postgres. Folding that into one file would mean a
browser spec driving expiry through the clock or an integration file
asserting what a page renders, and both are worse tests than the two that
exist.

Three journeys ride Playwright and five ride Vitest, so a green run of this
guard means all eight are claimed and here is which have executed in the
ordinary gate — never that all eight passed. The runner is derived from the
path rather than trusted, so it cannot become a label set to whichever value
made the split read better.

The extractor now reads `it(` as well as `test(`, and the parameterised
`it.each(table)("title")` form alongside the single-call one. It saw neither
before: five of the journeys are proved by Vitest files, every per-file leg
is over a matched set, and an empty match set satisfied them all. The
synthetic spec that proves the parse grew a Vitest half, and it is what
caught the parameterised form being invisible. What the matcher still cannot
see is written into its own doc comment.
2026-08-07 06:11:36 +02:00
Marc-André Bombeck 47c4560253 test: prove the banner names the kind of record, not just tags it
recordKindLabel chooses between two sentences that mean different things
and nothing could fail it: the journey asserted the data attribute, which
that function never reads, so inverting the arms left the suite green
while a guardian standing inside a managed profile was told it was an
ordinary shared record.
2026-08-07 03:56:30 +02:00
Marc-André Bombeck 7175db33f7 test: drop an assertion that could not fail
The pending-acceptance note renders unconditionally inside the invite
form, so asserting it after the invitation proved a string existed rather
than anything the journey did. The claim moves onto the pending row,
which the server has to produce. The note keeps its own leg in the unit
suite, where deleting the node makes it red.
2026-08-07 03:56:17 +02:00
Marc-André Bombeck e9084d9f17 test: cover the managed-profile journey
- drive creation, the switcher entry, the managed banner and the return
  trip in a real browser, plus the guardian roster and the floor stated
  before an invitation is accepted
- seed a guardian account with a second factor and refresh its step-up
  before the journey acts, since no fixture could reach a step-up-gated
  route before this
- tie the journey id to its test titles, so a marker that goes missing
  fails a check rather than vanishing from a run nobody counted
2026-08-07 03:22:51 +02:00
Marc-André Bombeck 6e5b420e4e test(e2e): give every switching journey its own session row
The suite only passed on one worker: four specs shared the scope
delegate's cookie jar and moved the same session's record selector, so
at CI's two workers they moved the epoch under each other and the
failures were the harness racing itself.

- one login per switching spec, which is a separate session row and
  therefore a separate selector; the login bucket is cleared between
  them because the ceiling is five and setup now signs in eight times
- each such spec runs in exactly one project, since a second project
  drives the same row from two browsers
- clear what the browser suite WRITES, so it can be run twice: the
  write journey saves a minute-rounded reading and collided with itself
- wait for adoption after leaving a record, retry a lost switch the way
  a person would, and budget the cross-tab hold for machine load
2026-08-06 23:04:08 +02:00
Marc-André Bombeck ffe38c9bb6 fix(02-12.1): reach fenced document routes through the app transport
A browser-issued <img src>, <iframe src> or <a href download> cannot
carry the record-session assertion, so the fence refused all three on
any session that had been inside a shared record — permanently, since
none of them will ever send a header.

- add a structural guard: no client file may name a record-fenced route
  in a src=, href= or window.location sink
- fetch the thumbnail, the inline preview and the download through the
  app transport and hand the DOM an object URL, revoked on unmount
- make the two API probes in the managed-profiles browser spec assert
  the same context the app beside them asserts
2026-08-06 20:36:45 +02:00
Marc-André Bombeck a636a3b4c5 fix(02-12): gate shared record navigation
- Wait for the shared-record banner before generated reads
- Keep the profile doorway in the navigation translation namespace
2026-08-06 14:35:02 +02:00
Marc-André Bombeck c42df8cbc3 fix(02-12): gate record preload scope
- Wait for a validated account scope before warming record reads
- Abort scoped preloads and discard handoffs across record transitions
- Cover malformed scope reloads and fresh own-record recovery
2026-08-06 14:32:32 +02:00
Marc-André Bombeck 21e111c06c fix(02-12): refuse malformed record access
- Preserve absent-server compatibility while rejecting published invalid blocks\n- Partition refused record reads and withhold protected presentation
2026-08-06 14:03:13 +02:00
Marc-André Bombeck b844d04676 fix(02-12): reject divergent shared-record state
- Verify the active record matches its canonical switcher entry\n- Skip the owner OCR probe in delegated Labs records
2026-08-06 13:39:37 +02:00
Marc-André Bombeck 358bb39465 fix(02-12): keep scoped record polls dormant
- suppress whole-record achievements reads for narrowed grants\n- stabilize record journey response assertions
2026-08-06 08:34:02 +02:00
Marc-André Bombeck e568caa2f1 test(02-12): make record switcher journeys deterministic
- Address granted records by their published username\n- Start the profile error mock before record navigation
2026-08-06 08:25:42 +02:00
Marc-André Bombeck 123bc15f16 fix(02-12): restore shared banner contrast
- Use the foreground token for warning context copy\n- Serialize shared-session browser journeys
2026-08-06 05:34:16 +02:00
Marc-André Bombeck 4999c786e3 test(02-12): exercise access-level record journeys
- Seed adult and managed whole-record grants for browser coverage\n- Verify scoped writes, settings boundaries, and refused coach reads
2026-08-06 05:26:21 +02:00
Marc-André Bombeck b231a7892c test(02-12): retire sharing inventory anchors
- remove obsolete inventory-only journey declarations\n- select persistent shared-record journeys by Playwright project config
2026-08-06 05:16:32 +02:00
Marc-André Bombeck 407f7cc044 test(02-12): exercise scoped record doorways
- seed isolated records for all eight shared domains\n- verify each browser switch lands on its scoped read surface
2026-08-06 05:12:33 +02:00
Marc-André Bombeck f69e1d7dec feat: add managed settings navigation
- Show only guardian settings for managed records\n- Keep record-scoped settings and exit context visible
2026-08-06 04:23:17 +02:00
Marc-André Bombeck 908897cc6b test: cover shared record modes 2026-08-06 04:17:58 +02:00
Marc-André Bombeck 01562b4aef test: use the shared browser test entry point 2026-08-06 00:08:54 +02:00
Marc-André Bombeck feb9d25e87 test: anchor sharing release journeys 2026-08-05 23:29:55 +02:00