| name | ios-simulator-microscope |
| description | Reproduce and measure iOS-Safari-only layout, rendering, and interaction bugs on a booted iOS Simulator (iPad/iPhone) — the real-device counterpart to the desktop-Playwright browser-microscope. Drives Safari via safaridriver WebDriver + xcrun simctl to inject JS, read live geometry / computed styles / viewport units, and screenshot the device; uses macOS CGEvent drags to scroll and collapse the toolbar so dynamic-viewport dynamics appear. Use when a bug only shows in real iOS Safari and not in Playwright/Chromium/jsdom: dynamic viewport (dvh/svh/lvh) or stale initial-containing-block gaps, 100vh/100% sizing wrong after toolbar collapse, :focus-visible over-matching on programmatic or touch focus, momentum-scroll or rubber-band glitches, sticky/fixed drift, top-layer <dialog>/sheet coverage, safe-area insets, or any "works in Storybook but broken on iPad" report. Trigger phrases: "test on the iPad simulator", "reproduce this on iOS", "iOS Safari only bug", "check on real Safari". |
| license | MIT |
| compatibility | macOS with Xcode + iOS Simulator runtimes (xcrun simctl, safaridriver). Node.js >= 18 for the session and bridge scripts; Swift toolchain for the CGEvent helpers. Terminal needs Accessibility permission for scroll/tap. |
| allowed-tools | ["Bash","Read"] |
| argument-hint | [device] [url] [what to inspect] |
| metadata | {"author":"oleg","version":"1.2"} |
Purpose
Reproduce and measure layout, rendering, and interaction bugs that only manifest
in real iOS Safari — the ones desktop Playwright (browser-microscope), headless
WebKit, and jsdom are structurally blind to because they don't model the iOS
toolbar, dynamic viewport, or touch input. It boots a Simulator, drives Safari
through safaridriver, injects JS to read exact geometry and viewport-unit
resolution, screenshots the device, and scrolls via macOS CGEvent to trigger
toolbar-collapse dynamics — turning "looks wrong on my iPad" into measured
numbers.
Prerequisites
All environmental preconditions are checked by ./scripts/preflight.sh (workflow step 1) — macOS, Xcode + a Simulator runtime, safaridriver, Node.js >= 18, a Swift toolchain, and Accessibility permission for the terminal. The script only checks and instructs; the human-only setup actions it may ask for are:
safaridriver --enable (one-time, prompts for auth).
- Granting the terminal Accessibility permission (System Settings → Privacy & Security → Accessibility; applies to new processes without a restart).
- Installing Xcode / an iOS Simulator runtime if absent.
Variables
DRIVER_PORT: 4444 # Port for safaridriver -p; scripts read SIM_DRIVER=http://localhost:4444
BRIDGE_PORT: 8899 # Port for sim-bridge.mjs serve (Mode B); scripts read SIM_BRIDGE_PORT
DEVICE_MATCH: iPad Air 13 # Substring to pick the device from simctl list (match the bug report's device class)
Pick a mode first
safaridriver and CGEvent gestures are mutually exclusive. A synthetic drag is
indistinguishable from a finger, so during a WebDriver session it raises the native
"Safari is Running an Automated Test" alert, which swallows that gesture and every
one after it. Choose before booting:
- Mode A — safaridriver (
sim-session.mjs): static measurement of a page as
loaded. JS injection, geometry, viewport units, page screenshots. No gestures.
- Mode B — bridge (
sim-bridge.mjs): anything involving a real gesture —
scrolling, toolbar collapse, momentum, rubber-band, touch focus. The bridge
reverse-proxies the target and injects a measurement agent, so no WebDriver
session exists and the lock never appears. Read ./references/automation-lock.md.
If the task says "scroll", "collapse the toolbar", or "after scrolling" → Mode B.
Workflow
-
Confirm the environment