Skip to content

UI E2E is flaky: serial debug server latency exceeds the 15s per-check window #862

Closed

Description

@e54-bot

Evidence

UI E2E has started failing intermittently on main and on unrelated PRs:

  • main @ 126a104 (run 39409313917): 3 early checks failed because the rendered statements were read before the selected rule's content flushed (conditions render with lookup names, actions render with lookup display names — only Set Global Variable(score, 0) present — block bodies indent).
  • PR run 39411166202 (package-store branch): timed out at openRule(4, 'after') waiting for a Wait statement while rules 0–3 had already resolved.
  • A stacked superset branch (feat/lint-package-commands, run 39411419983) ran the same test green on the same code.

Root cause hypothesis

wright ui serves requests serially (for request in server.incoming_requests() in crates/wright-cli/src/ui.rs). The UI fires the rule-content fetches in parallel (Promise.all in ui/src/App.svelte), so the fixture's six rules?rule&content calls plus the bulk/list fallbacks queue behind each other on a single-threaded debug-build server. When the tail request lands later than a check's 15s waitFor window (ui/scripts/e2e.mjs), the assertion reads either a still-loading state or a previous rule's stale body.

This makes the job timing-sensitive rather than correctness-sensitive, and #847 added a small per-request cost (disk_fingerprint) that may have tightened the margin.

Options

  • Raise the per-check waitFor timeout in e2e.mjs (cheapest; hides a real latency cliff if one exists).
  • Bound request latency in the harness itself, e.g. warm the six content requests before starting checks so no check window covers cold queueing.
  • Parallelize or pipeline the tiny_http server (an ADR-0032 design decision, not a test fix).

Filing for owner triage; both failing runs were re-triggered as reruns.

Activity

  1. e54-bot commented on Oct 20, 2026

    @e54-bot
    CollaboratorAuthor

    Root cause found and fixed in #864. Reproduced all three failure signatures locally with in-page request/state traces:

    1. "Loading rule…" hang — respond() added Connection: close to every answer. Chrome may already have dispatched the next POST /api onto a socket it still believed reusable; the close then dropped that request, and since POSTs are not retried at the network layer the fetch hung forever. The comment motivating close (a parked keep-alive/preconnect socket stalling the loop) is unfounded: tiny_http runs a reader task per connection inside an unbounded worker pool, so an idle socket only parks its own worker — the serial dispatch loop is unaffected. Verified directly that sequential requests on one connection now work (Re-using existing connection → 200, 200).

    2. Wrong-content / missing-marker failures — load()'s per-rule fallback ended with selected = rules[0].summary.id after await Promise.all(...). A click that landed while per-rule fetches were still in flight was reverted to rule 0 the moment the last fetch settled, so the pane rendered off's statements for whichever rule was opened (traces show all fetches resolving correctly while selected flipped back to 0). This produced both the stale-assertion failures and, when the revert landed inside one poll interval, the title-timeout variant.

    3. ENOTEMPTY exits — Chrome keeps writing profile files briefly after SIGKILL; rmSync now retries instead of failing a green run.

    Verification: 31 consecutive npm run test:e2e runs on Google Chrome with zero failures (the unfixed tree failed ~30 % of runs); cargo test -p wright-cli --all-features, clippy, fmt, npm test, and svelte-check all clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions