agentsclimarketplace

Macos e2e scaffold

Skill Paretofilm/superpowers-gstack/skills/macos-e2e-scaffold

How to use Superpowers and GStack together with Claude Code. Workflow manual, routing plugin, and CLAUDE.md generator. Use both frameworks — each in the phase where it excels.

Install
npx -y skills add Paretofilm/superpowers-gstack --skill macos-e2e-scaffold

Assembled 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.

What its author says it does

Copied from the file, not written here

One-shot XCUITest scaffolding for macOS SwiftUI apps: audits the project, generates ranked TIER-1/2/3 stubs, suggests accessibility identifiers, emits an xcresult runner. Manual only — modifies project files.

SKILL.md

21.4 KB, as published. Nobody here has run it

macos-e2e-scaffold

Manual-invocation skill that bootstraps XCUITest infrastructure for macOS SwiftUI projects.

Phase 0 — Self-check

Before any other action, run three refuse-conditions. Any failure → return early with explicit message; no files modified.

CheckDetect viaRefuse-message
Swift project*.xcodeproj directory or Package.swift in cwd"Not a Swift project. /macos-e2e-scaffold requires .xcodeproj or Package.swift in project root."
SwiftUI macOS appmacOS-discriminating signal (see below) AND SwiftUI scene (grep WindowGroup|Window(|Settings {|MenuBarExtra( in *.swift under source root)"No SwiftUI macOS app target detected. Skill is macOS-only — for iOS use /ios-e2e-scaffold, for AppKit use /appkit-e2e-scaffold (deferred)."
Not already scaffoldedany directory matching *UITests/ at cwd — EXCLUDING the iOS sibling's *iOSUITests/ (which must not block macOS scaffolding on multiplatform projects) — contains > 1 *.swift (find . -maxdepth 2 -type d -name '*UITests' ! -name '*iOSUITests' then count) — do NOT assume the <App>-prefixed name, since the scheme is not detected until Step 2"UI test target already exists (<found-dir>/, N test files). Skill won't overwrite — extend manually instead."

macOS-discriminating signal — REQUIRED

WindowGroup is cross-platform (shared with iOS) and does NOT identify macOS on its own. Detect macOS via the FIRST that matches:

  1. SPM: Package.swift contains .macOS( in a platforms: clause (and the target is an app, not a library).
  2. xcodegen: project.yml/xcodegen.yml target has platform: macOS.
  3. plain .xcodeproj: project.pbxproj contains SDKROOT = macosx or SUPPORTED_PLATFORMS includes macosx for the app target's build config.
  4. Corroborating (necessary-not-sufficient): a macOS-only scene type present (MenuBarExtra(, Settings {, Window() AND iOS-only signals absent (SDKROOT = iphoneos, platform: iOS, import UIKit, .fullScreenCover().

A project with ONLY iOS signals must be refused with the row's refuse-message pointing to /ios-e2e-scaffold — do NOT scaffold macOS tests from a bare WindowGroup.

Multiplatform targets (.iOS( AND .macOS( in Package.swift, or SUPPORTED_PLATFORMS lists both iphoneos and macosx): Phase 0 passes (macOS is among the platforms) and emits a note — "Multiplatform target detected; scaffolding macOS tests. Run /ios-e2e-scaffold separately for the iOS surface." Treat macosx/.macOS( presence as sufficient; do not refuse just because iOS is also supported.

TARGET_DIR convention

Set once in Phase 0 and used everywhere below (Step 10 file naming, the xcodegen/Xcode UI-test target name, and the runner's -only-testing: argument):

  • Single-platform macOS target → TARGET_DIR = <App>UITests
  • Multiplatform target → TARGET_DIR = <App>macOSUITests — so the macOS and iOS UI-test targets coexist. /ios-e2e-scaffold uses <App>iOSUITests in the same situation, and each scaffold's "already scaffolded" glob ignores the sibling's suffixed directory; without the suffix the two would collide on the same directory and the same xcodegen.yml/project.pbxproj UI-test target.

Always emit Phase 0 result on success:

## /macos-e2e-scaffold Phase 0
✅ Swift project detected (<project>.xcodeproj | Package.swift)
✅ SwiftUI macOS app (macOS signal: <SDKROOT=macosx | .macOS( | platform: macOS | macOS-only scene>; scene <Type> in <File.swift>:<line>)
✅ No existing UI test target

Project type: <xcodegen-managed | SPM-based | plain .xcodeproj>
Scheme: <SchemeName>
Source root: <path>
Total .swift files in source root: <N>
[Multiplatform note, if applicable — TARGET_DIR = <App>macOSUITests]

Proceeding with audit + scaffold.

What this skill does

  1. Audits the project: walks the SwiftUI Scene tree, ranks views by interactive-control density, identifies top 5.
  2. Suggests accessibility identifiers for each control in top 5 views; applies them after user batch-confirmation.
  3. Generates ranked TIER-1/2/3 test stubs with XCTFail("not implemented") placeholders, an identifier-convention doc, and a Claude-readable xcresult runner script.

What this skill is NOT

  • Not a review skill. Does not analyse spec/plan/PRD artefacts. Use /pitfall-verification (will-it-work?), /quality-review (will-it-feel-premium?), or /macos-native-review (is-it-Apple-native?) for those.
  • Not a code-quality reviewer. Does not check view-code idioms (use Antoine van der Lee's swiftui-expert-skill) or unit-test idioms (use swift-testing-expert).
  • Not iOS-aware. Use /ios-e2e-scaffold.
  • Not AppKit-aware. Use /appkit-e2e-scaffold (deferred).
  • Not snapshot-aware. Use /swiftui-snapshot-scaffold (deferred).
  • Not auto-invoked. Manual /macos-e2e-scaffold only — same model as setup-routing. Normally reached via /e2e-route.

Heuristic process (deterministic, Read+Grep based)

Step 1: Detect project type

  1. If xcodegen.yml or project.yml exists → xcodegen-managed
  2. Else if Package.swift contains .executableTarget(name:SPM-based
  3. Else if *.xcodeproj exists → plain .xcodeproj

Step 2: Detect scheme name

  • xcodegen: read name: field at root of xcodegen.yml/project.yml
  • SPM: read name: from Package(name: ...)
  • plain .xcodeproj: parse *.xcodeproj/xcshareddata/xcschemes/*.xcscheme filenames; fallback to project directory name

Step 3: Find source root

  • xcodegen: read targets.<schemename>.sources.path from yml
  • SPM: Sources/<TargetName>/
  • plain .xcodeproj: parse project.pbxproj for main app target's source group path = ; fallback to <schemename>/

Step 4: Walk Scene tree

  1. Grep source root: grep -rn -E 'WindowGroup|Window\(|Settings \{|MenuBarExtra\(' --include='*.swift'
  2. For each Scene file, grep its body for NavigationLink(destination:, .sheet(content:, .fullScreenCover(content:
  3. Recursively follow destinations to build view-graph (max depth: 5; cycle detection via view-name set)
  4. For each view in graph, count interactive controls using word-boundary patterns:
    • \bButton\b\s*[({]
    • \bToggle\b\s*\(
    • \bTextField\b\s*\(
    • \bPicker\b\s*\(
    • \bNavigationLink\b\s*\(

Step 5: Rank views

Sort views by (reference_count + interactive_control_count) descending. Tie-breaker: alphabetical by source-file name, then by line number. Top 5 receive identifier suggestions.

Step 6: Detect TIER mappings

  • TIER-1 #1 (Smoke): always (uses Scene-root window-title)
  • TIER-1 #2 (Happy-path): pick top-ranked view's primary button. Heuristic: Button containing await OR action calling a method named generate*/create*/save*/run*/start*. Fallback if no Button matches: pick the first Button in the top-ranked view (by line number); mark the generated stub with comment // HEURISTIC: generic fallback — no save/create/await action matched. Verify this is the right primary action. so user knows to double-check.
  • TIER-1 #3 (Error-recovery): pick first view (alphabetical by source-file name, then by line number — deterministic) containing .alert(...), errorMessage, failure, or error: Error
  • TIER-2 (Modal): only if .sheet(isPresented: or .fullScreenCover(isPresented: found in walked tree
  • TIER-2 (Menubar): only if .commands { ... } or MenuBarExtra( found
  • TIER-3 (Multi-window): only if WindowGroup-count + Window(-count > 1
  • TIER-3 (Toolbar): only if ToolbarItem( count ≥ 2

Step 7: Generate identifier suggestions

For each control in top 5 views:

  • Skip controls that already have .accessibilityIdentifier(...) set — check next 5 lines after the control declaration. Already-identified controls listed in report under "Already identified (preserved)" but not re-suggested.
  • Skip controls inside #Preview { ... } blocks or PreviewProvider (static var previews:) conformances — track brace-depth from #Preview or static var previews declarations; exclude when depth > 0.
  • Construct ID as <ViewName>_<ControlType>_<Purpose>
  • Purpose extracted from button label, action method name, or property name (in priority order)
  • snake_case all parts; _ separator
  • Examples: PlanCardView_Button_GeneratePlan, SettingsView_Toggle_EnableTelemetry, AIChatView_TextField_PromptInput

Step 8: Emit suggestions table for user confirmation

Present batch table in markdown:

| File:line | Current code | Suggested identifier |
|---|---|---|
| PlanCardView.swift:34 | Button("Generate") { ... } | PlanCardView_Button_GeneratePlan |
| ... | ... | ... |

Ask: "Apply all N suggestions? [a]ll / [c]herry-pick / [s]kip"

If [c]herry-pick: follow up with one question per suggestion: "Apply suggestion k of N? [y/n]". Accumulate accepted set; apply only that subset in Step 9.

If [s]kip: skip Step 9 entirely; test files in Step 10 still generated, with placeholder identifier comments showing what user must fill in manually.

Step 9: Apply identifiers

Use Edit tool, one identifier per Edit call. On uniqueness conflict (same ID would land on two distinct controls): skip both; flag for manual review in report.

Step 10: Generate test files

Per TIER, write one .swift file with XCTFail("not implemented — fyll inn assertion") placeholder + TODO-comment pointing to source-file:line + suggested assertions in comments.

File naming (all paths use the Phase 0 TARGET_DIR<App>UITests single-platform, <App>macOSUITests multiplatform):

  • <TARGET_DIR>/SmokeTest.swift (TIER-1 #1)
  • <TARGET_DIR>/HappyPathTests.swift (TIER-1 #2)
  • <TARGET_DIR>/ErrorRecoveryTests.swift (TIER-1 #3)
  • <TARGET_DIR>/ModalAndMenuTests.swift (TIER-2 if any)
  • <TARGET_DIR>/MultiWindowAndToolbarTests.swift (TIER-3 if any)

Step 11: Generate runner script

Write scripts/run-uitests.sh per template in §Runner-script-template (substitute <APP> with detected scheme name and <TARGET_DIR> with the Phase 0 TARGET_DIR). Make executable: chmod +x scripts/run-uitests.sh.

Step 12: Generate identifier convention doc

Write docs/accessibility-identifiers.md with the convention, examples, rationale, and a table listing all applied identifiers with their source-file:line.

Step 13: Emit final report

Per Output-format section below.

TIER rubric

TierAlways-generate?Heuristic triggerTest file
1 #1 SmokeyesalwaysSmokeTest.swift
1 #2 Happy-pathyestop-ranked view + primary actionHappyPathTests.swift
1 #3 Error-recoveryyesfirst .alert/error-state view (alphabetical+line tiebreak)ErrorRecoveryTests.swift
2 Modalconditional.sheet(isPresented: or .fullScreenCover(isPresented: presentModalAndMenuTests.swift
2 Menubarconditional.commands { ... } or MenuBarExtra( presentModalAndMenuTests.swift
3 Multi-windowconditionalScene-count > 1MultiWindowAndToolbarTests.swift
3 ToolbarconditionalToolbarItem(-count ≥ 2MultiWindowAndToolbarTests.swift

TIER-1 is non-negotiable. Even on a project where heuristics return weak matches, three test files appear. Smoke validates app launches. Happy-path and Error-recovery may need user-tuning but provide a starting structure.

TIER-2 and TIER-3 conditional. Skipped silently if pattern not detected. Report says e.g. "TIER-2 sheet/modal: not generated (no .sheet(isPresented:) found)".

Identifier convention

Format: <ViewName>_<ControlType>_<Purpose>

  • snake_case all parts
  • _ separator (consistent grep-by-view: PlanCardView_ matches all PlanCardView-controls)
  • Stable across label-text changes (refactor-safe)

Examples:

  • PlanCardView_Button_GeneratePlan
  • SettingsView_Toggle_EnableTelemetry
  • AIChatView_TextField_PromptInput
  • ToolbarView_NavigationLink_OpenSettings

Convention doc: docs/accessibility-identifiers.md (auto-generated by skill, includes table of all applied identifiers + rationale).

Output format

After Phase 0 emission and identifier-application, the final report (single message at end of skill execution):

## /macos-e2e-scaffold report — <ProjectName>

### Phase 0
✅ Swift project (<project-type>)
✅ SwiftUI macOS app (<N> Scenes detected, macOS signal: <signal>)
✅ No existing UI test target

### Project context
- Scheme: <SchemeName>
- Source root: <path>
- Top 5 views by control density: <View1>, <View2>, <View3>, <View4>, <View5>

### Identifier suggestions (<N> total)
✅ Applied (<N>): <Id1>, <Id2>, ...
⏭️  Skipped on uniqueness conflict (<M>): <Id-X> (collides with <Id-Y> — review manually)
ℹ️  Already identified (preserved <K>): <Id-existing-1>, ...
🚫 Excluded from Preview blocks (<L>)

### Test stubs generated (<S>)
**TIER-1 (3 — must implement)**
- `SmokeTest.swift` :: testTIER1_AppLaunches
- `HappyPathTests.swift` :: testTIER1_<HappyPathName>
- `ErrorRecoveryTests.swift` :: testTIER1_<ErrorPathName>

**TIER-2 (<n> — should implement if applicable)**
- `ModalAndMenuTests.swift` :: testTIER2_<...> (if pattern detected)

**TIER-3 (<n> — patterns not detected | generated)**
- Multi-window: <generated | not generated (single WindowGroup)>
- Toolbar: <generated | not generated (only N ToolbarItem)>

### Runner script
- `scripts/run-uitests.sh` — `xcodebuild test -only-testing:<TARGET_DIR>`, parses xcresult to Claude-readable JSON (Xcode 16+ format; falls back to plaintext on older Xcode)

### Convention doc
- `docs/accessibility-identifiers.md` — `<ViewName>_<ControlType>_<Purpose>`, snake_case, examples, full identifier table

### Project-type integration
<branch-specific instructions per Project-type-specific-behavior section>

### Next steps
1. <project-type-specific build step>
2. `./scripts/run-uitests.sh` — all <S> TIER stubs will fail with `XCTFail("not implemented")`. That's expected — fill in assertions per stub.
3. Re-invoke /macos-e2e-scaffold if you add new top-level views or controls. Skill detects existing UI test target and refuses overwrite (extend manually).

Project-type-specific behavior

xcodegen-managed

Modify xcodegen.yml (or project.yml) to add new target:

targets:
  <TARGET_DIR>:
    type: bundle.ui-testing
    platform: macOS
    sources:
      - <TARGET_DIR>
    dependencies:
      - target: <App>

Skill writes the diff. User runs xcodegen generate to regenerate .xcodeproj. Report says: "Run xcodegen generate before opening Xcode."

SPM-based

SwiftPM does NOT support UI Test bundles natively (only .testTarget for unit tests). UI Tests require .xcodeproj.

Skill detects this case:

  • Generate test files in Tests/<TARGET_DIR>/ directory
  • Print warning: "SPM doesn't support UI Test bundles. Generated files exist but require .xcodeproj. Recommend: switch to xcodegen-managed project, or add .xcodeproj manually."
  • Refuse to attempt project-modification

This is an honest limitation, not a skill failure. (Note: SPM-only is still a valid input — the skill proceeds and generates files; it does not refuse in Phase 0.)

Runner for SPM-only: do NOT emit the normal Step 11 runner — without an .xcodeproj there is no scheme, so xcodebuild test -scheme "$SCHEME" fails at scheme resolution, not at test execution. Instead write a stub scripts/run-uitests.sh that is honest about the precondition:

#!/usr/bin/env bash
# Auto-generated by /macos-e2e-scaffold (SPM-only project).
echo "SPM-only project: no .xcodeproj → no scheme for 'xcodebuild test'." >&2
echo "Convert to an xcodegen-managed project or add an .xcodeproj, then re-run /macos-e2e-scaffold." >&2
exit 1

So a CI consumer that follows /e2e-route's "Next action: ./scripts/run-uitests.sh" fails fast with a clear reason instead of an opaque scheme-resolution error.

plain .xcodeproj (no xcodegen)

Skill cannot reliably modify project.pbxproj programmatically (one wrong line corrupts the project).

  • Generate files in <TARGET_DIR>/ directory
  • Emit step-by-step manual instructions:
    1. Open <App>.xcodeproj in Xcode
    2. File > New > Target > macOS > UI Testing Bundle
    3. Name: <TARGET_DIR>  ← MUST equal the Phase 0 TARGET_DIR EXACTLY. The generated
       scripts/run-uitests.sh hardcodes `-only-testing:"${UITEST_TARGET}"` with this
       name baked in; a different Xcode-suggested name makes the runner report
       "no tests / target not found".
    4. Drag generated .swift files into target
    5. Set Host Application: <App>
    6. Build target once to verify
    
  • Report says: "Generated files exist; manual Xcode steps required for target setup. Name the UI-test target exactly <TARGET_DIR> to match the runner."

Failure modes

ModeDetectionResolution
Project doesn't buildxcodebuild build fails before scaffoldSkill stops; user fixes build first
Identifier uniqueness conflictSame ID for 2+ controlsSkip both; flag for manual review
Existing UI test targetPhase 0 globs *UITests/ (any dir, excluding *iOSUITests/) *.swift count > 1 — name-agnostic, before scheme detectionRefuse; suggest manual extension
Phase 0 failsRefuse-condition triggeredReturn early; never modify files
Platform ambiguous (WindowGroup only)No macOS-discriminating signal foundRefuse "No SwiftUI macOS app target detected" — do NOT assume macOS from WindowGroup
Multiplatform target.iOS( AND .macOS(, or both platforms in SUPPORTED_PLATFORMSPass; scaffold macOS tests into <App>macOSUITests/; note iOS surface needs /ios-e2e-scaffold
User declines identifier-application[s]kip answerSkip Step 9; generate test files with placeholder comments
Cherry-pick rejected per-suggestionUser says [n]Apply only confirmed subset
xcodegen not in PATHxcodegen not foundEmit instruction: brew install xcodegen
Empty SwiftUI appStep 4 yields zero interactive controlsGenerate Smoke test only; report "No interactive controls found — only Smoke test generated. Add controls and re-invoke."
Existing .accessibilityIdentifier(...)Step 7 next-5-lines checkPreserve; report under "Already identified (preserved)"
Controls in #Preview { ... } / PreviewProviderStep 7 brace-depth trackingExclude from suggestions and density count
xcresulttool API mismatchxcrun xcresulttool exits non-zeroRunner falls back to tail -50 of plaintext xcodebuild output; header notes Xcode 16+ requirement
xcodegen.yml unknown structureCannot find targets:/name: keysSwitch to plain .xcodeproj branch; do NOT modify yml; flag ambiguity in report

Skill never silently corrupts project files. All modifications confirmed; uniqueness conflicts skip; .xcodeproj is never directly edited.

Relationship to other skills

SkillLayerAsks
pitfall-verificationartifactWill this work?
quality-reviewartifactWill this feel premium?
macos-native-reviewartifactIs this Apple-native?
macos-e2e-scaffoldprojectIs this E2E-tested?
swiftui-expert-skill (Antoine)codeIs the view code idiomatic?
swift-testing-expert (Antoine)codeIs unit-test code idiomatic?
swift-concurrency-expert (Antoine)codeIs async/await usage correct?
core-data-expert (Antoine)codeIs the persistence layer well-designed?

macos-e2e-scaffold is one of the two scaffold skills (with /ios-e2e-scaffold) that create test infrastructure rather than reviewing artefacts. This warrants the manual-only invocation pattern (no auto-trigger) — skill should run with full user awareness, not as a pipeline step.

Runner script template

scripts/run-uitests.sh:

#!/usr/bin/env bash
# Auto-generated by /macos-e2e-scaffold v1.10.0
# Runs UI tests and emits Claude-readable JSON summary.
# Requires Xcode 16+ for JSON xcresulttool format; falls back to plaintext on older.

set -uo pipefail

SCHEME="<APP>"
UITEST_TARGET="<TARGET_DIR>"   # <App>UITests, or <App>macOSUITests on multiplatform
RESULT_BUNDLE="$(mktemp -d)/uitests.xcresult"

# Run the tests. Capture xcodebuild's OWN exit status via PIPESTATUS — piping into
# `tail` would otherwise mask a non-zero status (tail returns 0), making a failing
# committed-regression run look green in CI.
xcodebuild test \
  -scheme "$SCHEME" \
  -destination 'platform=macOS' \
  -only-testing:"${UITEST_TARGET}" \
  -resultBundlePath "$RESULT_BUNDLE" \
  -quiet 2>&1 | tail -50
TEST_STATUS=${PIPESTATUS[0]}

# Parse xcresult to JSON summary (Xcode 16+); fall back to plaintext on older Xcode.
xcrun xcresulttool get test-results summary --path "$RESULT_BUNDLE" --format json 2>/dev/null \
  | jq '{total: .totalTestCount, passed: .passedTests, failed: .failedTests, results: [((.testFailures // [])[]) | {test: .testIdentifier, file: .sourceCodeContext.location.filePath, line: .sourceCodeContext.location.lineNumber, message: .failureText}]}' 2>/dev/null \
  || { echo "(Xcode 16+ JSON format unavailable — falling back to plaintext)"; \
       xcrun xcresulttool get --path "$RESULT_BUNDLE" 2>/dev/null | tail -100 || true; }
# NOTE: `.testFailures` is null/absent on an all-green run; `(.testFailures // [])`
# guards `null | .[]` so passing runs still emit clean JSON instead of falling back.

# Exit with the REAL test status so CI / committed-regression runs fail when tests fail.
exit "$TEST_STATUS"

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.