E2e route
Pure dispatcher: picks the right E2E executor for a Swift test request from context (platform × intent × verification kind) and hands off. Routes to the scaffold/MCP-sim/QA/design-review skills.From its SKILL.md
npx -y skills add Paretofilm/superpowers-gstack --skill e2e-routeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.8 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
e2e-route
A pure dispatcher for Swift E2E testing. Given a test request it reads context
deterministically, chooses the right E2E executor, and dispatches — it does not own
execution. It is the thin routing layer above the scaffold skills and the MCP-live
tools, analogous to how pitfall-verification orchestrates lenses without
reimplementing them.
Invoke with: /superpowers-gstack:e2e-route
Phase 0 — Self-check
| Check | Detect via | Refuse-message |
|---|---|---|
| Swift project | *.xcodeproj or Package.swift in cwd | "Not a Swift project. /e2e-route needs a Swift app to route tests for." |
| Detectable target | a scheme/target is discoverable (see Platform below) | If none: ask the user once which scheme to target; else refuse "No scheme/target detected — cannot route." |
Routing inputs (read in order)
1. Intent — exploratory (MCP-live) vs committed (XCUITest)
- CI forces committed. Detect CI by running Bash:
If set, force committed — a CI context cannot drive a live MCP simulator session.[ -n "${CI:-}${GITHUB_ACTIONS:-}" ] && echo CI - Else infer from the user's verbs:
utforsk/sjekk/dogfood/trykk gjennom/explore/smoke/ "press through the flow" → MCP-livemå aldri knekke/regresjon/regression/CI/ "lock this down" → committed
- Ambiguous (no verb signal, not in CI) → ask the user once: "Exploratory live run, or committed regression test?"
2. Platform — iOS vs macOS
Detect via mcp__XcodeBuildMCP__show_build_settings / list_schemes (read
SDKROOT / SUPPORTED_PLATFORMS); if MCP unavailable, fall back to
grep -E 'SDKROOT|SUPPORTED_PLATFORMS' *.xcodeproj/project.pbxproj or read
.gstack/track. Ask only if undetectable.
Multiplatform tiebreak — REQUIRED (the user's premise is "both platforms"). If the
detected target supports BOTH iOS and macOS (SUPPORTED_PLATFORMS lists both
iphoneos and macosx, or .gstack/track = both, or two schemes one per
platform), the platform is NOT uniquely determined. Resolve in order:
- (a) If the test request names a platform ("test the iPhone flow", "the macOS menu bar") → use it.
- (b) Else ask the user once: "This app targets both iOS and macOS — route this test to iOS, macOS, or both?"
- (c)
both→ emit two decision blocks (one iOS, one macOS), each routing to its platform's executor. Run them SEQUENTIALLY. The scaffolds handle coexistence via their shared TARGET_DIR convention: on a multiplatform target each uses a platform-suffixed test directory —<App>iOSUITests/for /ios-e2e-scaffold,<App>macOSUITests/for /macos-e2e-scaffold — and each scaffold's "already scaffolded" Phase-0 check globs*UITests/name-agnostically but EXCLUDES the sibling platform's suffixed directory (*macOSUITests/ignored by ios,*iOSUITests/ignored by macos). So whichever runs second is not blocked by the first's files, and the two never collide on the same directory or the samexcodegen.yml/project.pbxprojUI-test target. Each decision block's "Next action" must name its platform-specific target dir.
3. Verification kind (optional refinement)
Functional / accessibility-assertion vs visual. A request about layout, spacing, colour, dark mode, or "does it look right" → the visual-regression row.
Routing table (the oracle)
| Intent | Platform | Executor |
|---|---|---|
| Committed regression | macOS | /macos-e2e-scaffold + its xcresult runner |
| Committed regression | iOS | /ios-e2e-scaffold |
| Exploratory / live | macOS | XcodeBuildMCP UI-automation (snapshot_ui → tap → screenshot) |
| Exploratory / live | iOS | ios-simulator MCP (ui_find_element / ui_tap) or /ios-qa |
| Visual regression | iOS | screenshot/vision diff + /ios-design-review |
| Visual regression | macOS | screenshot/vision diff + /design-review (generic designer's-eye QA — no macOS-specific reviewer exists) |
| Visual exploration (Tier-2 escalation) | iOS/iPadOS | /superpowers-gstack:ios-visual-explore — Gemini computer-use drives the app visually. Route here when the accessibility tree is insufficient (visual landmarks, layout regressions XCUITest can't assert, open-ended "find visual issues" missions). Not a first resort; paid Gemini API per run. |
Fallback
Degrade to the MCP-live row for the platform only when the chosen scaffold's Phase 0 actually refuses — i.e. one of the scaffold's three refuse-conditions fires:
- not a Swift project, or
- no SwiftUI app for the routed platform detected — no SwiftUI scene (e.g. a UIKit-/AppKit-only app) or no platform-discriminating signal (e.g. a pure-iOS app routed to /macos-e2e-scaffold, or vice versa), or
- a UI-test target already exists.
Emit an explicit note naming the unmet precondition. No false promise; always a way forward.
SPM-only is NOT a fallback trigger. The scaffold skills accept Package.swift
projects and proceed — they generate files under Tests/<TARGET_DIR>/ (<App>UITests,
platform-suffixed on multiplatform targets) with a "SwiftPM can't host a UI-test
bundle; add an .xcodeproj" warning. So for SPM-only iOS/macOS apps
the dispatcher still routes to the scaffold skill; it does NOT degrade to MCP-live.
Output — routing-decision block
Emit one block per resolved platform — normally exactly one; two when the
multiplatform tiebreak resolved to both (one iOS block + one macOS block) — then
stop. Do not build/tap/assert; hand control back after emitting.
## /e2e-route decision
Detected: platform=<iOS|macOS>, intent=<committed|exploratory|visual>, source=<scheme|.gstack/track|asked>
Chosen executor: <skill or MCP sequence>
Why: <one line tying context → routing cell>
Next action: <exact /skill to invoke OR exact MCP call sequence>
What this skill is NOT
- Not an executor. It does not build, tap, assert, or run tests itself — it names the executor and the exact next action, then hands off.
- Not a scaffolder. It does not modify app files or generate test stubs — the
scaffold skills (
/ios-e2e-scaffold,/macos-e2e-scaffold) do that. - Not a QA-report writer. Use
/ios-qa//qafor live QA reports. - Not auto-hooked. Manual
/e2e-route+ CLAUDE.md routing only — no PostToolUse/UserPromptSubmit hook (avoids hijacking existing/qa//ios-qarouting).
Shared foundation
Both executors locate controls via the accessibility tree the same way, using the
<ViewName>_<ControlType>_<Purpose> identifier convention (snake_case) owned by the
scaffold skills — e2e-route points at it but does not own or apply identifiers.
This is why the routing layer stays thin: element-lookup is identical whether the
executor is ui_find_element("PlanListView_Button_GeneratePlan") (MCP-live) or
app.buttons["PlanListView_Button_GeneratePlan"].tap() (XCUITest).
Simulator readiness ladder (MCP-live only)
When the routed executor is MCP-live (ios-simulator / XcodeBuildMCP / raw
idb+simctl), readiness is not a single boolean. "Booted" exists at three levels that
lag each other unpredictably — sometimes by minutes on a cold iOS 26 boot:
| Level | Signal | Reality |
|---|---|---|
| 1 | xcrun simctl list devices booted shows Booted | CoreSimulator state — earliest, a diagnostic not a gate |
| 2 | xcrun simctl bootstatus <udid> -b returns | launchd/system services — neither sufficient nor necessary for automation; a diagnostic not a gate |
| 3 | describe-all / snapshot_ui returns a non-degenerate tree | SpringBoard rendered, AX bridge live — the only readiness gate |
These are not a clean monotonic ladder. In the 2026-06-27 incident bootstatus -b
(level 2) was still blocking after the UI (level 3) was already up. Treat level 3 as the
only readiness gate; levels 1–2 are diagnostics you read, never conditions you wait on.
Rules — these prevent stranded passive waits:
- Poll the precondition you actually need (level 3), not a proxy. A device that
reports
Bootedcan still show a black screen with an empty AX tree. Gate the first probe on a non-degenerate tree, not onBooted. - Never block on
bootstatus -bas your wakeup signal. It can block far longer than the device takes to become usable — or indefinitely, if its boot-completion condition is never met — so it always needs an external timeout. Instead run a boundedrun_in_backgrounduntil-loop that exits the moment the level-3 precondition is true (one notification, within seconds):# Ready ≠ "Booted". A cold-boot AX tree can contain elements whose frames are # all null/0 (degenerate) — so gate on REAL geometry, not just on nodes existing. # REPLACE the ready() body with a check against YOUR tool's actual output (an # element with a NON-ZERO frame width — a label alone does not prove geometry). # Confirm field names once against a real dump. Note: idb roles are bare ("Button", # "Application"), NOT AX-prefixed — do not grep for "AXButton". Until you replace it, # ready() fails loudly, so the loop can never falsely report success. ready() { echo "readiness predicate not implemented — replace ready()" >&2 return 1 } start=$SECONDS until ready; do if [ $((SECONDS - start)) -gt 120 ]; then echo "TIMEOUT: AX tree still degenerate" >&2 exit 1 # fail loudly — a wrapping background task must NOT notify "ready" fi sleep 1 done - Always pair "I'll be notified when the task finishes" with a fallback wakeup. The
harness re-invokes on task completion; a hung task strands you forever. Set a
ScheduleWakeupfallback (or a boundedMonitortimeout) so a hang can't cost 10 idle minutes. If your only plan is "I'll be notified automatically," you have no plan for the notification not arriving. - Never emit a bare "waiting." Either you are actively polling (a background until-loop that exits) or you hand control back. Idle-waiting on an opaque blocking command is the anti-pattern that wasted real time on a Fase-2 iPad spike (2026-06-27).
Relationship to other skills
| Skill | Layer | Asks |
|---|---|---|
e2e-route | routing | Which executor for this test? |
ios-e2e-scaffold / macos-e2e-scaffold | project | Is this E2E-tested? |
ios-qa | live | Does the running app behave? |
ios-design-review | visual | Does it look right on device? |
pitfall-verification | artifact | Will this work? |
e2e-route sits above the project-layer scaffold skills and the MCP-live tools. It
decides the executor; the executor decides how.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.