feature(ios-web): Adding support for ios simulator, physical and web beta#54
Merged
Conversation
…paired-disconnected)
…m flow, control gating
…pace The screenshot covers only the CSS viewport (e.g. 1200x766) but maestro's element list includes below-the-fold nodes and a full-document wrapper whose bounds run far past it (e.g. 1860 tall). The frontend inferred the hierarchy coordinate space from the max bounds in the tree, so it picked up that document height and compressed the inspector overlay vertically. Two parts: the screenshot poller already reports the CSS viewport in the web_frame payload, but the paint hook overwrote it with the PNG's natural (devicePixelRatio) size; now we paint at native resolution but report the CSS size as the device coordinate space. And for web, hierarchyDims is the reported viewport (streamW/H) rather than the tree's max bounds. Together with the viewport-sized root (35b47ab) the overlay and hit-test line up; below-fold elements fall outside the visible canvas as expected. iOS is unaffected (its reported dims equal the bitmap's natural size).
maestro drives web via a Selenium-managed chromedriver that launches a headed Chrome. Our teardown SIGKILLs the `maestro studio` JVM (stop / kill_on_drop), which reaps neither the driver nor the browser, so headed Chrome windows piled up across sessions. Sweep the orphaned chromedriver + webdriver-flagged Chrome on web keeper start (cull a prior session's leftovers) and stop (close the window we opened). Matched by selenium/chromedriver and --test-type=webdriver markers a user's normal Chrome never carries, so personal browsing is untouched.
A fast Cmd+Q (macOS) or window close used to terminate the app without running the async teardown, leaving orphaned maestro studio / chromedriver / Chrome / iproxy processes behind (kill_on_drop doesn't fire on a hard exit). Now both the window CloseRequested and app-level ExitRequested are held: the backend emits `quit-requested` and the frontend either asks for confirmation (default) or, if the user opted out, quits straight away. Either path calls `confirm_quit`, which flips `quit_confirmed`, tears down every session via the extracted `teardown_all_sessions` (shared with disconnect_device), then exits — so nothing is orphaned regardless of the prompt. The dialog has a 'Don't ask me again' checkbox; the preference is persisted and re-enableable from Settings → General.
The default macOS app menu's Quit calls AppKit `terminate:`, which hard-exits without a preventable RunEvent::ExitRequested — so Cmd+Q bypassed the confirm-before-quit dialog (the window close button worked because it goes through WindowEvent::CloseRequested). Replace the menu on macOS with one whose Quit is a plain item bound to Cmd+Q that emits `quit-requested` instead of terminating; the rest mirrors the macOS defaults (predefined Edit/Window items keep native copy/paste/undo). The ExitRequested guard stays as a safety net for other exit paths; Windows/ Linux are unaffected (no app menu; they quit via the window close path).
Adds physical iPhone support alongside the existing iOS Simulator,
Android, and Web paths — purely additive. devicelab's maestro-ios-device
builds/runs the XCTest HTTP driver on the device (:22087) and forwards a
local port (6001) over usbmux, removing the go-ios tunnel blocker that
made stock maestro unable to drive a physical device.
- device: `physical: bool` on Device; ios discovery merges simctl sims +
`devicectl list devices` physical iPhones (connected-only: real hardware
with a transport, tunnelState != unavailable — a plugged-in iOS 17+
device idles at 'disconnected' and must still list).
- ios_session: IosDriverKeeper gains an IosTarget {Simulator, Physical}.
Physical spawns `maestro-ios-device --team-id --device`, HTTP client on
the forwarded port, and pulls preview frames from the driver's HTTP
/screenshot (sim path — simctl boot + maestro studio + simctl shots —
unchanged). Reuses wait_until_ready / hierarchy / input / deviceInfo.
- commands: ensure_ios_keeper reads device.physical from state; run_flow
physical branch keeps the bridge alive and runs via
spawn_ios_device_runner (`--driver-host-port`), unlike the sim path.
- runner: spawn_ios_device_runner with a `--driver-host-port` preflight
that surfaces a clear 'install devicelab patched maestro' error.
- tool_paths: maestro_ios_device binary path (+ getter, view, set).
- frontend: physical iPhones render in the main device list (booted=false
but physical=true) labelled '… · device'; tool-paths Settings field.
Not yet validated on a real device (user tests later): needs maestro-ios-
device installed, Xcode, an Apple Team ID, and a Developer-Mode iPhone.
161 Rust tests + 146 frontend tests, build/clippy/typecheck/lint green.
Removes the manual `curl … setup.sh` step for physical iOS — only users who actually want it trigger the download. New `install_ios_device_bridge` command fetches the devicelab release binary for the Mac's arch (arm64/amd64), marks it executable, runs its `setup` (which pulls the XCTest runner + patched maestro jars into ~/.maestro), and persists the path as the maestro_ios_device override. `ios_device_bridge_installed` backs an 'Install automatically' button in Settings → Tool paths (shown only when the bridge is missing). devicelab is Apache-2.0, so fetching its release is fine; we host nothing. Keeper's not-found error now points at that button. Build/clippy/typecheck/lint + 146 FE tests green.
Patched maestro 2.5.1 registers --driver-host-port with hidden=true, so it never appears in `maestro --help`. The physical-device flow-run preflight now probes `maestro --driver-host-port 6001 test --help` and treats the flag being accepted (no picocli "Unknown option"/"Unmatched argument") as support, instead of grepping help text — which gave a false negative and blocked physical-device runs.
Avoid flashing false ✗ rows (maestro/Xcode/bridge "not installed") before the async status probe and the parent's bridge/team-id load resolve — render a "Checking your setup…" placeholder until both are known.
The screenshot poller slept a fixed 350ms after each frame, so physical devices ran at ~1.8 fps (200ms capture + 350ms sleep). The driver's /screenshot is itself ~200ms (the device-side ceiling, ~5 fps, confirmed: parallel requests don't beat sequential — capture is serialized), so poll back-to-back for physical devices and let capture latency pace us. Errors back off 500ms to avoid spinning when the device is unplugged. Simulator cadence (pre-framebuffer fallback) is unchanged.
…hork) The in-app "Install automatically" now fetches the bridge + patched maestro 2.5.1 jars + XCTest runner from BlueShork/maestro-ios-device (public) instead of upstream devicelab, which only patches maestro 2.0.9-2.1.0. Updates the Tools hints accordingly. This makes physical-iPhone setup one-click for users on maestro 2.5.1.
The first physical-device connect builds the XCTest driver (~10 min), so the generic "Waiting for frames…" looked frozen. Show a dedicated state for physical iOS only: animated spinner, a phase message (connecting → building), the ~10 min heads-up, and a running elapsed timer so it's clearly progressing.
The inspect action menu is a React child of the device-view container that drives the inspector (hover on pointer-move, tap on pointer-down, swipe on wheel). React events bubble up the React tree regardless of the menu's fixed positioning, so moving/clicking/scrolling inside the menu re-triggered the inspector underneath (extra hovers and phantom taps on the device). Stop pointer/click/wheel/contextmenu propagation at the menu root; menu buttons sit deeper so their handlers still fire, and outside clicks still close it.
Maestro's command output mirrors ElementSelector.description(): a text selector
is quoted ("Welcome") but an id selector is unquoted (id: welcomeMessage), and
inputText prints its value unquoted. parseLine's regexes required quotes, so
id-based assertVisible/tapOn (and unquoted inputText) produced no StepEvent and
their editor lines never turned green. Capture the target up to the run
indicator / 'is visible', then normalize quoted text, 'id: <v>', or the raw
value to the bare arg so it matches the YAML AST. Verified against Maestro's
ElementSelectorTest ("Assert that id: hello_element is visible").
The Run→Stop toggle only fired after ipc.runFlow returned. On physical iOS that round-trip awaits maestro_supports_driver_host_port, which boots the maestro JVM (maestro test --help) on every run — several seconds where the button looked stuck on Run. - frontend: optimistic 'starting' state posted on click (disabled 'Starting…' button) before the backend round-trip; rolls back on error; re-entry guard so Cmd/Ctrl+R can't double-launch. Logs now clear in setStarting so early runner stdout isn't dropped. - backend: cache confirmed-good maestro bins so the JVM capability probe runs once per session instead of every run.
The Performance panel's <section> lacked h-full/min-h-0 (so its background stopped short of the pane height) and used a fixed w-[280px] shrink-0 (so a white gap showed on the right when the pane was wider). Match RunConsole: h-full min-h-0 w-full.
Adds an editor-style context menu to the Workspace tree: - file → Rename, Delete - folder → New file, New folder, Rename, Delete - empty area/root → New file, New folder New ContextMenu UI primitive wrapping @radix-ui/react-context-menu. Rename is inline (the label becomes a prefilled input with the base name selected). renameEntry/deleteEntry in workspace-ops repoint the open editor, lastOpenFile and expanded keys when a file or an ancestor folder is renamed/deleted, and remove folders recursively. Hover action buttons kept. Grants fs:allow-rename in the Tauri capabilities (rename was previously denied).
Bump version across package.json, tauri.conf.json, Cargo.toml; refresh BUSL Change Date; add v0.4.0 changelog and a macOS release-staging helper.
- install_ios_device_bridge returns early on non-macOS, but the Unix-only PermissionsExt::set_mode still has to compile on Windows. Move it into a #[cfg(unix)] block so the Windows CI build links. - Remove docs/physical-ios-setup.md and scripts/stage-release.sh.
The sim-capture-poll thread used objc2 accessors that panic on a nil/null return: every bare Retained<_> msg_send in attach() (nil context / device set / availableDevices / io / ioPorts), and IOSurface::baseAddress() (which returns NonNull and panics when a locked surface has no CPU-mappable base). With panic = abort (release profile) any such panic on this background thread aborts the whole process — which is why selecting a just-booted simulator crashed only the production build (in debug, panic=unwind just kills the poll thread silently and the app survives). Make every fragile call recoverable: nil objc returns become a descriptive AppError (or skip-this-device), and the base address is read via the raw, nullable IOSurfaceGetBaseAddress so the existing null guard actually fires. Adds ignored manual-repro tests against a booted sim.
…markdown components - flowUrl: the value class [^"'\n]+ overlapped the trailing whitespace quantifier, giving super-linear backtracking (S5852, a security hotspot). Capture the rest of the line with an unambiguous pattern and strip quotes/space in code; also switches to RegExp.exec (S6594). - UpdateDialog: hoist the react-markdown component map to module scope so the element factories aren't redefined on every render (clears the 'nested component definition' smells).
…eliability) The menu root already swallows pointer/click/wheel events so they don't leak to the inspector underneath; add an onKeyDown stopPropagation too. Coherent with that intent and clears Sonar's 'interactive element needs a keyboard listener' bug (Reliability New Code B -> A).
|
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.


Pull request
What
Why
Closes #
How
Checklist
feat(inspector): ...)pnpm typecheck && pnpm lintpassescargo fmt --check && cargo clippy -- -D warningspassescargo test --libpassesunwrap()/expect()in production code paths