Imported from EdgeApp/edge-dev-agents (
.cursor/skills/build-and-test/SKILL.md). Install upstream withnpx skills add EdgeApp/edge-dev-agents --skill build-and-test. Copyright stays with the author.
A real on-simulator UI test that logs into the pre-provisioned test account, navigates to the Buy tab, requests a $500 quote, and captures a proof screenshot. PASS requires the screenshot to actually render the resolved quote.
Parallel-session env contract: when the agent-watcher spawns this session as one of several parallel slots, it exports $AGENT_SIM_UDID (the slot's cloned simulator) and $AGENT_METRO_PORT (the slot's Metro port) into the shell. The scripts below honor them automatically — select-ios-sim.sh --accept-udid "$AGENT_SIM_UDID" skips name/runtime resolution and trusts the clone, and ios-rn-build.sh falls back to $AGENT_SIM_UDID / $AGENT_METRO_PORT when --udid / --port are not passed (forwarding a non-8081 port to react-native run-ios). On a manual run with neither var set, behavior is unchanged: resolve the iOS 18 sim by name and use Metro 8081.
0a. Prerequisites (check, install if missing)
xcrun -version→ Xcode CLTmaestro --version→ install withcurl -Ls "https://get.maestro.mobile.dev" | bash, then add$HOME/.maestro/binto PATH. maestro needs JDK 11+; Temurin 17 works.
0b. Resolve + boot the simulator
There can be multiple "iPhone 16 Pro Max" devices across runtimes. Only the iOS 18 device holds the test accounts (the funded-test-accounts roster; default login: the primary account). The iOS 26.x device does NOT.
UDID=$(~/.cursor/skills/build-and-test/scripts/select-ios-sim.sh \
--runtime "iOS 18" --device "iPhone 16 Pro Max" --boot)
If the script exits 2 (ambiguous), narrow --runtime (e.g. "iOS 18.6").
0c. Build + install + launch the app
~/.cursor/skills/build-and-test/scripts/ios-rn-build.sh \
--udid "$UDID" --bundle-id co.edgesecure.app --detach
~/.cursor/skills/build-and-test/scripts/ios-rn-build-wait.sh --udid "$UDID" # re-run while it exits 7
Skips the full RN build when the app is already installed (cached path: seconds; a fresh build is usually just a few minutes — the Hermes prebuilt is prefetched). Pass --force-rebuild to always rebuild.
0d. Run the maestro capture
~/.cursor/skills/build-and-test/scripts/capture-buy-quote.sh
Drives maestro/buy-quote-input.yaml (login → Buy → $500), then captures via an external simctl screenshot burst — keeping the last frame taken while the app was alive. Retries up to 5 cycles. Writes /tmp/agent-mvp-buy-quote-screenshot.png on success.
0e. PASS / FAIL contract
On capture-buy-quote.sh exit 0, the screenshot must visibly show USD 500, a non-empty Amount BTC, and the 1 BTC = <rate> USD line. Emit:
build-and-test: PASS (iOS maestro — Buy $500 quote)
screenshot: /tmp/agent-mvp-buy-quote-screenshot.png
On exit nonzero, emit FAIL with the last 30 lines of the script's output:
build-and-test: FAIL — Buy $500 quote not captured
<last 30 lines>
Return success exit only on PASS.
0f. Critical gotchas baked into the flow (do not "fix" them)
Edge's RN keypad drops digits tapped too fast → wrong PIN → exponential lockout (465s → 914s → …). Each PIN digit tap in buy-quote-input.yaml uses waitToSettleTimeoutMs. Never speed it up. If a run logs "Invalid PIN: Account locked for N seconds", wait — do NOT tap.
On this debug build, hideKeyboard reliably triggers an RN Fabric text-measure SIGABRT. The flow leaves the keyboard up. Do not add hideKeyboard steps.
assertVisible/extendedWaitUntil traverse the a11y hierarchy on a poll loop, provoking the same Fabric crash on the Buy scene. The flow stops polling once the amount is entered; the capture script uses external simctl screenshots (no hierarchy traversal).
[ -d node_modules ] || ~/.cursor/skills/pm.sh install
npx tsc --noEmit
Emit PASS:
build-and-test: PASS (tsc --noEmit clean)
Or FAIL with the last 30 lines of failing output:
build-and-test: FAIL — <command> exit <code>
<last 30 lines>
Same PASS/FAIL contract as step 1.