Skip to content

test: harness follow-ups from PR #10's review (no DNS lookups, deferred evaluateHandle, SV-25/SV-26, perf) - #19

Merged
siyabendoezdemir merged 10 commits into
mainfrom
lead/harness2
Oct 10, 2026
Merged

siyabendoezdemir merged 10 commits into
mainfrom
lead/harness2

Conversation

@siyabendoezdemir

@siyabendoezdemir siyabendoezdemir commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Test-harness follow-ups from PR #10's review: F2, F4, F5, F6 and F7. Test code and scripts only; nothing in the app changes. It is stacked on PR #17, which is stacked on PR #16, and all three contain main with settings (PR #15).

What changes

  • F2: tests no longer make DNS lookups (49c8b6a).
    • The dead proxy stops every request, but Chromium looks up a link's host before anyone clicks it. PK-04's link to meet.google.com sent queries upstream.
    • Orbit now launches with --host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost, EXCLUDE 127.0.0.1. This applies in the e2e harness, the capture script and the perf script. The perf script also had no dead proxy, for Orbit or its Chromium baseline; it now has both.
    • A test's own --host-resolver-rules go first, so a later lane can still map a host or make one fail (pages lane, NV-13). Chromium checks every EXCLUDE before any MAP and looks an excluded host up for real, so a test's own EXCLUDE may only name a loopback host; offlineArgs() throws otherwise (4345e72, review F3).
    • Onboarding's first-run launches (launchFirstRun) get the same rules (4345e72, review F1). A note says the rules don't stop an mDNS lookup, which nothing asks for (F4).
  • F4: evaluateHandle is deferred too, and method shorthands work (162d58d).
    • The harness already runs app.evaluate in a task of its own, so it never lands part-way through main's code. app.evaluateHandle still ran by interrupt.
    • The wrapper threw on a method shorthand (({ m(_e, a) {…} }).m), which Playwright's own evaluate accepts.
    • The relaunch test's two calls through app.browserWindow(page) (a handle, whose own evaluate can't be deferred) now use app.evaluate.
  • F5: the perf script's timings (c8957f6).
    • The tab switch times the command alone, without the test hook's deferral hop.
    • The direct ctx.tabs.sleep call starts between tasks.
    • A new --port option sets the fixture server's first port.
  • F6: SV-26 never passes without seeing the grow-in (eba76a7).
    • When the renderer was starved, both shape checks were skipped and the test passed on the end state alone.
    • Now that round's pane is closed and a new one grows in, up to three rounds (as for timing budgets, D46). If no round's frames came in time, the test fails and names each round's frame counts.
    • After the review (4ead39e):
      • A retry waits for the last close to finish before adding its pane (M1). Until the closed pane is gone and its ghost has faded, the neighbour may not reach the new pane's edge, and the new pane appears at full size.
      • The gate asks for 3 frames by 300 ms, not 8: a frame by 100 ms and two while the pane grows are what the shape checks need (M2).
  • F7: SV-25's sidebar test checks that its drag is live (eba76a7), so it can't pass because no drag started. Since 4ead39e it checks again just before its count, after the 900 ms wait (review F9).

Left for later: F3 (waitOffline checks sessions only at launch). A check of sessions made mid-test needs a way for a test to opt out when it sets a proxy on purpose. No session leaked in the traced run.

Tests that fail without the change

  • F2: the new smoke test resolved example.com and localhost in every profile's session. Without the rules: Expected substring "ERR_NAME_NOT_RESOLVED", Received "2606:4700:10::6814:179a … 104.20.23.154 172.66.147.243".
    • Since 4345e72 it probes 127.0.0.2 instead (review F5). An address needs no lookup, so only the rules refuse it, and a missing rule can't make the test itself send a query. With the rules missing only that address: Received string "127.0.0.2".
  • F4: the new TB-22 test keeps main busy in timers and asks five times through each path.
    • Without the evaluateHandle wrapper: Expected false, Received true (the handle's code ran inside a timer).
    • Without the shorthand change: SyntaxError: Unexpected token '{'.
  • F6 and F7 (test changes): checked with two scratch edits, taking the press out of SV-25's drag and making the sampler miss every frame before 300 ms. The old tests both passed. The new ones fail:
    • SV-25: Expected pattern /\bis-dragging-tab\b/, Received string "".
    • SV-26: "frames never came in time to show the grow-in (round 1: 0 frames by 100 ms, 0 by 300 ms; …)".
  • Review M1 and M2 (scratch copies of SV-26, at load 25–43):
    • M1, rounds 1 and 2 forced to retry, 10 runs each:
      • without the wait, 6 of the 20 panes added right after a close entered at full size, and 2 runs failed (Expected: < 389.6, Received: 487);
      • with it, 0 of 20 entered at full size, and 10 of 10 runs passed.
    • M2, the shell renderer kept busy 45 ms per frame:
      • gated at 8 frames, 3 of 3 runs failed with "frames never came in time", though every round grew from width 0 with 4–6 frames by 300 ms;
      • gated at 3, 3 of 3 passed, with both shape checks run.
  • The EXCLUDE guard (scratch spec):
    • example.com, host.example and 127.0.0.2 are refused;
    • localhost, x.localhost and ::1 pass through, in order.
  • smoke, onboarding and split, with the guard's scratch: 53 passed.
  • F5: one perf run, offline, on ports 3390+: exit 0; tab switch 5.7 ms median in main and 25 ms until painted.

Merging

Merge gate (D42, D50)

Run locally on the Linux VPS on this PR's head as it was, 42c9819. It contains PR #16's head 2019597, PR #17's head 728fc45 and main (60da889, with settings):

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
  • Build, typecheck and lint: exit 0.
  • E2E at 1440×900, one worker, no retries: 305 passed, 0 failed, 2 skipped, exit 0, in 19.3 min (07:50–08:10 UTC, load average 16–43).
    • The two skipped are media's test.fixme frame tests (MD-01, MD-09).
    • The run includes settings' 36 tests under the new resolver rules.
    • Main logged no uncaught exception, and no quit was killed.
  • Unit: 434 of 435. The failure was PR test(commandbar): judge CB-22's budgets on the best of up to three rounds #17's CB-22 ranking bench. At a load average of 39, beside vitest's other workers, all three of its runs missed the 30 ms p95 in wall time: 54.5, 30.3 and 51.6 ms. Alone it passed 4 of 4.
  • On the new head 7be3b2b (08:15 UTC, load 17–21): build, typecheck and lint exit 0; unit 435 of 435 in 49 files. In that run the bench's wall p95 was 36.3 ms, which the old test would have failed, and its CPU p95 was 8.4 ms.
    • git diff --stat 42c9819 7be3b2b shows tests/unit/commandbar.test.ts only (+24/−8). The e2e suite loads no unit test, so the e2e result above carries over (D50).
  • macOS: unverified. The macOS e2e run needs a Mac. The change is test code and scripts only.

Merge gate after the review (D42, D50)

Run locally on the Linux VPS on this PR's head 4345e72. It contains PR #16's head f49d66a, PR #17's head 2c9e366 and main (60da889):

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
  • Build, typecheck and lint: exit 0. Unit: 435 of 435 in 49 files.
  • E2E at 1440×900, one worker, no retries: 306 passed, 0 failed, 2 skipped, exit 0, in 16.2 min (10:25–10:41 UTC, load average 9–34).
    • The two skipped are media's test.fixme frame tests (MD-01, MD-09).
    • The one test more than the last gate is PR test(e2e): ask Orbit to quit from a task of its own #16's cancelled-quit test. It failed, as its test.fail() expects.
    • CB-22's e2e test met its budgets in round 1 of its first launch: p95 11.5 ms to results and 33.2 ms painted, open median 24.4 ms.
    • Main logged no uncaught exception, and no quit was killed outside the cancelled-quit test.
  • macOS: unverified. The macOS e2e run needs a Mac. The change is test code and scripts only.

Review

  • Independent review (reviews/lead-harness2.md): approve with 2 must-fixes, both in SV-26. The lead fixed both, with failing-first evidence above:
    • M1: a retry added its pane while the last close was still animating, so the pane appeared at full size;
    • M2: the gate asked for 8 frames by 300 ms, more than its checks need.
  • Follow-ups:
    • Done here: F1, F3, F4, F5 and F9.
    • Routed to the finish-views lane: F10, Orbit's entryRect fallback, which skips the grow-in when no old pane overlaps.
    • Open: F2 ([::1] and *.localhost fixtures; none is used), F6 (functionSource's error message), F7 (the scripts' remaining interrupts), F8 (a note on the perf script) and F11 (macOS SV-26; waitOffline mid-test).
  • The fix check for all three PRs runs on this head, 4345e72.

🤖 Generated with Claude Code


Note

Low Risk
Changes are limited to e2e helpers, test specs, and capture/perf scripts; production app code is untouched.

Overview
Test harness and tooling follow-ups from PR #10 review — no application code changes.

Offline isolation is tightened with Chromium --host-resolver-rules so hostnames (except localhost) fail to resolve, blocking DNS lookups before link clicks. Shared via new offlineArgs() in e2e, plus capture and perf scripts; perf also applies the dead proxy to Orbit and the Chromium baseline. A smoke test asserts resolver behavior across profile sessions.

Main-process Playwright hooks defer evaluateHandle like evaluate, support method shorthand via functionSource, and add TB-22; relaunch tests use app.evaluate instead of handle evaluate.

Perf harness adds --port, defers inspector-driven work with setImmediate for sleep/tab-switch timings, and times tab activation through ctx.commands.run directly.

Split e2e hardening: SV-25 asserts an active tab drag during the wait; SV-26 retries grow-in sampling up to three rounds so the test cannot pass on end state alone when frames are starved.

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

siyabendoezdemir and others added 6 commits October 10, 2026 07:41
…runs (PR #10 review F2)

The dead proxy stops every request, but Chromium looks up a link's host before anyone clicks
it: PK-04's link to meet.google.com sent A, AAAA and HTTPS queries upstream through
systemd-resolved. Orbit now launches with --host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE
localhost, EXCLUDE 127.0.0.1 in the e2e harness, the capture script and the perf script (Orbit
and its Chromium baseline; the perf script had no dead proxy either, and now has both).

A test's own --host-resolver-rules go first, so a lane can still map or fail a host of its own.

The new smoke test resolves example.com and localhost in every profile's session. Without the
rules it fails: Expected substring "ERR_NAME_NOT_RESOLVED", Received "2606:4700:10::6814:179a
… 104.20.23.154 172.66.147.243".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aluate (PR #10 review F4)

The harness defers app.evaluate to a task of its own, so it never lands part-way through main's
code, but app.evaluateHandle still ran by interrupt, and the wrapper threw on a method shorthand
(`({ m(_e, a) {…} }).m`: SyntaxError: Unexpected token '{') that Playwright's own evaluate runs.
Both now go through the same deferral, and a shorthand becomes a function as Playwright does it.
The relaunch test's two calls through app.browserWindow(page) (a handle whose own evaluate can't
be deferred) use app.evaluate instead.

The new TB-22 test keeps main busy in timers and asks five times through each path. Without the
evaluateHandle wrapper: Expected false, Received true (the handle's code ran inside a timer).
Without the shorthand change: SyntaxError: Unexpected token '{'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cks its drag is live (PR #10 review F6, F7)

SV-26: when no frame landed in the first 100 ms and fewer than 8 in the first 300 ms (a starved
renderer), both shape checks were skipped and the test passed on the end state alone. Now that
round's pane is closed and a new one grows in, up to three rounds (as for timing budgets, D46),
and the test fails, naming each round's frame counts, if none came in time. On the macOS CI VM
only the end state is checked, as before (D17).

SV-25's sidebar test passed if the drag never started. It now checks html.is-dragging-tab before
looking for a drop target.

With the press taken out of SV-25's drag and a sampler that misses every frame before 300 ms
(scratch edits), the old tests both pass; the new ones fail: "Expected pattern:
/\bis-dragging-tab\b/, Received string: """ and "frames never came in time to show the grow-in
(round 1: 0 frames by 100 ms, 0 by 300 ms; …)".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. Settings' tests run with the new resolver rules: their feeds and pages are on
localhost or 127.0.0.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… direct sleep call (PR #10 review F5)

__orbit.run waits for a task of its own before running a command, so tabSwitch.mainSide also
counted that hop (2.6 ms median in the review). The perf script now waits for the task itself and
times only ctx.commands.run. Its direct ctx.tabs.sleep calls, which bypass __orbit.run, start
between tasks too. A --port option sets the fixture server's first port (default 3350), so a run
can stay out of a port range another agent is using.

Run once offline (--runs 1 --no-chrome --port 3390): exit 0; tab switch 5.7 ms median in main,
25 ms until the shell painted it; 15.2 MB per sleeping tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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: 382eec85-0109-4b59-9266-30cc80670b77)

siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
… (D51)

The stack's gate on PR #19's head (42c9819) failed this bench. At a load average of 39, beside vitest's
other workers, all three runs missed the 30 ms p95 in wall time (54.5, 30.3 and 51.6 ms; medians 9-17 ms).
Alone it passed 4 of 4. Ranking is synchronous and never waits, so its cost is its thread's CPU time; wall
time on a loaded machine also counts the turns other processes get. The bench now times each keystroke
with process.threadCpuUsage() and logs the wall p95 of the same samples beside it. The e2e CB-22 test
still times the app in wall time, and the best of up to three runs stays (D46).

Alone and in three full unit runs at load 27-31: CPU p95 6.3-8.8 ms (p50 4.0 ms), while the same samples'
wall p95 was 15.7-26.3 ms; 435 of 435 each time. With a scratch 35 ms of CPU added to rank() for queries of
14 or more characters, all three runs fail at a CPU p95 of 39.3 ms ("expected 39.285 to be less than or
equal to 30").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead/harness2

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: 79704018-729a-452a-adfb-950a9f6f4c5c)

siyabendoezdemir and others added 3 commits October 10, 2026 10:14
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>
… asks for what its checks need (PR #19 review M1, M2)

M1: a retry added its pane while the last close was still animating. The neighbour then hadn't
grown past the new pane's edge, nothing overlapped it, and Orbit's entryRect falls back to the full
target, so the pane appeared at full size and the shape check failed ("Expected: < 389.6, Received:
487"). A retry now waits until the closed pane is gone from the shell and its ghost has faded.
Scratch (rounds 1-2 forced to retry, 10 runs each, load 34-43):
  - without the wait: 6 of the 20 panes added after a close entered at full size (round 2: 4 of
    10, round 3: 2 of 10), and 2 of 10 runs failed with the error above;
  - with it: 0 of 20, and 10 of 10 runs passed. Round 1, with the neighbour at rest: 0 of 20.

M2: the gate asked for 8 frames by 300 ms (about 27 fps); the two shape checks need a frame by
100 ms and two while the pane grows. A loaded renderer gives 4-7, so the test threw "frames never
came in time" on grow-ins it could judge. It now asks for 3. Scratch with the shell renderer kept
busy 45 ms per frame: gated at 8, 3 of 3 runs failed, though every round grew from width 0 with 1-2
frames by 100 ms and 4-6 by 300 ms; gated at 3, 3 of 3 passed in round 1, with both shape checks
run. A pane that jumps or a starved renderer still fails.

F9: SV-25 checks again that the drag is live just before its count, after the 900 ms wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ly for loopback; a probe only the rules refuse (PR #19 review F1, F3-F5)

F1: launchFirstRun (onboarding's first-run launches) passed only the dead proxy, which doesn't stop
speculative lookups. It now builds its switches with offlineArgs(), as launchOrbit does.

F3: Chromium checks every EXCLUDE rule before any MAP, wherever it is in the list, and looks an
excluded host up for real, so a test's own EXCLUDE turned real DNS back on for that host. The
comment said the first matching rule wins, which holds for MAP only. offlineArgs() now throws on an
own EXCLUDE for a host that isn't loopback (localhost, 127.0.0.1, ::1, *.localhost). No test passes
its own rules today. Checked with a scratch spec: example.com, host.example and 127.0.0.2 are
refused; localhost, x.localhost and ::1 pass through in order.

F4: a note beside the rules that they don't stop an mDNS lookup; nothing asks for one.

F5: the smoke test's lookup probe was example.com, which passes without the rules on a machine with
no DNS, and would itself send a query on one with DNS if the rules went missing. It now probes
127.0.0.2: an address needs no lookup, so only the rules refuse it. With the rules missing that one
address (scratch), the test fails: "Received string: 127.0.0.2".

zz-guard scratch, smoke, onboarding and split: 53 passed, at load 8-31.

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: 2b658f71-c8b3-4d63-84f1-d7e615b386f0)

@siyabendoezdemir
siyabendoezdemir changed the base branch from lead/budgets to main October 10, 2026 11:38
@siyabendoezdemir
siyabendoezdemir merged commit e672076 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>
siyabendoezdemir added a commit that referenced this pull request Oct 11, 2026
…hat is no expression (PR #19 review F6)

A getter, a computed method name or a bound function turned into broken source. The converted source
is now checked with new Function too, and when neither parses, functionSource throws Playwright's
"Passed function is not well-serializable!" before anything runs in main. core.spec's TB-22 harness
test checks all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 11, 2026
…SV-26)

entryRect took its start from the old pane that covered the new pane's place,
and returned the full target when that pane was closing in the same change (or
none covered it yet), so the new pane appeared at full size. It now looks only
at panes that stay, and otherwise starts the pane as a zero-width sliver on the
side its neighbour is (PR #19 review F10). The test adds a pane in the same
moment its neighbour's other side closes.

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