Skip to content

test(e2e): ask Orbit to quit from a task of its own - #16

Merged
siyabendoezdemir merged 6 commits into
mainfrom
lead/quit-task
Oct 10, 2026
Merged

siyabendoezdemir merged 6 commits into
mainfrom
lead/quit-task

Conversation

@siyabendoezdemir

@siyabendoezdemir siyabendoezdemir commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

The e2e quit check (D28) caught an exception in archive's gate run that wasn't Orbit's. As Orbit quit, main logged TypeError: timer._onTimeout is not a function (media.spec.ts:1485, PR #12's gate run). This PR fixes the harness.

Cause

  • Playwright's app.close() calls app.quit() through the inspector (electronHandle.evaluate(({ app }) => app.quit())).
  • Node's timer loop checks that a due timer is still set, then calls it.
    • Orbit's quit handlers clear pending timers: persistence.flush() clears the state file's next write, and history's flushSync() does the same.
    • Run between that check and the call, they make Node call a cleared timer.
  • Reproduced outside Orbit with Electron's own Node (v24.21). A worker's inspector session kept evaluating "clear every timer" while 200,000 timers fired. It landed mid-dispatch in 10 of 10 runs, and threw this error in 3.
  • Real quits come from a menu, a shortcut or the OS as ordinary tasks, so users can't hit this.

Fix (tests/e2e/helpers.ts)

  • app.close() first asks Orbit to quit from a task of its own. The ask goes through the harness's deferred evaluate, and since f49d66a the evaluated function also waits for a task of its own (review F3).
  • It then waits for Orbit to exit, and only then calls Playwright's close, which finds Orbit gone (review M1). Playwright's close always asks again through the inspector: called any earlier, it could finish a quit Orbit had cancelled, and it could run inside a timer again.
  • An app that already quit on its own skips the ask.
  • The kill clocks are unchanged and bound the wait: 35 s from app.close() for the quit to reach main, then 15 s for it to finish.

Tests (core.spec.ts)

  • "[TB-22] app.close() asks Orbit to quit between tasks, never part-way through a timer (D28)". One timer keeps main busy for 1.5 s as the test closes it, and a before-quit listener logs whether it ran inside a timer.
    • With Playwright's raw close: 4 of 4 runs fail ("quit handler inside a timer: true").
    • With the fix: they pass.
    • The first version used ten 10 ms intervals; the review found it passed without the fix in 3 of 12 runs (F1).
  • "[TB-22] a quit Orbit cancels fails the test" (test.fail(), review M1). Orbit cancels the first quit, as a "quit anyway?" question would.
    • Without the wait for exit: "Expected to fail, but passed", 2 of 2.
    • With it: the quit check kills Orbit after 15 s and fails the test, as D28 intends.
  • With the fix, at load 46–51: both tests passed 3 of 3, and core.spec.ts passed 21 of 21. Lint and typecheck: exit 0.

Merge gate (D42)

Run locally on the Linux VPS on this PR's head, e4486e4 (main at 32b8894, with PR #14, merged in):

npm run build; npm run typecheck; npm run lint; ./node_modules/.bin/vitest run
xvfb-run -a -s "-screen 0 1440x900x24" ./node_modules/.bin/playwright test --workers 1 --retries 0 --reporter=line

Review

🤖 Generated with Claude Code


Note

Low Risk
Changes are limited to e2e harness teardown and new tests; no production app code.

Overview
Fixes the e2e D28 teardown so Orbit is asked to quit from the main process between Node tasks, instead of relying on Playwright’s inspector-driven app.quit() first—which could run before-quit handlers in the middle of timer dispatch and trigger spurious timer._onTimeout is not a function failures.

In tests/e2e/helpers.ts, wrapped app.close() now calls new askQuit() (deferred setImmediate + app.quit(), up to 10s) and only then Playwright’s native close, after the app has actually exited; apps that already quit skip the ask. Existing quit timeouts (35s / 15s) are unchanged.

tests/e2e/core.spec.ts adds [TB-22] coverage: one test asserts quit handlers never run inside a busy timer during close; another (test.fail) documents that a cancelled quit must not be “completed” by Playwright’s second quit attempt.

Reviewed by Cursor Bugbot for commit f49d66a. Bugbot is set up for automated code reviews on this repo. Configure here.

siyabendoezdemir and others added 3 commits October 10, 2026 06:14
Playwright's app.close() calls app.quit() through the inspector, which can run
Orbit's quit handlers part-way through other main-process code, even between
Node's check that a due timer is still set and its call to it. A handler that
clears that timer there (persistence.flush() clears the state file's next
write) makes Node call a cleared timer: "timer._onTimeout is not a function",
logged as an exception nothing caught. Seen once in archive's gate run
(media.spec.ts:1485).

The harness now asks for the quit first through its deferred evaluate, which
starts in a task of its own. Electron emits before-quit once, so Playwright's
own app.quit() then finds Orbit quitting and does nothing.

The new core test keeps main busy in timers for about 3 s and records where
the quit handler runs; it fails without the ask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a fixed 3 s burst, Playwright's own work before it asks (flushing the
trace) sometimes outlasted the burst, so the test passed without the fix in 1
of 2 runs. Ten 10 ms intervals keep main in timers until the quit: without the
ask, 6 of 6 runs fail; with it, 4 of 4 pass.

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

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: a398f076-d089-475d-a958-a55cdcc511fc)

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: ec06eb5d-1ca9-4287-810a-ad6d940422bf)

No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
No conflicts.

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

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: f0cc9473-167b-4d64-af25-d7c5c7efeb13)

…one; a cancelled quit fails (PR #16 review M1)

Review M1: Playwright's close always asks Orbit to quit again, through the inspector. Called as soon
as the harness's own ask returned, it finished a quit Orbit had cancelled, so the test passed in
0.2 s where D28 should fail it, and that second ask could again run inside a timer. app.close() now
waits for Orbit to exit after its own ask, and calls Playwright's close only then. The 15 s and 35 s
budgets still bound the wait and kill Orbit if needed. The ask also waits for a task of its own
inside the evaluated function (F3), so it stays deferred where app.evaluate isn't wrapped.

New test: a quit Orbit cancels once fails the test (test.fail). Without the wait for exit, 2 of 2
runs report "Expected to fail, but passed". With it, the failure is the quit check's: "still
running: it had the quit for 15 s".

F1: the timer test now keeps main busy in one 1.5 s timer as the test closes it. Playwright's raw
close fails it every time (4 of 4, "quit handler inside a timer: true"); with ten 10 ms intervals it
passed 3 of 12 runs.

With the fix, at load 46-51: both quit tests passed 3 of 3; core.spec.ts 21 passed.

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

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 2236e6f9-9157-4db7-9dcc-6470d2e6bcbc)

siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
One conflict, in tests/e2e/core.spec.ts: both sides added a test after TB-22's quit-timer test.
Kept both: PR #16's cancelled-quit test first, beside the other quit test, then this branch's
evaluate test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@siyabendoezdemir
siyabendoezdemir merged commit 8d635d5 into main Oct 10, 2026
1 check passed
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 11, 2026
… task of its own (PR #19 review F7)

- The test hook's flush() writes the state file from a task of its own (later), as the other hooks
  do, so a flush asked through the inspector no longer runs part-way through other main-process code.
  flushNow() writes in the calling task, for an evaluate that checks the state first and must write in
  the same turn (downloads' saveStateWhen).
- scripts/perf/run.mjs and scripts/capture/record.mjs quit Orbit as the e2e harness does (askQuit,
  PR #16): app.quit() from a task of its own, then Playwright's close only cleans up. The capture
  script's setBounds runs from a task of its own too.
- run.mjs takes --cold-only, Orbit's cold start alone, for the loading view's cold-start rounds in
  PERF.md (PR #18 fix check 3, F5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant