Interactive Simulator UX Audit (idb-driven)
Snapshot tests render a view in a fixed-size NSHostingView — they model neither
navigation, taps, the device safe area, nor the Dynamic Island. A whole class of bugs
only shows when something actually drives the app: a screen that never appears after
a selection, a flow that's unplayable when the user is signed out of a cloud account,
completion content clipped by the Dynamic Island. This skill is the audit loop that
catches those, using idb (Facebook's iOS Simulator automation tool) to tap, read the
accessibility tree, and capture screenshots against a booted Simulator.
When to invoke
- Asked to "test the UI", "find UX problems", or "drive the simulator".
- Verifying a navigation, modal, or completion flow actually renders end-to-end after a code change.
- Auditing safe-area / Dynamic-Island layout, or an offline / signed-out flow, that snapshot tests can't model.
- Debugging a report that only reproduces "in the app," not in any test.
Scope
Owns: live Simulator driving via idb, the audit loop, and the coordinate/build gotchas
below. Does not own: scripted, CI-run UI tests that launch and assert without a human
watching → host-driven-xcuitest-e2e; static pixel-diff regression gates → swift-testing-baseline.
Use this skill first to find a bug interactively; write a host-driven XCUITest afterward
to pin the fix.
Inputs
context: fork runs this skill in a subagent with no access to the conversation
history — it can't infer anything from earlier turns, only from the invocation
arguments and this file. When invoking (matches argument-hint: "[udid] [flow]"), supply:
udid — the target booted simulator's identifier (idb list-targets). One
fork drives exactly one simulator; never omit this and let the fork boot/pick one
implicitly — see "One booted simulator serializes all driving" under Gotchas, and
the fleet-sizing note under Preflight below for running several forks in parallel.
flow — what to audit: the screen/feature and the specific behavior in
question (e.g. "onboarding flow: verify the paywall's dismiss button returns to the
correct tab, not the root").
- Anything else the fork can't discover on its own: which app/scheme is under test,
whether the build is already installed (skip "Build + install the app under test"
below if so), and any account/state precondition (e.g. "drive it signed out").
Without these, the fork has no way to know which simulator to drive or what "done"
looks like — it starts from this file alone.
Prereq: install idb (one-time, not via Homebrew)
If your project's policy forbids Homebrew, a direct GitHub release download is a distinct,
usually-allowed path — confirm against your own policy, then:
idb_companion: download idb-companion.macos-arm64.tar.gz from
https://github.com/facebook/idb/releases → extract to e.g.
~/idb-tools/companion/idb-companion.macos-arm64/ (binary lives in bin/, with a sibling
Frameworks/ directory the binary loads via @executable_path). An objc
duplicate-class warning for FBProcess at launch is non-fatal. The asset filename
changes across releases — confirm the current one first with
gh release view --repo facebook/idb --json assets.
idb CLI: pip3 install --user fb-idb.
- Put both on
PATH. Symlink idb directly. For the companion, use a wrapper script
that execs the real binary's absolute path — a bare symlink breaks the
@executable_path/../Frameworks rpath and the companion fails to load its frameworks.
- Verify:
idb list-targets, then against a booted simulator's UDID,
idb ui describe-all --udid <udid> returns the accessibility tree (element frames +
labels) in device-point space (e.g. an iPhone 17 Pro reports 402×874 pt).
Preflight: how many simulators fit on this Mac
Before running multiple agents or audit sessions in parallel, size the fleet with
arithmetic, not a tool — steps 1-2 need nothing beyond Activity Monitor or xcrun simctl
and already give a usable answer for most cases.
- Measure your own per-simulator footprint. Boot one simulator running your actual
app, let it settle, then read its
phys_footprint — Activity Monitor's Memory column
for the simulator's processes (or sum it yourself via xcrun simctl spawn <udid> ...
if scripting). Runnable parallel count ≈ available RAM ÷ that measured number. Don't
adopt a fixed GB figure from a blog post or any tool's README as your budget — real
footprint shifts with iOS version, installed apps, and what the app under test does.
- Default answer: lower the parallel count, not the tooling. If stock simulators
already saturate the machine, that's the normal case — reduce how many agents/sessions
run at once until it fits. As a dated, third-party reference point only (not a catalog
default): on a 16 GB M1 Pro, stock simulators reportedly start thrashing around 5
concurrent instances (
simslim/README.md:9, verified 2026-09-03). If step 1's math
already gets you a workable number, stop here — step 3 is optional and unrelated to the
rest of this skill.
- Only if still constrained and willing to trade away some background services, a
persistent per-simulator daemon-disable is available via third-party tooling. Gate on
command -v simslim first — if it's absent, that's fine, stop at step 2, nothing else in
this skill depends on it. To install without Homebrew: go install github.com/mobai-app/simslim/cmd/simslim@latest (a Go toolchain can be provisioned
through mise, see mise-tool-management); or download the plain release tarball
directly, simslim-v0.8.0-macos-arm64.tar.gz from
https://github.com/MobAI-App/simslim/releases/download/v0.8.0/ (asset name/version
verified via gh release view MobAI-App/simslim, 2026-09-03 — simslim's own README
documents only Homebrew and go install, simslim/README.md:29-41, so this direct-tarball
path isn't in its docs either). Once present, it's one command per simulator: simslim on <udid> to disable, simslim off <udid> to revert.
- Persistence only survives reboot on iOS 18.5+ runtimes; older runtimes are rejected
before anything is touched (
simslim/README.md:303-307).
- Slimming drops Spotlight/in-Settings search, push notifications (
apsd) and StoreKit
testing (storekitd), and universal links (swcd) unless kept via --except/--keep
(simslim/README.md:342-347).
erase, delete+recreate, and "Erase All Content and Settings" all revert to stock; the
profile must be reapplied (simslim/README.md:331-336).
- This skill doesn't track simslim's CLI beyond the two commands above — its own README
is the source of truth for anything else.
Build + install the app under test
- Check the installed build version first, in the app's own Settings/About screen if it
has one. A stale install silently invalidates every finding in the session — confirm you're
testing the build you think you are before reporting anything as a bug.
- Typical build:
xcodebuild -workspace <App>.xcworkspace -scheme <Scheme> -sdk iphonesimulator -configuration Debug -destination 'platform=iOS Simulator,name=<device>' -derivedDataPath build/sim build, then xcrun simctl install <udid> <App.app>.
- Build from a normal checkout, not an ephemeral agent worktree, if your project keeps
gitignored build secrets (API keys, provisioning config) outside version control. A worktree
missing those files can make a Debug build crash at launch on a startup assertion — that's
an environment artifact of the worktree, not a code bug; don't chase it as one.
The drive loop
idb ui describe-all --udid <udid> # element frames + accessibility labels
idb ui tap --udid <udid> <x> <y> # tap at device-point coordinates
xcrun simctl io <udid> screenshot <path.png> # capture, then read the PNG and look at it
xcrun simctl io <udid> recordVideo <path.mp4> # capture a flow as video
recordVideo fails outright if <path.mp4> already exists (NSPOSIXErrorDomain code 17,
"file exists") — pass --force to overwrite, or rm -f <path.mp4> first, especially when re-running
the same recording path across attempts.
- Get tap coordinates from
describe-all, not from eyeballing a screenshot — a
screenshot is rendered at the device's pixel scale (commonly 3×), not point space. Tap the
center of an element's reported frame.
- Look at every screenshot. The accessibility tree tells you what elements exist; only
the rendered image shows clipping, overlap, empty space, unreadable glyphs, or wrong z-order.
- After each tap,
describe-all again before the next action — a tap can miss, dismiss an
unrelated system alert, or navigate further than expected, and you need to know where you
actually landed.
When describe-all legitimately returns an empty tree
idb's accessibility dump is not 100% reliable — observed in practice (and tracked upstream,
e.g. facebook/idb#767) to come back empty or missing elements on some view hierarchies, with no
element frame to tap from. When that happens, don't treat "no pixel-coordinate tapping" as
absolute: fall back to screenshot pixels ÷ device scale = points (e.g. a 1206×2622 px
screenshot at a 3× scale device → tap at pixel ÷ 3, so 402×874 pt for that iPhone 17 Pro) and
screenshot after every tap to confirm it landed correctly — this fallback is only safe
because you're verifying each step, not because the math is guaranteed accurate.
Gotchas
- Shells that don't word-split an unquoted variable (zsh, by default) will pass
"$xy" as one argument and fail with invalid int value if you built a coordinate string
like xy="201 488". Pass literal integers, or force splitting (${=xy} in zsh).
idb must be on PATH for any MCP or wrapper tool that shells out to it — without it,
taps fail with spawn idb ENOENT even though a plain screenshot still works (screenshot can
go through simctl alone; tapping cannot).
- One booted simulator serializes all driving. Don't run two agents or two audit
sessions against the same simulator concurrently — their taps collide. Running several
agents each against their own booted simulator is fine and is a fleet-sizing question,
not a driving one — see the Preflight section above.
- Stress layout deliberately:
xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large then relaunch to test Dynamic Type; reset with
content_size large. appearance dark|light for color scheme. System alerts (permission
prompts, sign-in sheets) persist across an app relaunch — dismiss them before reading the
app underneath.
- Reaching a hard-to-blind-tap end state (a puzzle win, a multi-step checkout): if the app
has a debug-only launch argument or hook that seeds a near-terminal state, use it rather than
trying to solve the app's own logic via taps — that's testing your tapping, not the UX.
What to probe (this is what snapshots miss)
- Negative / offline paths: airplane mode mid-flow, cloud account signed out, retry after
a failed network call, a purchase-restore with nothing to restore. Core functionality should
rarely hard-gate on an optional cloud/account state — verify it doesn't.
- "Online but signed out" is not the same as "offline" — test both. For any
cloud-backed screen, these diverge: offline, network calls fail fast (they throw
immediately, no connection to wait on); online-but-signed-out, the same calls can hang
(a real network round-trip stalls waiting on an unauthenticated container that never
resolves). A pass under airplane mode can mask a hang that only reproduces online.
- Account-gated features need a real signed-in test account in the simulator — a
cloud-save resume affordance or a leaderboard, for instance, may by design show nothing when
signed out, which is correct behavior, not a bug; don't flag graceful degradation as broken
without first confirming the same flow works signed in.
- Navigation / modals: does the destination screen actually appear after a selection; does
a close/leave action show its confirmation; back-stack behavior after several pushes.
- Safe area / Dynamic Island: overlay or completion content clipped or overlapping system
chrome on a notch/island device — invisible to a fixed-frame snapshot.
- End-to-end completion: a full success and a full failure path, including any step that
submits to an external service that might be unavailable.
File each finding with its screenshot as evidence and a repro; label it environmental (stale
build, wrong account state) versus a genuine bug, and re-verify on a fresh, correctly-built
install before reporting it as real.
Rationale
A snapshot test proves a view renders correctly given a state; it says nothing about
whether the app ever reaches that state through real interaction, or how it behaves at the
literal edges of a physical device (notch, Dynamic Island) that a fixed test harness frame
doesn't model. Driving the actual Simulator is the only check that covers the seam between
"the view is correct" and "the user can get there."
Deviation considerations
- No Simulator access (Linux CI, headless-only environment): this skill doesn't apply;
rely on
host-driven-xcuitest-e2e for automated coverage and snapshot tests for pixel
regressions instead.
- A pure macOS (AppKit/SwiftUI-Mac) app:
idb targets iOS/iPadOS/tvOS simulators only;
drive a Mac app with host-driven-xcuitest-e2e's window-frame-click pattern instead.
Common Mistakes
- Tapping from screenshot pixel coordinates without first trying
describe-all — taps
land at the wrong spot on any non-1× device unless converted (pixel ÷ scale); only fall back
to pixel math when describe-all legitimately returns an empty tree (see above), and verify
every such tap with a follow-up screenshot.
- Reporting a bug from a stale installed build — always confirm the running version first.
- Treating "online + signed out" and "offline" as one case — they exercise different code
paths (fail-fast vs. hang) and must both be driven separately.
- Running two sim-driving sessions against one booted simulator — taps interleave and
corrupt both audits' results.
- Chasing a worktree-only launch crash as a code bug when the project keeps build secrets
outside version control — rule out the environment first.
Review Checklist
Related skills
host-driven-xcuitest-e2e — turn a finding from this audit into an automated, CI-runnable regression test.
swift-testing-baseline — the static snapshot-testing layer this skill complements, not replaces.
ios-accessibility-engineering — Dynamic Type / VoiceOver checks that pair naturally with this audit loop.
mise-tool-management — the general non-Homebrew tool-install pattern behind the idb and simslim install steps above.
1---2name: interactive-simulator-ux-audit3description: Use when auditing an iOS/iPadOS app's live behavior in the Simulator — navigation, modals, back-stack, completion flows, safe-area/Dynamic-Island clipping, offline or signed-out states — bugs a fixed-size snapshot render structurally cannot show. Covers installing `idb` without Homebrew (direct GitHub release + an exec wrapper that preserves rpath), the describe-all → tap → screenshot drive loop, device-point vs screenshot-pixel coordinate spaces, and the "stale build" / "worktree launch crash" false-negative traps. Use when asked to "test the UI", "find UX problems", "drive the simulator", or verify an interactive flow end-to-end. Also covers sizing a parallel-simulator fleet — how many booted simulators fit in RAM when each agent drives its own.4---56# Interactive Simulator UX Audit (idb-driven)78Snapshot tests render a view in a fixed-size `NSHostingView` — they model neither9navigation, taps, the device safe area, nor the Dynamic Island. A whole class of bugs10only shows when something actually drives the app: a screen that never appears after11a selection, a flow that's unplayable when the user is signed out of a cloud account,12completion content clipped by the Dynamic Island. This skill is the audit loop that13catches those, using `idb` (Facebook's iOS Simulator automation tool) to tap, read the14accessibility tree, and capture screenshots against a **booted** Simulator.1516## When to invoke1718- Asked to "test the UI", "find UX problems", or "drive the simulator".19- Verifying a navigation, modal, or completion flow actually renders end-to-end after a code change.20- Auditing safe-area / Dynamic-Island layout, or an offline / signed-out flow, that snapshot tests can't model.21- Debugging a report that only reproduces "in the app," not in any test.2223## Scope2425Owns: live Simulator driving via `idb`, the audit loop, and the coordinate/build gotchas26below. Does **not** own: scripted, CI-run UI tests that launch and assert without a human27watching → `host-driven-xcuitest-e2e`; static pixel-diff regression gates → `swift-testing-baseline`.28Use this skill first to *find* a bug interactively; write a host-driven XCUITest afterward29to *pin* the fix.3031## Inputs3233`context: fork` runs this skill in a subagent with **no access to the conversation34history** — it can't infer anything from earlier turns, only from the invocation35arguments and this file. When invoking (matches `argument-hint: "[udid] [flow]"`), supply:3637- **`udid`** — the target **booted** simulator's identifier (`idb list-targets`). One38 fork drives exactly one simulator; never omit this and let the fork boot/pick one39 implicitly — see "One booted simulator serializes all driving" under Gotchas, and40 the fleet-sizing note under Preflight below for running several forks in parallel.41- **`flow`** — what to audit: the screen/feature and the specific behavior in42 question (e.g. "onboarding flow: verify the paywall's dismiss button returns to the43 correct tab, not the root").44- Anything else the fork can't discover on its own: which app/scheme is under test,45 whether the build is already installed (skip "Build + install the app under test"46 below if so), and any account/state precondition (e.g. "drive it signed out").4748Without these, the fork has no way to know which simulator to drive or what "done"49looks like — it starts from this file alone.5051## Prereq: install `idb` (one-time, not via Homebrew)5253If your project's policy forbids Homebrew, a direct GitHub release download is a distinct,54usually-allowed path — confirm against your own policy, then:55561. **`idb_companion`**: download `idb-companion.macos-arm64.tar.gz` from57 https://github.com/facebook/idb/releases → extract to e.g.58 `~/idb-tools/companion/idb-companion.macos-arm64/` (binary lives in `bin/`, with a sibling59 `Frameworks/` directory the binary loads via `@executable_path`). An objc60 duplicate-class warning for `FBProcess` at launch is non-fatal. The asset filename61 changes across releases — confirm the current one first with62 `gh release view --repo facebook/idb --json assets`.632. **`idb` CLI**: `pip3 install --user fb-idb`.643. Put both on `PATH`. Symlink `idb` directly. For the companion, use a **wrapper script**65 that `exec`s the real binary's *absolute path* — a bare symlink breaks the66 `@executable_path/../Frameworks` rpath and the companion fails to load its frameworks.674. Verify: `idb list-targets`, then against a booted simulator's UDID,68 `idb ui describe-all --udid <udid>` returns the accessibility tree (element frames +69 labels) in **device-point** space (e.g. an iPhone 17 Pro reports 402×874 pt).7071## Preflight: how many simulators fit on this Mac7273Before running multiple agents or audit sessions in parallel, size the fleet with74arithmetic, not a tool — steps 1-2 need nothing beyond Activity Monitor or `xcrun simctl`75and already give a usable answer for most cases.76771. **Measure your own per-simulator footprint.** Boot one simulator running your actual78 app, let it settle, then read its `phys_footprint` — Activity Monitor's Memory column79 for the simulator's processes (or sum it yourself via `xcrun simctl spawn <udid> ...`80 if scripting). Runnable parallel count ≈ available RAM ÷ that measured number. Don't81 adopt a fixed GB figure from a blog post or any tool's README as your budget — real82 footprint shifts with iOS version, installed apps, and what the app under test does.832. **Default answer: lower the parallel count, not the tooling.** If stock simulators84 already saturate the machine, that's the normal case — reduce how many agents/sessions85 run at once until it fits. As a dated, third-party reference point only (not a catalog86 default): on a 16 GB M1 Pro, stock simulators reportedly start thrashing around 587 concurrent instances (`simslim/README.md:9`, verified 2026-09-03). If step 1's math88 already gets you a workable number, stop here — step 3 is optional and unrelated to the89 rest of this skill.903. **Only if still constrained and willing to trade away some background services**, a91 persistent per-simulator daemon-disable is available via third-party tooling. Gate on92 `command -v simslim` first — if it's absent, that's fine, stop at step 2, nothing else in93 this skill depends on it. To install without Homebrew: `go install94 github.com/mobai-app/simslim/cmd/simslim@latest` (a Go toolchain can be provisioned95 through `mise`, see `mise-tool-management`); or download the plain release tarball96 directly, `simslim-v0.8.0-macos-arm64.tar.gz` from97 `https://github.com/MobAI-App/simslim/releases/download/v0.8.0/` (asset name/version98 verified via `gh release view MobAI-App/simslim`, 2026-09-03 — simslim's own README99 documents only Homebrew and `go install`, `simslim/README.md:29-41`, so this direct-tarball100 path isn't in its docs either). Once present, it's one command per simulator: `simslim on101 <udid>` to disable, `simslim off <udid>` to revert.102 - Persistence only survives reboot on iOS 18.5+ runtimes; older runtimes are rejected103 before anything is touched (`simslim/README.md:303-307`).104 - Slimming drops Spotlight/in-Settings search, push notifications (`apsd`) and StoreKit105 testing (`storekitd`), and universal links (`swcd`) unless kept via `--except`/`--keep`106 (`simslim/README.md:342-347`).107 - `erase`, delete+recreate, and "Erase All Content and Settings" all revert to stock; the108 profile must be reapplied (`simslim/README.md:331-336`).109 - This skill doesn't track simslim's CLI beyond the two commands above — its own README110 is the source of truth for anything else.111112## Build + install the app under test113114- **Check the installed build version first**, in the app's own Settings/About screen if it115 has one. A stale install silently invalidates every finding in the session — confirm you're116 testing the build you think you are before reporting anything as a bug.117- Typical build: `xcodebuild -workspace <App>.xcworkspace -scheme <Scheme> -sdk iphonesimulator118 -configuration Debug -destination 'platform=iOS Simulator,name=<device>' -derivedDataPath119 build/sim build`, then `xcrun simctl install <udid> <App.app>`.120- **Build from a normal checkout, not an ephemeral agent worktree**, if your project keeps121 gitignored build secrets (API keys, provisioning config) outside version control. A worktree122 missing those files can make a Debug build crash at launch on a startup assertion — that's123 an environment artifact of the worktree, not a code bug; don't chase it as one.124125## The drive loop126127```128idb ui describe-all --udid <udid> # element frames + accessibility labels129idb ui tap --udid <udid> <x> <y> # tap at device-point coordinates130xcrun simctl io <udid> screenshot <path.png> # capture, then read the PNG and look at it131xcrun simctl io <udid> recordVideo <path.mp4> # capture a flow as video132```133134`recordVideo` fails outright if `<path.mp4>` already exists (`NSPOSIXErrorDomain` code 17,135"file exists") — pass `--force` to overwrite, or `rm -f <path.mp4>` first, especially when re-running136the same recording path across attempts.137138- **Get tap coordinates from `describe-all`**, not from eyeballing a screenshot — a139 screenshot is rendered at the device's pixel scale (commonly 3×), not point space. Tap the140 center of an element's reported `frame`.141- **Look at every screenshot.** The accessibility tree tells you *what* elements exist; only142 the rendered image shows clipping, overlap, empty space, unreadable glyphs, or wrong z-order.143- After each tap, `describe-all` again before the next action — a tap can miss, dismiss an144 unrelated system alert, or navigate further than expected, and you need to know where you145 actually landed.146147### When `describe-all` legitimately returns an empty tree148149`idb`'s accessibility dump is not 100% reliable — observed in practice (and tracked upstream,150e.g. facebook/idb#767) to come back empty or missing elements on some view hierarchies, with no151element frame to tap from. When that happens, don't treat "no pixel-coordinate tapping" as152absolute: fall back to **screenshot pixels ÷ device scale = points** (e.g. a 1206×2622 px153screenshot at a 3× scale device → tap at pixel ÷ 3, so 402×874 pt for that iPhone 17 Pro) and154**screenshot after every tap** to confirm it landed correctly — this fallback is only safe155because you're verifying each step, not because the math is guaranteed accurate.156157## Gotchas158159- **Shells that don't word-split an unquoted variable** (zsh, by default) will pass160 `"$xy"` as one argument and fail with `invalid int value` if you built a coordinate string161 like `xy="201 488"`. Pass literal integers, or force splitting (`${=xy}` in zsh).162- **`idb` must be on `PATH`** for any MCP or wrapper tool that shells out to it — without it,163 taps fail with `spawn idb ENOENT` even though a plain screenshot still works (screenshot can164 go through `simctl` alone; tapping cannot).165- **One booted simulator serializes all driving.** Don't run two agents or two audit166 sessions against the same simulator concurrently — their taps collide. Running several167 agents each against their *own* booted simulator is fine and is a fleet-sizing question,168 not a driving one — see the Preflight section above.169- **Stress layout deliberately**: `xcrun simctl ui <udid> content_size170 accessibility-extra-extra-extra-large` then relaunch to test Dynamic Type; reset with171 `content_size large`. `appearance dark|light` for color scheme. System alerts (permission172 prompts, sign-in sheets) persist across an app relaunch — dismiss them before reading the173 app underneath.174- **Reaching a hard-to-blind-tap end state** (a puzzle win, a multi-step checkout): if the app175 has a debug-only launch argument or hook that seeds a near-terminal state, use it rather than176 trying to solve the app's own logic via taps — that's testing your tapping, not the UX.177178## What to probe (this is what snapshots miss)179180- **Negative / offline paths**: airplane mode mid-flow, cloud account signed out, retry after181 a failed network call, a purchase-restore with nothing to restore. Core functionality should182 rarely hard-gate on an optional cloud/account state — verify it doesn't.183- **"Online but signed out" is not the same as "offline" — test both.** For any184 cloud-backed screen, these diverge: offline, network calls fail fast (they throw185 immediately, no connection to wait on); online-but-signed-out, the same calls can **hang**186 (a real network round-trip stalls waiting on an unauthenticated container that never187 resolves). A pass under airplane mode can mask a hang that only reproduces online.188- **Account-gated features need a real signed-in test account** in the simulator — a189 cloud-save resume affordance or a leaderboard, for instance, may by design show nothing when190 signed out, which is correct behavior, not a bug; don't flag graceful degradation as broken191 without first confirming the same flow works signed in.192- **Navigation / modals**: does the destination screen actually appear after a selection; does193 a close/leave action show its confirmation; back-stack behavior after several pushes.194- **Safe area / Dynamic Island**: overlay or completion content clipped or overlapping system195 chrome on a notch/island device — invisible to a fixed-frame snapshot.196- **End-to-end completion**: a full success and a full failure path, including any step that197 submits to an external service that might be unavailable.198199File each finding with its screenshot as evidence and a repro; label it environmental (stale200build, wrong account state) versus a genuine bug, and re-verify on a fresh, correctly-built201install before reporting it as real.202203## Rationale204205A snapshot test proves a view renders correctly *given* a state; it says nothing about206whether the app ever reaches that state through real interaction, or how it behaves at the207literal edges of a physical device (notch, Dynamic Island) that a fixed test harness frame208doesn't model. Driving the actual Simulator is the only check that covers the seam between209"the view is correct" and "the user can get there."210211## Deviation considerations212213- **No Simulator access (Linux CI, headless-only environment)**: this skill doesn't apply;214 rely on `host-driven-xcuitest-e2e` for automated coverage and snapshot tests for pixel215 regressions instead.216- **A pure macOS (AppKit/SwiftUI-Mac) app**: `idb` targets iOS/iPadOS/tvOS simulators only;217 drive a Mac app with `host-driven-xcuitest-e2e`'s window-frame-click pattern instead.218219## Common Mistakes2202211. **Tapping from screenshot pixel coordinates without first trying `describe-all`** — taps222 land at the wrong spot on any non-1× device unless converted (pixel ÷ scale); only fall back223 to pixel math when `describe-all` legitimately returns an empty tree (see above), and verify224 every such tap with a follow-up screenshot.2252. **Reporting a bug from a stale installed build** — always confirm the running version first.2263. **Treating "online + signed out" and "offline" as one case** — they exercise different code227 paths (fail-fast vs. hang) and must both be driven separately.2284. **Running two sim-driving sessions against one booted simulator** — taps interleave and229 corrupt both audits' results.2305. **Chasing a worktree-only launch crash as a code bug** when the project keeps build secrets231 outside version control — rule out the environment first.232233## Review Checklist234235- [ ] `idb list-targets` confirms the target simulator is booted before driving starts.236- [ ] Installed build version checked and matches the intended commit/build.237- [ ] Every tap coordinate came from a fresh `describe-all`, not a screenshot pixel estimate.238- [ ] Every screenshot was actually viewed, not just captured.239- [ ] Both offline and online-signed-out variants driven for any cloud-backed screen.240- [ ] Safe-area / Dynamic-Island framing checked on a notch/island-class device.241- [ ] Each reported finding has a screenshot + repro steps and is labeled bug vs. environmental.242243## Related skills244245- `host-driven-xcuitest-e2e` — turn a finding from this audit into an automated, CI-runnable regression test.246- `swift-testing-baseline` — the static snapshot-testing layer this skill complements, not replaces.247- `ios-accessibility-engineering` — Dynamic Type / VoiceOver checks that pair naturally with this audit loop.248- `mise-tool-management` — the general non-Homebrew tool-install pattern behind the `idb` and `simslim` install steps above.