xcode-dev-loop
WHEN building, testing, or visually verifying an Xcode project from the command line — xcodebuild runs, Swift Testing result reading, simulator install/launch, and screenshots; NOT for authoring Swift source or tests; returns the canonical build/test/inspect loop.
- License 许可
- MIT
- 所属仓库
- mintuz/skills
- 最近更新
- 2026-11-13
- Stars
- ★ 35
- Forks
- 7
01 前言 Front Matter
- name
- xcode-dev-loop
- description
- WHEN building, testing, or visually verifying an Xcode project from the command line — xcodebuild runs, Swift Testing result reading, simulator install/launch, and screenshots; NOT for authoring Swift source or tests; returns the canonical build/test/inspect loop.
03 SKILL.md 正文
Xcode Dev Loop
Discover, build in the background, read results, launch on a simulator, capture screens.
This loop runs and inspects; it does not author Swift source or tests. Route
Swift Testing authoring to swift-testing, XCTest UI automation to xcuitest,
and existing non-UI XCTest unit/integration or measure tests to the project's
established XCTest conventions with tdd. A dedicated reusable non-UI XCTest
authoring guide remains a gap. Preserve the suite's framework rather than forcing
a migration. Return here to execute and interpret the real result.
1. Discover Before You Guess
- Find the container first: list the checkout for a
.xcworkspaceor.xcodeproj. - List the schemes of a workspace with
xcodebuild -list -workspace <name>.xcworkspace. - List the targets, build configurations, and schemes of a project with
xcodebuild -list -project <name>.xcodeproj(use this form when you need a target name). - Build and test with the container flag you listed with, and always name the scheme:
xcodebuild test -workspace <name>.xcworkspace -scheme <Scheme>. Thetestaction fails without a scheme. - List simulator UDIDs with
xcrun simctl list devices available -j; resolve the UDID once, then reuse it. - Read installed simulator command options with
xcrun simctl help <subcommand>. - Never address a simulator by name: duplicate names ("iPhone 17 Pro Max") cause "No booted simulator named …" failures. Only the listing output separates same-named devices, so give the listing command beside the UDID whenever you replace a name.
- When a scheme needs a runtime the default Xcode lacks (e.g. a watchOS beta), export
DEVELOPER_DIR=/Applications/Xcode-<version>.app/Contents/Developerfor the whole session, so everyxcodebuildandxcruncall selects the same Xcode.
2. Build and Test in the Background
-
Never let
xcodebuildhold the foreground: a clean build or test run routinely exceeds the 600 s command timeout, and a command killed at the timeout leaves no verdict. -
Start the run in the background, redirect its output to a log file in the scratchpad, and keep a handle whose exit status you can read later. Prefer the harness's background facility (
Bashwithrun_in_background: true) and the task handle it returns. -
With only a plain shell, make the run record its own exit status; a later shell cannot
waitfor another shell's child. Give every attempt its own log and status path; a stale status file ends the wait at once and reports the wrong result:( xcodebuild ... > "$LOG" 2>&1; echo $? > "$LOG.status" ) & # start it until [ -f "$LOG.status" ]; do sleep 5; done # wait, in a later call -
Use
CODE_SIGNING_ALLOWED=NOfor simulator builds. -
Pin the destination as
platform=iOS Simulator,id=<udid>. -
When one target or test matters, scope the run with
-only-testing:<TestTarget>[/<Suite>/<test>]; preserve the log. -
Never wait with a chained foreground
sleep; the harness blocks it. -
Wait on the kept handle with the harness's blocking wait (
Monitor, or the task-output wait), or for the.statusfile with the loop above. Read the exit status first, then the log's terminal marker. Do not wait by grep alone: it keeps polling whenxcodebuildexits before writing a marker.
3. Read Results Correctly (Swift Testing Is Not XCTest)
- Swift Testing does NOT emit XCTest's
Test Case ... passedlines. Executed 0 tests, with 0 failuresis the empty XCTest summary; it proves nothing.- Always pass
-resultBundlePathwith a new path per attempt;xcodebuildfails when the path already exists. - Read the verdict from that fresh bundle with
xcrun xcresulttool get test-results summary --path <bundle>. A pass needs the expected non-zero test count there, no failure or crash there,** TEST SUCCEEDED **, and a zero exit status (principle-prove-it-works). - The log's
✔ Test run with N tests in M suites passed, with N greater than zero, corroborates that count; a pattern that also matches zero proves nothing. A filtered or-quietlog can omit the line, so never treat its absence alone as the verdict. - Take the expected test count from the target's tests or test plan, never from the run you are judging.
- Match the expected case IDs against
xcresulttool get test-results tests --path <bundle>; totals alone do not prove each selected case ran. Inspecttest-detailsandmetricswhen a case fails or measures performance, preserving the actual measurements and failure reason. Pin the toolchain, configuration, device and workload for comparisons. State the non-UI XCTest authoring-guide gap explicitly when using project conventions as the fallback. - A run that executed zero tests is not a pass, whatever its exit status
(
principle-gaps-are-not-passes). Suspect the selection before the code: confirm-only-testingstill names the current suite and test after any rename, and that the target belongs to the test plan. - Grep once with the full set of terminal markers:
\*\* BUILD SUCCEEDED \*\*,\*\* BUILD FAILED \*\*,\*\* TEST SUCCEEDED \*\*,\*\* TEST FAILED \*\*,Testing failed:, and^error:. Judge the marker together with the exit status. - To confirm which suites ran, match both spellings: XCTest prints
Test Suite '.*' started, and Swift Testing prints aSuite .* startedrecord. Match them case-sensitively as printed. - On failure, read the bundle summary for the structured crash reason, such as
Test crashed with signal abrt; it may appear only there. The log may repeat it as an underlying error, but the bundle is the reliable source. Do not re-run blind. - Treat
The test runner hung before establishing connectionand diagnostic-collection timeouts as a suspected environmental fault, not a proven diagnosis. Shut down the pinned simulator (xcrun simctl shutdown <udid>) only if this run created and still owns it, then retry once within the run's time bounds with a new result-bundle path. Pinning a pre-existing device does not confer ownership: preserve it and report blocked when recovery needs its shutdown. Never usexcrun simctl shutdown all. - Pass
TEST_RUNNER_<NAME>=<value>on thexcodebuild testcommand; Xcode strips the prefix and gives the variable to the test runner process. A UI test target's app under test does not receive it; setXCUIApplication.launchEnvironmentfor that case. test-without-buildingruns the product and.xctestrunfile an earlier build produced, so it can report a success that excludes your latest source change. Rebuild withxcodebuild testbefore you trust a pass, and whenever that product is the suspect.- A test-bundle load error names the missing library, not the cause. When a load failure survives the rebuild, inspect the framework's embedding and runpath settings as candidates.
4. Install, Launch, and Drive
- Install the build you just made before launching. Never launch whatever is already on the
device: it can come from another branch (
principle-prove-it-works). Record revision plus relevant uncommitted-source identity and bind it to the actual built, installed and launched product; a matching HEAD alone is insufficient. simctl installneeds a booted device. Boot the pinned device withxcrun simctl bootstatus <udid> -b, which boots it if shut down and waits for the boot to finish. Do not callxcrun simctl bootfirst: it fails on a running device.- Know the product path before you install: pass
-derivedDataPath <dir>to the build, or readBUILT_PRODUCTS_DIRandFULL_PRODUCT_NAMEfromxcodebuild -showBuildSettings. On that call repeat every flag the build used, including-derivedDataPath, the scheme, and the destination; different flags report another build's product path. - Read the bundle id from that build, not from memory:
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' <app-path>/Info.plist. - Prefer the simulator MCP: call its install action with the new
.apppath, then its launch action with the bundle id. Pass the pinned UDID to both; their device argument is optional and defaults elsewhere. - A running MCP server does not inherit a
DEVELOPER_DIRexported in a shell. When the run needs a beta Xcode, use thexcruncommands with thatDEVELOPER_DIRinstead. - When the MCP launch fails with
Failed to spawn xcrun (via disclaimer), fall back toxcrun simctl install <udid> <app-path>, thenxcrun simctl launch <udid> <bundle-id>. - A deep link (
xcrun simctl openurl <udid> <scheme>://<path>) or existing DEBUG/seed mechanism may establish a screen or fixture precondition. It must not replace the public interaction that the claimed journey is meant to prove. Use accessible UI automation for that boundary, not blind coordinate taps. For capture-only screen setup, prefer an existing deep link or DEBUG scenario over UI navigation; its purpose here is to reach the state, not prove creation or another public-action boundary. - Read the accessibility hierarchy before you tap; coordinates are the last resort.
5. Screenshots
- Pick ONE capture mechanism per run:
xcrun simctl io <udid> screenshot <path>.pngwhen the file must persist for comparison, the MCP screenshot action to look now. Never use both for the same frame. - Number the files (
round2/01-hub-gate.png) so rounds diff cleanly against a baseline. - A freshly erased simulator posts a system banner soon after boot. Wait for the boot with
xcrun simctl bootstatus <udid> -b, then read the accessibility hierarchy and confirm no system alert covers the view. Capture only after that check passes; a fixed short delay is not evidence (principle-observe-dont-infer).
Isolation, cleanup and evidence
Use independently owned simulator/app/fixture state when a run mutates data. Do not erase or reset a user's shared simulator or store. Record ownership before launch; only a created device may be shut down/deleted as this run's teardown. On success, error or cancellation, release only resources still owned by the run, never kill by process name or trust a reused PID. Preserve logs, result bundles and captures outside cleanup targets and confirm they survive teardown. A blocked device/entitlement/system boundary is not proved through another simulator path.
Use synthetic data and disposable accounts. Inspect and redact shareable log, result-bundle and capture derivatives; keep necessary originals access-controlled under the project's retention policy. Never publish credentials, customer data or whole unreviewed bundles.
In a capture plan, name the pinned UDID and derive the bundle identifier from the intended build; pass both to the launch command. Report missing state proof, failed cleanup and unavailable evidence explicitly rather than implying success.
6. Cadence
- Compile after the domain layer and again after the first view, not once at the end
(
principle-sequence-verifiable-units). - Run the test suite once, early, before you declare a change done.
Related Skills
app:debug— structured feedback loop for simulator failures, user-reported or observedcore:visual-feedback— interactive appearance diagnosis and before/after review; this loop supplies the execution, captures and identity/state evidence.app:swift-testing— Swift Testing authoring;app:xcuitest— XCTest UI authoring. Keep non-UI XCTest tests in their existing framework.app:swiftui-architecture— SwiftUI patterns for the code under test
04 同仓库更多 Skills
xstate
WHEN React state changes in response to an external answer — async submit, in-flight or loading flags, retries, cancellation, polling, optimistic updates, stale responses — or to an interaction with an interruptible middle, such as a drag or a gesture; or WHEN building, reviewing, testing, or visualising XState 5 actors and @xstate/react integrations; NOT for a value one event sets completely, such as a text draft, a focus flag, or a plain open and closed toggle; models typed lifecycles and renders legal Mermaid stateDiagram-v2 charts.
web-design
WHEN refining UI layout, typography, color, or polish; NOT code implementation; provides concise principles for intentional, legible design.
tailwind
USE WHEN writing, fixing or reviewing styling in a project that uses Tailwind CSS, even a one-class change or a request that does not name Tailwind: colours, focus rings, spacing, class names chosen by a prop, map or CVA variants, theme tokens and dark mode in the Tailwind stylesheet or tailwind.config, setup or upgrades (if unsure whether the project uses Tailwind, check package.json or the main stylesheet first); NOT WHEN writing plain CSS or Sass in a project without Tailwind; produces class names Tailwind can find, syntax for the installed major version, visible focus indicators, and no unrequested dependency or invented brand value.
refactoring
WHEN you need a plan to refactor, restructure, or clean up working code and the test result is known, either green or a recorded failing set; NOT for new feature delivery or any behavior change; reversible-baseline safety with prioritized improvements.
react
USE WHEN changing the logic, data, props, state, Effects or rendered text of a React component, hook or page, even a one-line change; NOT WHEN the request is styling only (class names, Tailwind, CSS, dark mode, spacing, shadows) or tests only, or when it models submit, in-flight, loading, retry or failure states, even for a named React form (xstate covers that, including its React wiring); applies React 19 composition, state, Effect, structure, API and performance rules with only the project's installed libraries.
react-testing
USE WHEN writing, fixing or reviewing a React Testing Library test for a component, hook or context provider, even one small test, or when asked for an end-to-end journey inside a Vitest or React Testing Library suite; NOT WHEN changing the component itself (react), testing XState actors or @xstate/react (xstate), or writing a Playwright or Cypress test; covers renderHook, providers, async results, forms and false-pass anti-patterns, and declines end-to-end journeys with the testable slices.