Skip to content

test: run the WebKit webview tests concurrently, with the lifetime cycles on 7 lanes - #41175

Open
robobun wants to merge 1 commit into
mainfrom
robobun/a8793ae5/webview-test-speed
Open

robobun wants to merge 1 commit into
mainfrom
robobun/a8793ae5/webview-test-speed

Conversation

@robobun

@robobun robobun commented Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/js/bun/webview/webview.test.ts is one of the slowest files in CI: 72 to 83s on the macOS 15 lane and 115 to 123s on the macOS 26 lane (both Tart VMs), for example 76.6s in build #109310. The physical macOS boxes take 9s (arm64) and 15s (x64).
  • The time is process launches. The file opens about 110 WKWebViews one after the other, 70 of them in the 64-cycle lifetime test, and every view that navigates gets a WebContent process. That costs about 85ms per view on an M-series Mac and about 0.7s in the VMs.

Fix

Background

  • Bun.WebView on macOS is one host subprocess per bun process (the bun binary re-executed with BUN_INTERNAL_WEBVIEW_HOST). Each new Bun.WebView() is a WKWebView plus an off-screen NSWindow in that host; its first navigate() launches a WebContent process. Operations on different views run in parallel, so concurrent tests overlap the launches.
  • bun test runs consecutive test.concurrent tests as one group, up to --max-concurrency (default 20). The plain validation tests at the top of the file run first, then the whole macOS group. The lanes inside the file bound the number of live WebContent processes independently of that default.
  • Native key events go through the process-wide text input machinery (NSTextInputContext). With two key-input tests in flight, CI saw one press("Escape") arrive in the page as a stream of repeated keydowns (5 on x64, 610 on the VM), which is why those tests are serialized.
  • The rendering-dependent tests (rAF, CSS animation, native wheel) stay todo on CI, as before: the CI runners have no display, so CVDisplayLink never fires.
Notes

Timings of test/js/bun/webview/webview.test.ts on main, from the Buildkite job logs (the file runs in the parallel bucket, 4 files at a time):

build agent time
109310 darwin-arm64-ciabatta-tart-15 (macOS 15.7.7 VM) 76.6s
109305 darwin-arm64-focaccia-tart-15 71.7s
109250 darwin-arm64-baguette-tart-26 (macOS 26 VM) 114.8s
109218 darwin-arm64-challah-tart-26 123.2s
109117 darwin-arm64-challah-tart-26 119.3s
109305 darwin-aarch64-26.6.2 (physical) 8.4s
109201 darwin-aarch64-26.6.1 (physical) 9.0s
109310 darwin-x64-14.8.9 (physical) 15.7s

Per-test timings are not in the logs (only the file total), and the JUnit artifacts are on S3, which this environment cannot reach, so the breakdown is derived from the view count: 76.6s / 110 views is about 0.7s per view in the VM, 9s / 110 about 85ms on the physical arm64 box.

The first version of this PR (pooled views, 7 lifetime lanes, assertions rewritten) measured in build #109335: 9.79s in the parallel bucket on darwin-arm64-focaccia-tart-15, then 6.29s, 3.44s and 3.25s when the runner re-ran the file alone; 3.42s, 2.98s, 3.01s and 3.03s on darwin-x64-14.8.9. Two tests were red on every attempt, both from assertions that version added (two screenshot() calls in flight on one view, which the per-view slot guard rejects; a navigate() right after onNavigationFailed, which fires before the failed navigation's slot is released). A third test, press dispatches virtual keys, flaked once per lane with repeated Escape keydowns. The numbers for this version come from its own CI run.

The lanes() helper is the same as the one in #39078 (the Chrome sibling file); the two can share a module once either lands.

Linux runs, bun bd test test/js/bun/webview/webview.test.ts: before 2.10s and 2.17s (2 pass, 60 skip, 1 todo), after 2.06s and 2.07s (6 pass, 58 skip, 1 todo). Nothing but the constructor validation runs off macOS.

@robobun

robobun commented Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reworked to the scheduling change alone, waiting for CI on 607aa3d.

The first version (pooled views plus rewritten assertions) measured 9.79s in the parallel bucket on the macOS 15 VM and 3.4s on x64 (build 109335), against 72 to 123s and 15s on main, but two of the assertions it added were wrong on WebKit and a key-input test flaked under concurrency. This version keeps every test body as it is on main and changes only how the tests are scheduled: test.concurrent through lanes (own-view tests 4 at a time, native input 1 at a time), the 70 lifetime cycles on 7 lanes, and a validation table that runs on every platform. Linux: 6 pass, 58 skip, 1 todo.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 827fae17-25dd-4262-b4d5-c4f20ade8b1c

📥 Commits

Reviewing files that changed from the base of the PR and between d6af50f and 607aa3d.

📒 Files selected for processing (1)
  • test/js/bun/webview/webview.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


Walkthrough

Changes

The WebView test suite now uses lane-aware concurrency, structured constructor error checks, concurrent lifetime stress coverage, serialized native input tests, and child-process isolation for process-sensitive scenarios.

WebView Test Suite

Layer / File(s) Summary
Concurrency lanes and lifetime stress
test/js/bun/webview/webview.test.ts
The suite adds bounded concurrency lanes. The lifetime test runs seven concurrent lanes and validates instance creation and cleanup.
Constructor validation assertions
test/js/bun/webview/webview.test.ts
Constructor tests compare structured error fields for invalid dimensions and unsupported non-headless mode.
Serialized native input coverage
test/js/bun/webview/webview.test.ts
Click, typing, and key-press tests use the serialized input lane. The persistent datastore gate uses the lane-aware wrapper.
Child-process lifecycle isolation
test/js/bun/webview/webview.test.ts
Close, host-death, pending-promise rejection, and natural process-exit tests run in child processes.

Merge Risk: ⚪ Minimal · up to 607aa

The PR makes the WebKit webview tests run concurrently while preserving serialized native-input tests and adds cross-platform constructor validation; it should reduce CI time without changing product behavior, and no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: concurrent WebKit webview tests with seven lifetime-test lanes.
Description check ✅ Passed The description explains the problem, implementation, verification results, performance context, and test-scope constraints. It does not use the exact template headings, but it contains the required i…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, implementation, verification results, performance context, and test-scope constraints. It does not use the exact template headings, but it contains the required information and is sufficiently complete.


Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Sep 2, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread test/js/bun/webview/webview.test.ts Outdated
Comment thread test/js/bun/webview/webview.test.ts Outdated
…cles on 7 lanes

webview.test.ts opened about 110 WKWebViews one after the other, one
WebContent process launch each. In the Tart macOS VMs that run the macOS
15 and 26 CI lanes the file took 72 to 123 seconds.

Every macOS test now runs with test.concurrent, through a lane that caps
how many tests of a kind are in flight: own-view tests four at a time,
native input tests one at a time, child process tests unlaned. The 70
lifetime create/navigate/close cycles run on seven lanes; the cap they
guard against is on lifetime instances, not concurrent ones. The
constructor validation tests are a table with exact error shapes and run
on every platform.
@robobun robobun changed the title test: run the WebKit webview tests on pooled views, concurrently test: run the WebKit webview tests concurrently, with the lifetime cycles on 7 lanes Sep 2, 2026
@robobun
robobun force-pushed the robobun/a8793ae5/webview-test-speed branch from 54a665c to 607aa3d Compare September 2, 2026 14:36

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — both earlier points are addressed: skip now goes through test.concurrent.skip so the group stays contiguous, and the concurrent-screenshot() assertion is gone with the rest of the assertion rewrite.

Checked the lanes() semaphore (hand-off keeps busy counted, no over-admission), the 7-lane lifetime test (shared ++next is JS-atomic; created === 70 still proves the 65th lifetime instance navigated; watchdog is a rejecting deadline, not a sleep), and that the plain test/test.each validation cases sit before the first test.concurrent so they don't split the group. test.todo at EOF has nothing after it to split.

Extended reasoning...

Overview

Test-only refactor of test/js/bun/webview/webview.test.ts to run the macOS WebView tests concurrently instead of sequentially. Adds a lanes(width) counting-semaphore helper and tiered wrappers (it capped at 4 concurrent view-opening tests, itInput serialized for native key/mouse, itRendering todo-on-CI, itInChild/itConcurrent uncapped). The 70-cycle lifetime regression test is parallelized across 7 internal lanes with a shared next counter. Constructor validation is lifted into a platform-agnostic test.each with exact {name, code, message} assertions via a new thrown() helper. No production code touched.

Security risks

None. Test-only change; no auth, crypto, network egress, or untrusted input handling introduced. The only server usage remains local Bun.serve({ port: 0 }).

Level of scrutiny

Low-to-moderate. This is a test-file scheduling refactor with no runtime code changes. The concurrency helper is a textbook semaphore (hand off directly to a waiter or decrement busy). The lifetime test's invariant — that the 65th+ lifetime WKWebView can navigate — is preserved: ++next is atomic in single-threaded JS, each lane closes its view before pulling the next number, and expect(created).toBe(70) guards against a lane bailing early. The 30s watchdog is a rejecting race deadline (commented as such), not a sleep, and the strengthened expect(view.url).toBe(url) is an improvement over toStartWith.

Other factors

This is a re-review after a force-push. The prior version's two flagged issues are both resolved: the Promise.all of two screenshot() calls on one view was dropped along with the rest of the assertion rewrite (confirmed absent from the file), and skip is now test.concurrent.skip, so itPersistentDataStore on macOS < 15.2 no longer splits the concurrent group. The plain test / test.each validation cases are positioned before the first it(...), so the concurrent group starts fresh after them; the trailing test.todo at EOF has nothing to split. No CODEOWNERS entry covers this path. Exit reason was dry_streak.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant