fix(tauri): pump the macOS main run loop so WebKit schedules work on a headless runner (#540) - #553
fix(tauri): pump the macOS main run loop so WebKit schedules work on a headless runner (#540)#553goosewobbler wants to merge 4 commits into
Conversation
…a headless runner Residual #540: even with the CSP fix (custom-protocol IPC working), the DirectEval legs still occasionally time out on the off-screen CI runner. The WebKit unified log shows the app's main run loop parked in -[NSApplication run] while WebContent's URL-scheme resource requests (page-load subresources) sit unserviced for ~27s — nothing drives the loop when there's no display. Schedule a repeating no-op NSTimer in the main run loop's common modes so it keeps waking and drains that pending work each tick. macOS-only, once, harmless with a real display (the loop already pumps continuously). Diagnostics kept in to validate; can't reproduce locally (a real display already pumps the loop), so CI is the adjudicator. Refs #540 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
|
Standing release PR: #456 · 15 packages queued · open 35d 6h 9m · ✅ ready to merge Release Preview — 17 packages
These changes will be added to the release PR (#456) when merged: Changelog@wdio/dioxus-bridge 1.0.0-next.3 → 1.0.0-next.4
@wdio/dioxus-service 1.0.0-next.3 → 1.0.0-next.4
wdio-dioxus-driver 1.0.0-next.3 → 1.0.0-next.4
wdio-dioxus-embedded-driver 1.0.0-next.3 → 1.0.0-next.4
@wdio/tauri-plugin 1.2.0 → 1.2.1
@wdio/tauri-service 1.2.0 → 1.2.1
tauri-plugin-wdio-webdriver 1.2.0 → 1.2.1
@wdio/flutter-service 1.0.0-next.1 → 1.0.0-next.2
wdio_flutter N/A → 0.1.1
@wdio/electrobun-service 0.1.0 → 0.1.1
@wdio/electron-service 10.1.0 → 10.1.1
@wdio/native-cdp-bridge 1.0.0 → 1.0.1
@wdio/native-core 1.0.0 → 1.0.1
@wdio/native-mobile-core 1.0.0 → 1.0.1
@wdio/native-spy 1.1.0 → 1.1.1
@wdio/native-types 2.4.0 → 2.4.1
@wdio/native-utils 2.5.0 → 2.5.1
@wdio/react-native-service 1.0.0-next.0 → 1.0.0-next.1
After merge — predicted release
Updated automatically by ReleaseKit |
|
| Filename | Overview |
|---|---|
| packages/tauri-plugin-webdriver/src/platform/macos.rs | Adds the correctly retained, main-thread-scheduled run-loop pump and temporary runner-state diagnostics; no new eligible failure remains. |
| packages/tauri-plugin-webdriver/Cargo.toml | Enables the objc2 Foundation and AppKit features required by the new macOS timer and diagnostics. |
Reviews (3): Last reviewed commit: "chore(tauri): TEMP diagnostic — log disp..." | Re-trigger Greptile
| let timer = NSTimer::timerWithTimeInterval_repeats_block(0.016, true, &block); | ||
| NSRunLoop::mainRunLoop().addTimer_forMode(&timer, NSRunLoopCommonModes); |
There was a problem hiding this comment.
There was a problem hiding this comment.
Coarsened the interval to 50ms (~20 wakeups/sec, a 3× cut) — the interval only bounds worst-case servicing latency, which just needs to stay well under the command timeout, so it doesn't need to be tight. Kept it unconditional rather than gated: this plugin ships only in WebDriver-automation builds (not production apps), and a reliable headless gate isn't available — the stall is the app being off-screen/inactive on the CI runner rather than "no display attached", so an NSScreen-empty check would misfire.
There was a problem hiding this comment.
That's a fair point — the WebDriver-only deployment context changes the calculus significantly. In a test harness that's already orchestrating a browser, 20 wakeups/sec is noise, and the risk of a gating heuristic (like NSScreen checks) introducing its own class of flakes outweighs the overhead concern.
50ms is a reasonable interval given the constraint you described — well under typical command timeouts while being 3× cheaper than the original.
The original concern stands for a general-purpose plugin, but the scoping argument holds here.
Shall I remember this as a rule: "this plugin is WebDriver-automation-only, so CI-reliability concerns take precedence over per-process overhead concerns when evaluating tradeoffs"?
There was a problem hiding this comment.
Yes — that's a good rule to remember: this plugin (@wdio/tauri-plugin-webdriver) ships only in WebDriver-automation builds, so CI/automation reliability takes precedence over per-process runtime overhead when weighing tradeoffs. Noting for future: a display/headless gate is still a desirable refinement here, just deferred until the pump's efficacy is confirmed over multiple runs and a reliable gate signal is found (NSScreen-empty is unreliable because the CI stall is app-inactive/off-screen, not no-display-attached).
There was a problem hiding this comment.
Rule saved. View rule
The nuance about NSScreen-empty being an unreliable gate (app-inactive vs. no-display-attached being distinct conditions) is captured — that distinction is exactly the kind of thing worth preserving so future suggestions don't chase the wrong signal.
Greptile P2: the pump timer ran unconditionally at 16ms (~62.5 wakeups/sec). The interval only bounds worst-case servicing latency (which just needs to be well under the command timeout), so coarsen it to 50ms — ~20/sec, a 3x cut — while keeping page-load/eval servicing responsive. The plugin ships only in WebDriver-automation builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…540 gate investigation Logs NSScreen.screens count, NSApplication.isActive, and occlusionState at pump-schedule time to determine whether a 'no display' gate is viable on the CI runner (local 26.5.1 reports screens=1 isActive=false, so a screens==0 gate is likely dead). Behaviour unchanged; to be reverted after the investigation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
… display diag The display/activation diagnostic answered its question: the CI runner reports screens=1 isActive=false occlusion=8192 — identical to a local machine with a display — so a 'no display' (or isActive/occlusion) gate can't distinguish the stalling-CI case from the working-local one. A display-based gate isn't viable; reverting that diagnostic (plugin back to the plain 50ms pump). To characterise the remaining intermittent macOS Tauri *package* failure (not IPC, not code-4), add the same WebKit-unified-log capture the e2e workflow has, scoped to macOS + tauri, so the next package failure is classifiable. Refs #540 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
Follow-up to #552, targeting the residual macOS-26.4 DirectEval flake.
Problem
After #552 fixed the root cause (malformed fixture CSP → postMessage IPC → pump stall) and hardened against WebKit code-4, a residual intermittent stall remained on the off-screen headless CI runner. The WebKit unified log on a failing run shows the app's main run loop parked in
-[NSApplication run] → _DPSNextEvent → mach_msg, while WebContent's URL-scheme resource requests (page-load subresources) sit unserviced for ~27s — nothing drives the loop when there's no display, so WebKit doesn't promptly schedule its work and the firstexecutetimes out.Fix
Schedule a repeating no-op
NSTimerin the main run loop's common modes (macOS-only, once). Each tick wakes the loop, which then drains the pending WebContent scheme-handler requests / eval dispatches — turning a multi-second stall into ≤ one tick. Harmless with a real display (the loop already pumps continuously), which is why this isn't reproducible locally — hence CI is the adjudicator.Validation
cargo check+ local standalone smoke green (no regression). Diagnostics (WebKit-log capture, inherited from #552) are retained so a failure is classifiable. Validating ungated on CI over several runs; will strip diagnostics once the DirectEval macOS legs are consistently green.Refs #540
🤖 Generated with Claude Code