npx skillsauth add laststance/skills qa-iosInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
When running this skill in Codex, translate Claude Code-only primitives before acting: AskUserQuestion -> chat/request_user_input, TodoWrite -> update_plan, Task/TaskCreate/TeamCreate/SendMessage -> spawn_agent/send_input/wait_agent when available and allowed, and EnterPlanMode/ExitPlanMode -> a concise chat plan plus explicit approval.
Resolve Read/Write/Edit/Bash/WebSearch/WebFetch to Codex file/shell/web tools, and map ~/.claude/... paths to ~/.agents/... or ~/.codex/... unless the task explicitly targets Claude Code.
When running this skill in Cursor Agent, translate Claude Code-only primitives before acting: AskUserQuestion -> AskQuestion; TodoWrite -> Cursor TodoWrite or an equivalent checklist; Task/TaskCreate/TeamCreate/SendMessage/multi-agent flows -> Cursor Task (subagents), parallel Tasks, or run_in_background when allowed (TeamCreate/SendMessage may have no exact match); EnterPlanMode/ExitPlanMode -> Plan mode (SwitchMode / CreatePlan) plus explicit user approval.
Resolve Read/Write/Edit/StrReplace/Bash/web/search/MCP via Cursor Composer or Agent equivalents. MCP names written as mcp__server__tool typically map to call_mcp_tool with configured server identifiers. Map ~/.claude/... to ~/.cursor/skills/, .cursor/skills/, and .cursor/rules/ unless the task explicitly targets Claude Code.
Drive an iOS app running in the Simulator, collect evidence (screenshots + AX trees + logs), grade issues, and produce a report. Fixing is out of scope — this skill reports. If the user wants to fix bugs, hand the report to a session that has access to the Swift source.
Web QA tools (Playwright, headless Chrome) do not reach iOS. Native apps behave differently:
log stream or crash
reports in ~/Library/Logs/DiagnosticReports/This skill codifies the minimum systematic path that catches the most common classes of iOS bugs without Xcode instrumentation.
In scope: Simulator-only black-box testing — driving the app via accessibility actions, capturing screenshots, parsing the AX tree, reading crash + log output, comparing against HIG rules.
Out of scope:
/exhaustive-qa as the model
if you need a fix loop on the Swift side)If any of these are unknown, use AskUserQuestion to collect them:
com.example.myapp. Without this, launch_app fails.mcp__ios-simulator__get_booted_sim_id. If nothing is booted, ask which
device + iOS version to boot, then xcrun simctl boot <udid> + open -a Simulator.mcp__ios-simulator__install_app.Do not guess any of these. Missing context causes the skill to test the wrong app or skip real areas.
Goal: confirm a clean starting state, know which simulator we're on, capture a "before anything happened" screenshot for regression comparison later.
mcp__ios-simulator__get_booted_sim_id → note the UDID. All subsequent
calls should target this UDID (either pass udid explicitly or rely on
IDB_UDID env).xcrun simctl list devices booted -j | head -50
Record: device name, iOS version, state. These go in the report.mkdir -p /tmp/qa-ios-session
xcrun simctl spawn booted log stream \
--predicate 'subsystem == "<bundle-id>" OR eventMessage CONTAINS "error"' \
--level debug > /tmp/qa-ios-session/log.txt 2>&1 &
echo $! > /tmp/qa-ios-session/log.pid
Replace <bundle-id> with the target's bundle identifier. Kill this
process at the end of the session.mcp__ios-simulator__launch_app with terminate_running: true — this
forces a clean launch from cold state, which is the state users actually
encounter.mcp__ios-simulator__screenshot →
/tmp/qa-ios-session/00-baseline.png.mcp__ios-simulator__ui_describe_all → save to
/tmp/qa-ios-session/00-ax-tree.json. This is the first datum for tap
target audits.If the launch fails, STOP and report to the user. Common causes: wrong
bundle ID, app not installed, simulator not actually booted (check with
xcrun simctl list devices booted).
Goal: build a mental map of what screens exist before diving deep.
Walk the app's primary navigation breadth-first. For a typical app this means: tab bar (if any) → each tab's root → first-level detail screens. Do not drill into modals, forms, or edge cases yet. At each stop:
NN-<screen-slug>.pngNN-<screen-slug>-ax.jsonThe goal is a checklist of screens to deep-test in Phase 2. Budget: roughly 2–5 minutes of real time. If the app has >15 top-level screens, ask the user which ones matter most for this run.
How to navigate without guessing coordinates: call
ui_describe_all → scan for a node with the expected label/role (e.g.
{ "AXLabel": "Settings", "AXRole": "AXButton" }) → extract its frame
(an {x, y, width, height} rect) → tap its center with ui_tap({ x: x + width/2, y: y + height/2 }). AX-driven tapping is far more reliable than
pixel-guessing from the screenshot.
For every screen from Phase 1, work the checklist below. Most bugs fall out of this loop — the point is to be systematic, not clever.
references/hig-compliance.md
(safe area, readable contrast, layout breaks)frame.width < 44 or frame.height < 44
AND whose role is tappable (AXButton, AXLink,
AXSwitch) — flag as HIG tap target violationAXValue contains obvious placeholder text ("Lorem
ipsum", "TODO", "Untitled")AXStaticText node whose bounding box is
smaller than the text length could render → likely truncatedEnumerate every tappable node on the screen from the AX dump. Tap each in turn. After each tap:
Cancel button) and continueEvery AXTextField / AXTextView:
ui_type("…") with (a) empty submit, (b) valid input, (c) invalid input
for the semantic type (e.g., non-email into an email field, 0 into a
positive-integer field, 1000-char string into a short field)Force the non-default conditions the OS controls:
| Condition | How to force | What to check |
|-----------|--------------|---------------|
| Dark Mode | xcrun simctl ui booted appearance dark | Color inversions, missing dark assets, invisible text |
| Light Mode | xcrun simctl ui booted appearance light | (restore) |
| Dynamic Type — largest | xcrun simctl ui booted content_size extra-extra-extra-large | Text overflow, clipped labels, broken layout |
| Landscape | Simulator → Device → Rotate Left (Cmd+←) | Layout breaks, safe-area misuse |
| Network off | Simulator → Features → Toggle Network Link Conditioner (or xcrun simctl status_bar booted override --dataNetwork none) | Offline states, error messages |
| Low Power Mode | xcrun simctl status_bar booted override --batteryState charged --batteryLevel 20 + user flips Low Power in Settings | Animations reducing, background tasks behaving |
At minimum: Dark Mode + Dynamic Type XXL + Landscape. These three catch the vast majority of state bugs.
AXLabel. Flag
anything tappable with only a AXRole and no label.AXLabel and no sibling label → probably missing
accessibilityLabelAXHeader presence on screens that have visible
heading text but no marked headerSee references/issue-taxonomy-ios.md for the full category list.
Take every issue collected across phases and classify:
references/issue-taxonomy-ios.md)hig / functional / visual / accessibility /
state / content / crashDe-duplicate aggressively. If five screens have the same tap-target violation on the same reused button component, that's one issue with a list of affected screens — not five.
Use templates/qa-report-template-ios.md as the skeleton. Fill in:
xcrun simctl appinfo booted <bundle-id>)Save the report to ./qa-reports/ios-<date>-<device-short>.md relative to
the user's current directory. Save all screenshots and AX dumps to
./qa-reports/ios-<date>-<device-short>/.
Don't embed the full AX JSON in the report — they're 5k-50k tokens each. Save them alongside and link. Only embed the specific snippet relevant to an issue.
Always, even if the report isn't finished:
# Stop log stream
[ -f /tmp/qa-ios-session/log.pid ] && kill "$(cat /tmp/qa-ios-session/log.pid)" 2>/dev/null
# Clear status bar overrides so the sim returns to normal
xcrun simctl status_bar booted clear
# Restore appearance if you changed it
xcrun simctl ui booted appearance light
# Restore content size
xcrun simctl ui booted content_size medium
Leaving the simulator in a non-default state creates confusing "why is my status bar frozen at 9:41?" moments for the user later.
Before telling the user "done":
qa-reports/ios-<date>-<device>.md exists and opens cleanly{SCORE} placeholders left)references/issue-taxonomy-ios.md — severity + category definitionsreferences/ios-mcp-reference.md — MCP tool + xcrun simctl cheat sheetreferences/hig-compliance.md — the specific HIG rules this skill checkstemplates/qa-report-template-ios.md — fill-in report skeletonLoad the references on demand (they're not in context by default). When
you hit a category question during triage, read the taxonomy. When you
need a simctl recipe you don't remember, read the MCP reference.
xcrun simctl shutdown booted then
xcrun simctl boot <udid> + open -a Simulator. Re-launch the app.ls -lt ~/Library/Logs/DiagnosticReports/ | head -5 — the most recent
.ips is your crash. Don't try to decode it; quote the first 20 lines
and the crashed thread's stack into the report.xcrun simctl listapps booted — grep for the
display name. If that fails, the app isn't installed on this sim.tools
Inspect video frame-by-frame and capture-then-verify UI motion. Extract frames from any clip (handed to you, screen-recorded, or self-captured) with ffmpeg and read them as images; record an interaction (Playwright / computer-use / iOS simulator) and verify animations, transitions, and motion that static screenshots and getComputedStyle cannot reveal. Use when verifying animations/transitions/motion, analyzing a video or .webm/.mp4, extracting frames, checking how something "looks" in motion, or recording a UI flow to inspect.
testing
Cited research briefs
development
Daily coding habit prompts JP
development
React core deep-dive JP