The e2e suite, 8.3 minutes to 6.8, with every assertion kept #221

Merged
Grandiras merged 2 commits from e2e-leaner into main 2026-09-23 10:17:32 +00:00
Owner

The e2e suite went from 8.3 minutes to 6.8 (500s → 406s, one worker, same stack), with every assertion kept: 165 tests became 153, and 153/153 pass. The changes come from a per-step profile of a full run: a small custom reporter printing every step over a second. docs/testing.md says how to rerun one.

Four wastes

  • A circuit wait that raced the circuit. Four copies of waitForCircuit (permissions, requests, pds, helpers/members.js) waited for a _blazor network response, up to 8s. When the circuit came up during the navigation before the call, which is the usual case locally, the response had already passed, so the wait sat out the full 8 seconds. One requests test paid it twice: 16s of 19. helpers/circuit.js is the one copy now. It asks the page instead: the negotiate request is in the Resource Timing buffer whichever side of the call it happened on.
  • Sleeping through app timers. The handle placeholder rolls every 3.8s, and three tests waited out a whole rotation. They run on Playwright's clock now (page.clock.install() / runFor), with time still flowing normally. Fixed waits stay where real time is the point: the entrance specs' late-replay windows and the reconnect dialog.
  • A toast over the button. Toasts took clicks for their three seconds. Adding a card to the middle column meant waiting for the "Card created" toast to leave the "+ Add card" it covered, for a member as much as for a test. Toasts have nothing to press, so they're click-through now (app.css). This one is a member-visible fix. I left the changelog alone because it's too small to spend one of the block's seven lines on.
  • Every first click waiting out the columns' entrance. Playwright won't click a moving control. The suite now runs with reducedMotion: 'reduce', a real setting the app honours by skipping its entrances, and the specs about motion opt back in: board-entrance, auth-entrance, and the calendar's cascade tests.

Also: the search helper still types one key event per character, just without 30ms of pause after each (the slow-circuit typing test keeps its own pace). And goHome waits for a resumed page's circuit before clicking home: a link clicked mid-startup reached / and was then rendered back to the resumed page (seen once in a full run).

Fused, by the fixture rule

  • filter.spec.js: six questions of one filter (flyout, narrowing, badge, other column, a new card's tags in a filtered and an unfiltered column, the emptied-column message, clearing) are one walk over one tag.
  • calendar.spec.js: one dated card's life (set, reloaded, in its day cell, paged past, moved from the calendar, cleared) is one test instead of five.
  • board.spec.js: create, edit, Escape and confirmed delete are one card's lifecycle. The javascript: link probe moved into the comment test's own card. It used to comment on .card.first(), somebody else's card, which is a write teardown cannot undo.

Every fused assertion keeps an expect(…, 'label'), so a failure still names what broke.

Checks

Full suite 153/153 in 6.8m. Unit tests 558/558. docs/testing.md records the four wastes and the rules they leave behind.

🤖 Generated with Claude Code

The e2e suite went from **8.3 minutes to 6.8** (500s → 406s, one worker, same stack), with every assertion kept: 165 tests became 153, and 153/153 pass. The changes come from a per-step profile of a full run: a small custom reporter printing every step over a second. `docs/testing.md` says how to rerun one. ## Four wastes - **A circuit wait that raced the circuit.** Four copies of `waitForCircuit` (`permissions`, `requests`, `pds`, `helpers/members.js`) waited for a `_blazor` network *response*, up to 8s. When the circuit came up during the navigation before the call, which is the usual case locally, the response had already passed, so the wait sat out the full 8 seconds. One `requests` test paid it twice: 16s of 19. `helpers/circuit.js` is the one copy now. It asks the page instead: the negotiate request is in the Resource Timing buffer whichever side of the call it happened on. - **Sleeping through app timers.** The handle placeholder rolls every 3.8s, and three tests waited out a whole rotation. They run on Playwright's clock now (`page.clock.install()` / `runFor`), with time still flowing normally. Fixed waits stay where real time is the point: the entrance specs' late-replay windows and the reconnect dialog. - **A toast over the button.** Toasts took clicks for their three seconds. Adding a card to the middle column meant waiting for the "Card created" toast to leave the "+ Add card" it covered, for a member as much as for a test. Toasts have nothing to press, so they're click-through now (`app.css`). This one is a member-visible fix. I left the changelog alone because it's too small to spend one of the block's seven lines on. - **Every first click waiting out the columns' entrance.** Playwright won't click a moving control. The suite now runs with `reducedMotion: 'reduce'`, a real setting the app honours by skipping its entrances, and the specs about motion opt back in: `board-entrance`, `auth-entrance`, and the calendar's cascade tests. Also: the search helper still types one key event per character, just without 30ms of pause after each (the slow-circuit typing test keeps its own pace). And `goHome` waits for a resumed page's circuit before clicking home: a link clicked mid-startup reached `/` and was then rendered back to the resumed page (seen once in a full run). ## Fused, by the fixture rule - `filter.spec.js`: six questions of one filter (flyout, narrowing, badge, other column, a new card's tags in a filtered and an unfiltered column, the emptied-column message, clearing) are one walk over one tag. - `calendar.spec.js`: one dated card's life (set, reloaded, in its day cell, paged past, moved from the calendar, cleared) is one test instead of five. - `board.spec.js`: create, edit, Escape and confirmed delete are one card's lifecycle. The `javascript:` link probe moved into the comment test's own card. It used to comment on `.card.first()`, somebody else's card, which is a write teardown cannot undo. Every fused assertion keeps an `expect(…, 'label')`, so a failure still names what broke. ## Checks Full suite 153/153 in 6.8m. Unit tests 558/558. `docs/testing.md` records the four wastes and the rules they leave behind. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The e2e suite, 8.3 minutes to 6.8, with every assertion kept
All checks were successful
CI / build (pull_request) Successful in 42s
16a4fd9c98
A per-step profile of a full run found four wastes: a circuit wait that raced
the circuit and sat out its 8 s timeout (one helper now, asking the page's
Resource Timing buffer instead), placeholder tests sleeping through a 3.8 s
timer (Playwright's clock now), a toast lying over the middle column's
"+ Add card" for three seconds (click-through now, for members too), and
every first click on a board waiting out the columns' entrance (the suite
runs with reduced motion; the motion specs opt back in). And, by the
fixture rule: filter.spec's six questions of one filter, calendar.spec's one
dated card's life and board.spec's card lifecycle are one test each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

⏳ Held on the OpenCode Go window — the 5-hour window is at 100% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-23T01:12:57.059Z) — nothing to re-trigger.

⏳ **Held on the OpenCode Go window** — the 5-hour window is at 100% (the bot holds at 95%). This continues automatically after the window resets (2026-09-23T01:12:57.059Z) — nothing to re-trigger. <!-- opencode-run:paused -->
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review -->
opencode-reviewer approved these changes 2026-09-23 01:14:56 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. The four wastes are real and the fixes are sound: the shared waitForCircuit asks the Resource Timing buffer instead of racing a network response (strictly better than the four copies it replaces, which swallowed failure), the clock usage is correct (page.clock.install() keeps time flowing; runFor(4500) covers the 3800+420 ms rotation), and the reduced-motion default with explicit opt-backs in the three motion specs is the right shape. I traced the three fused tests against their helpers: every deleted test's assertions survive, the cards/tags involved are each test's own, and the filter test's badge = 1 and before > 1 assumptions hold even with leftover untagged cards from earlier spec files on the shared board. The toast click-through is a genuine member-visible improvement. Two non-blocking notes: (1) waitForCircuit now hard-fails after 15 s where the old copies silently carried on — that's the right call, but the Resource Timing buffer is capped (~250 entries per document) and is not cleared by in-app navigations, so a very long-lived page could in principle overflow it before a later waitForCircuit call; not reachable by the current call sites, which all follow a full load. (2) The fused board test's first .column-add click is not retried the way createCard/openFilter retry theirs — pre-existing pattern, not a regression, but it is the one remaining cold-circuit race in the file.



⏱ 1m 29s · 4 steps · $0.0090 · tokens: 37.2k in / 3.3k out (+58.4k cache-read) · Go: 5h 0% · wk 46% · mo 56%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge. The four wastes are real and the fixes are sound: the shared `waitForCircuit` asks the Resource Timing buffer instead of racing a network response (strictly better than the four copies it replaces, which swallowed failure), the clock usage is correct (`page.clock.install()` keeps time flowing; `runFor(4500)` covers the 3800+420 ms rotation), and the reduced-motion default with explicit opt-backs in the three motion specs is the right shape. I traced the three fused tests against their helpers: every deleted test's assertions survive, the cards/tags involved are each test's own, and the filter test's `badge = 1` and `before > 1` assumptions hold even with leftover untagged cards from earlier spec files on the shared board. The toast click-through is a genuine member-visible improvement. Two non-blocking notes: (1) `waitForCircuit` now hard-fails after 15 s where the old copies silently carried on — that's the right call, but the Resource Timing buffer is capped (~250 entries per document) and is not cleared by in-app navigations, so a very long-lived page could in principle overflow it before a later `waitForCircuit` call; not reachable by the current call sites, which all follow a full load. (2) The fused board test's first `.column-add` click is not retried the way `createCard`/`openFilter` retry theirs — pre-existing pattern, not a regression, but it is the one remaining cold-circuit race in the file. --- --- ⏱ 1m 29s · 4 steps · $0.0090 · tokens: 37.2k in / 3.3k out (+58.4k cache-read) · Go: 5h 0% · wk 46% · mo 56% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-221/run-2026-09-23T01-13-27-077Z.md) <!-- opencode-reviewed-head:16a4fd9c98bd2ed1edcee74cf2cab229ec1aad8c --> <!-- opencode-verdict:approve -->
Author
Owner

Thanks. On the two notes:

  1. Resource Timing cap: a full buffer stops accepting new entries and keeps the ones it has. The negotiate request is among the first things a page loads, so it's recorded long before a page could reach the cap. The overflow would have to happen before the circuit negotiated, which none of this suite's pages come near. So I've left the helper as it is.
  2. The lifecycle test's first click: fixed. It's now retried like every other first click in board.spec.js, and passes in 6s.

Also rebased onto main (#222, #223). Unit tests 560/560.

Thanks. On the two notes: 1. **Resource Timing cap**: a full buffer stops *accepting new* entries and keeps the ones it has. The negotiate request is among the first things a page loads, so it's recorded long before a page could reach the cap. The overflow would have to happen *before* the circuit negotiated, which none of this suite's pages come near. So I've left the helper as it is. 2. **The lifecycle test's first click**: fixed. It's now retried like every other first click in `board.spec.js`, and passes in 6s. Also rebased onto `main` (#222, #223). Unit tests 560/560.
Grandiras force-pushed e2e-leaner from 16a4fd9c98
All checks were successful
CI / build (pull_request) Successful in 42s
to 0c04c21c3f
All checks were successful
CI / build (pull_request) Successful in 49s
2026-09-23 10:06:24 +00:00
Compare
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge — re-review of the new commits. Both notes from my last pass are resolved: the fused board test's first .column-add click is now retried with toPass like every other first click in the file, and helpers/circuit.js now documents the Resource Timing buffer's document-scoped lifetime (in-app navigation answers at once on the same circuit; a full load resets it, which is exactly when a new circuit is needed) — turning the caveat I raised into a stated invariant.

Re-verified on disk rather than from the diff: the app really honours prefers-reduced-motion (App.razor, auth-enter.js, calendar-interop.js, handle-placeholder.js, six CSS blocks), so the suite-wide reducedMotion: 'reduce' default runs a real member path and the three motion specs opt back in correctly. The toast click-through is consistent with the container (which was already pointer-events: none) and loses nothing — the toast markup has nothing pressable. All four local waitForCircuit copies are gone and every call site imports the shared helper; the new goHome call site is correctly scoped to the resumed-page branch. The fused tests keep every assertion with labels, the XSS probe now runs on the test's own card, and the clock usage (install() before load, runFor(4500) ≥ the 3800+420 ms rotation) is correct, with fixed waits left only where real time is the point.

Non-blocking: the Resource Timing buffer's ~250-entry-per-document cap remains a theoretical limit for a very long-lived page, but no current call site can reach it — all follow a full load.



⏱ 9m 14s · 8 steps · $0.0300 · tokens: 127.0k in / 14.7k out (+119.8k cache-read) · Go: 5h 1% · wk 74% · mo 70%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge — re-review of the new commits. Both notes from my last pass are resolved: the fused board test's first `.column-add` click is now retried with `toPass` like every other first click in the file, and `helpers/circuit.js` now documents the Resource Timing buffer's document-scoped lifetime (in-app navigation answers at once on the same circuit; a full load resets it, which is exactly when a new circuit is needed) — turning the caveat I raised into a stated invariant. Re-verified on disk rather than from the diff: the app really honours `prefers-reduced-motion` (App.razor, auth-enter.js, calendar-interop.js, handle-placeholder.js, six CSS blocks), so the suite-wide `reducedMotion: 'reduce'` default runs a real member path and the three motion specs opt back in correctly. The toast click-through is consistent with the container (which was already `pointer-events: none`) and loses nothing — the toast markup has nothing pressable. All four local `waitForCircuit` copies are gone and every call site imports the shared helper; the new `goHome` call site is correctly scoped to the resumed-page branch. The fused tests keep every assertion with labels, the XSS probe now runs on the test's own card, and the clock usage (`install()` before load, `runFor(4500)` ≥ the 3800+420 ms rotation) is correct, with fixed waits left only where real time is the point. Non-blocking: the Resource Timing buffer's ~250-entry-per-document cap remains a theoretical limit for a very long-lived page, but no current call site can reach it — all follow a full load. --- --- ⏱ 9m 14s · 8 steps · $0.0300 · tokens: 127.0k in / 14.7k out (+119.8k cache-read) · Go: 5h 1% · wk 74% · mo 70% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-221/run-2026-09-23T10-07-25-824Z.md) <!-- opencode-reviewed-head:0c04c21c3f1d78487e7ad0c61c35db3c78106d7b --> <!-- opencode-verdict:approve -->
Grandiras deleted branch e2e-leaner 2026-09-23 10:17:32 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!221
No description provided.