agentsclimarketplace

Host driven xcuitest e2e

Skill wei18/apple-dev-skills/apple-dev-skills/skills/host-driven-xcuitest-e2e

Reusable Claude Code skills for AI-agent-driven Swift / Apple-platform development — composable via git submodule; aggregates other specialist skill repos

Install
npx -y skills add wei18/apple-dev-skills --skill host-driven-xcuitest-e2e

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 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

Use when wiring or debugging launch-the-app XCUITest E2E with Tuist — a native `.uiTests` target needs its own scheme with `testAction: .targets([...])` (adding it to an `.xctestplan` builds but fails with "no test bundles available to test"), named to avoid colliding with an SPM `<Target>UITests` package test target. Also covers driving SwiftUI on macOS, where `element.tap()` throws `point.x != INFINITY` and `app.coordinate(...)` resolves to `(-inf, -inf)` — worked around by anchoring on the window element and clicking finite element frames — plus `hittable` being an invalid NSPredicate key, the sandbox rejecting `/tmp` writes, and locale-stable accessibility queries. Use when asked "why does xcodebuild say no test bundles" or "how do I XCUITest-drive my macOS app".

SKILL.md

9.5 KB, as published. Nobody here has run it

Host-Driven XCUITest E2E

"Host-driven" means the test process launches the real app and drives it through the Simulator or a Mac window via XCUIApplication — distinct from unit/snapshot tests, which never launch a process at all. This is the automated, CI-runnable sibling of interactive-simulator-ux-audit: use that skill to find a flow bug by hand, this one to pin it as a regression test.

When to invoke

  • Wiring a first host-driven E2E target in a Tuist-generated project.
  • xcodebuild test reports "There are no test bundles available to test" for a target that otherwise builds cleanly.
  • Driving a SwiftUI macOS (AppKit-hosted) window with XCUITest and taps aren't landing.
  • Naming a new UI test target and avoiding a collision with an existing package test target.

Scope

Owns: Tuist scheme/target wiring for native XCUITest targets, and the macOS driving mechanics below. Does not own: manual/interactive Simulator exploration → interactive-simulator-ux-audit; unit/snapshot test framework choice → swift-testing-baseline; general navigation architecture under test → swiftui-navigation-architecture.

Tuist wiring: a dedicated scheme, not a test plan

A Tuist .uiTests product target needs its own scheme with an explicit testAction: .targets([...]) — not membership in an existing scheme's .xctestplan.

// Project.swift
let e2eTarget = Target.target(
    name: "MyAppE2ETests",
    destinations: .iOS,
    product: .uiTests,
    bundleId: "com.example.myapp.e2etests",
    sources: ["E2ETests/**"],
    dependencies: [.target(name: "MyApp")]
)

let e2eScheme = Scheme.scheme(
    name: "MyApp-E2E",
    shared: true,
    buildAction: .buildAction(targets: ["MyApp", "MyAppE2ETests"]),
    testAction: .targets(["MyAppE2ETests"])   // NOT .testPlans([...])
)
  • Adding a native .uiTests target to an existing scheme's .testPlans([...]) builds the scheme but produces no .xctest bundle for it — xcodebuild test then fails with "no test bundles available to test". A package's own SPM test targets run fine via a test plan because they're cross-linked into the plan's buildables; a same-project native UI test target is not. The fix is the dedicated scheme above, not a plan tweak.
  • TestAction's factory is .targets(...), not .testAction(...); TestableTarget accepts a plain string literal target name.
  • Name the target <App>E2ETests, not <App>UITests, if the same package already has an SPM snapshot/unit target with that suffix (e.g. MyAppUITests for snapshot tests) — the two would collide.
  • Give the E2E target its own build settings (not the app's entitlement-carrying settings) so it carries no unintended app capabilities, and keep the E2E scheme's default test action on Debug only — it should never be part of a Release archive or the fast default test run.

Driving SwiftUI on macOS: window-frame anchoring

On native (AppKit-hosted) macOS SwiftUI, the naive APIs don't work:

  • element.tap() throws point.x != INFINITY (NSInternalInconsistencyException) — SwiftUI exposes no accessibility activation point on macOS the way it does on iOS.
  • app.coordinate(withNormalizedOffset: .zero) resolves to (-inf, -inf) — the application element itself has no usable frame to normalize against.

Working pattern: element frames in the accessibility tree are finite and correct. Anchor a coordinate on the main window instead of the element, and click by frame:

let window = app.windows.firstMatch

func click(_ rect: CGRect) {
    let origin = window.frame.origin
    window.coordinate(withNormalizedOffset: .zero)
        .withOffset(CGVector(dx: rect.midX - origin.x, dy: rect.midY - origin.y))
        .tap()
}

click(app.buttons["submit"].frame)

Other macOS-driving specifics:

  • hittable is not a valid NSPredicate key in an XCUIElementQuery predicate (XCTElementQueryInvalidPredicate at runtime) — isHittable only works as a Swift-side property check, never inside a predicate string.
  • The test-runner process's sandbox usually can't write to /tmp. Dump diagnostic state (e.g. app.debugDescription) via print() so it lands in the xcodebuild log directly — and don't pipe xcodebuild through tail or another filter that can drop buffered output before the dump is flushed.
  • Tapping the same element across multiple steps where its accessibility label mutates between taps (e.g. a cell whose label changes once filled): capture element.frame once and convert it to an app-relative coordinate up front, rather than re-querying the element by its now-stale label on each subsequent tap.
  • Test classes that touch XCUIElement APIs must be @MainActor under Swift 6 strict concurrency — those APIs are main-actor-isolated.
  • -only-testing:<Target>/<Class>/<method> requires all three path segments; a partial path silently runs the whole target instead of failing.

Locale-stable queries

If the app ships more than one locale, query by a stable accessibility identifier set in code (accessibilityIdentifier("game.completion.hero")) rather than by visible label text — translated strings break a hardcoded English-label query the moment a non-English locale runs the same test. Reserve literal label matching for elements whose text is guaranteed locale-invariant by design (e.g. digits, or an identifier deliberately not localized).

Deterministic entry points for hard-to-reach states

A flow that's expensive or unreliable to reach through pure UI interaction (a multi-step win condition, a rare error state) benefits from a debug-only, launch-argument-gated seam that deterministically lands the app one step from the state under test, so the test asserts the transition, not the app's own internal logic reproduced via blind taps. Keep this seam compiled out of Release builds.

Rationale

A host-driven E2E test is the only automated check that proves the full launch → navigate → interact → assert path works end-to-end, including scheme/target wiring, entitlements, and real accessibility tree resolution — none of which a unit or snapshot test exercises. It's slower and more environment-sensitive than either, which is why it complements rather than replaces them (see the test pyramid in swift-testing-baseline).

Deviation considerations

  • Cross-package test target (the UI test target must live in a different SwiftPM package than the app target): a dedicated scheme may not resolve the cross-package build graph cleanly — an .xctestplan referencing both packages' schemes can be the more reliable choice there; verify empirically rather than assuming the dedicated-scheme rule always wins.
  • iOS-only app with no macOS target: skip the window-frame-anchoring section entirely; standard element.tap() works on iOS.

Common Mistakes

  1. Adding a native .uiTests target to an existing test plan instead of giving it a dedicated scheme — silently produces zero test bundles.
  2. Naming a new E2E target <App>UITests when an SPM snapshot/unit target already uses that name — Xcode target collision.
  3. Calling element.tap() on macOS — throws point.x != INFINITY; use window-frame anchoring instead.
  4. Putting hittable in an NSPredicate string — runtime XCTElementQueryInvalidPredicate; use isHittable in Swift code.
  5. Re-querying an element by a label that mutates during the test — capture the frame once, before the label changes.
  6. Matching on visible label text in a localized app — breaks under any non-English locale; query by accessibility identifier instead.

Review Checklist

  • The E2E target has its own Tuist scheme with testAction: .targets([...]), not a .xctestplan membership.
  • The E2E target's name doesn't collide with any SPM <Target>UITests package target.
  • The E2E scheme is Debug-only and excluded from the Release archive action.
  • macOS taps go through window-frame anchoring, not element.tap() / app.coordinate(...).
  • No hittable inside an NSPredicate string.
  • Locale-sensitive elements are queried by accessibility identifier, not label text.
  • -only-testing: invocations include all three path segments.
  • Any debug-only entry-point seam is compiled out of Release builds.

Related skills

  • interactive-simulator-ux-audit — the manual/exploratory sibling; find the bug there, pin it here.
  • swift-testing-baseline — where E2E sits in the overall test pyramid.
  • swiftui-navigation-architecture — the navigation shape these tests typically assert against.
  • swiftpm-modularization — package/target layout that affects whether a dedicated scheme or a test plan is the right wiring.

Gives 0 of the 12 instructions most e2e browser skills give

Counted across 407 of the 410 authors here whose files we hold, read 2026-08-06

  • use page object model patternin 35 of 407, across 25 files
  • Snapshot to get element refsin 24 of 407, across 14 files
  • keep tests independentin 23 of 407, across 18 files
  • Interact using refs from the latest snapshotin 23 of 407, across 11 files
  • clean up test data after each testin 21 of 407, across 15 files
  • test user behavior not implementationin 20 of 407, across 14 files
  • quarantine flaky tests explicitlyin 19 of 407, across 10 files
  • wait for specific network conditionsin 18 of 407, across 8 files
  • re-snapshot after navigation or dom changesin 17 of 407, across 10 files
  • Detect running dev servers before writing test codein 17 of 407, across 7 files
  • use web-first assertionsin 17 of 407, across 14 files
  • capture screenshots or videos on test failurein 17 of 407, across 14 files

Said here and by no other author read

  • wire native UI tests to a dedicated scheme
  • use testAction targets instead of test plans
  • name the target AppE2ETests
  • click element frames anchored to the window
  • check isHittable in swift code only
  • dump diagnostic state via print

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

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.