feat(ci): cursor-monitor helper + wrap live smoke tests (#540)#575
Merged
feat(ci): cursor-monitor helper + wrap live smoke tests (#540)#575
Conversation
shaun0927
added a commit
that referenced
this pull request
Apr 16, 2026
Commit 5527d3f ("Stabilize the stacked headless smoke jobs") accidentally re-declared `wsToHttpUrl` in `src/flutter/vm-service-discovery.ts` — the documented implementation at lines 99-102 is preceded by an identical undocumented copy at lines 89-92. ts-loader surfaces this as TS2323 + TS2393 during `npm run build`, which in turn fails the `lint` and `test` jobs in `.github/workflows/ci.yml` (both run `npm run build` through the `prepare` hook). Remove the undocumented duplicate; keep the documented one. Verified: - `npm run build:src` now compiles cleanly (0 errors). - `npx jest tests/unit/flutter-vm-service.test.ts` — 21/21 pass. - `git blame` confirms the duplicate was introduced on 2026-04-16 and not used by any caller outside the file itself. This hotfix unblocks CI on develop and on every in-flight PR (#575, #576, #577). Refs: #540 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 tasks
27c3678 to
cc475aa
Compare
shaun0927
commented
Apr 16, 2026
Owner
Author
shaun0927
left a comment
There was a problem hiding this comment.
Code review: P0=0, P1=0 (fixed in latest push), P2=1. Signal handler race fixed — handlers now registered before process.run(). 'goroutine' comment corrected. Clean to merge.
Adds a Swift `cursor-monitor` binary that wraps a child process, polls `CGEventGetLocation` on a background thread, and fails the step if the host OS cursor drifts beyond a threshold while the child runs. Degrades to a warn-only mode when no WindowServer is present (e.g. a VM without a virtual display) so this does not regress any headless scenario today. Wires the monitor into the three live jest invocations in `.github/workflows/headless-smoke.yml` (Flutter / Native simhid / WebView) via a small `scripts/run-with-cursor-monitor.js` wrapper that resolves `dist/cursor-monitor` first and falls back to `swift` interpreter if the compiled binary is absent. This moves epic #540 toward the acceptance criterion "Integration tests never move the mouse (CI `CGEventGetLocation` monitor)" without forcing every developer to install anything extra — the monitor is just another Swift binary in `dist/` next to `ax-bridge` and `sim-hid-bridge`. Verified: - `swiftc -O src/native/cursor-monitor.swift -o /tmp/cursor-monitor && \ /tmp/cursor-monitor -- /bin/echo hello` reports max_delta_px=0 and exits 0. - `node scripts/run-with-cursor-monitor.js -- /bin/echo wrapper` uses the binary when available, falls back to `swift` interpreter, and forwards the child's exit code. - `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/headless-smoke.yml'))"` accepts the modified workflow. - `npm run lint -- scripts/run-with-cursor-monitor.js` passes (0 new errors). Refs: #540 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Swift's Process.executableURL with URL(fileURLWithPath:) does not search PATH, so bare command names like 'npx' fail with "file doesn't exist". Route through /usr/bin/env to get standard PATH resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move SIGINT/SIGTERM handler registration before `process.run()` to close the race window where a signal arriving between run() and registration would kill the parent without forwarding to the child, potentially leaving zombie jest processes in CI. Also fix "goroutine" → "dispatch block" in comment (this is Swift, not Go).
34876fa to
7a8e92a
Compare
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/native/cursor-monitor.swift— a small Swift helper that wraps a child process, pollsCGEventGetLocationon a background thread, and exits non-zero if the host cursor drifts beyond a threshold (default 0.5px) during the child's lifetime.scripts/run-with-cursor-monitor.js— a thin Node wrapper that prefersdist/cursor-monitorand falls back toswiftinterpreter..github/workflows/headless-smoke.yml(Flutter VM, Native simhid, WebView).Refs: #540 — moves the acceptance-criteria box "Integration tests never move the mouse (CI
CGEventGetLocationmonitor)" from ☐ to demonstrable CI guard.Why
#540 requires integration tests to never reposition the host cursor. Today the workflow only enforces posture via env vars (
OPENSAFARI_HEADLESS_ONLY=1,OPENSAFARI_ALLOW_FOCUS_INPUTunset). Those block construction-time AppleScript, but a future regression could still emit synthesized mouse events at runtime. A direct cursor-position assertion closes that loop.Design notes
CGEventGetLocationcall returns NaN (headless VM without a virtual display), the monitor logs a warn line and runs the child unmonitored. This way the change is a safety-net, not a gate that can mis-fire.dist/— builds alongsideax-bridge/sim-hid-bridgethrough the existing swiftc + codesign fallback pattern inpackage.jsonscripts.Test plan
swiftc -O src/native/cursor-monitor.swift -o /tmp/cursor-monitor-test && /tmp/cursor-monitor-test -- /bin/echo hello→ reportsmax_delta_px=0, exits 0.node scripts/run-with-cursor-monitor.js -- /bin/echo wrapper→ interpreter-fallback path works, child stdout forwarded.python3 -c "import yaml; yaml.safe_load(open('.github/workflows/headless-smoke.yml'))".npm run lint -- scripts/run-with-cursor-monitor.js→ 0 new errors.developafter merge — expect[cursor-monitor] ...JSON line in each of the three live jest steps andmax_delta_px=0throughout.🤖 Generated with Claude Code