From xcode-sim-automation
Creates automated UI test for a view, runs it, and captures screenshots to ~/Downloads. Use when the user asks to create screenshots, capture UI images, test view rendering, or generate visual documentation for a view.
npx claudepluginhub gestrich/xcode-sim-automation --plugin xcode-sim-automationThis skill uses the workspace's default tool permissions.
Creates a UI automation test for a specific view, executes the test, and extracts screenshots. This skill automates the entire workflow from test creation to screenshot extraction.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Creates a UI automation test for a specific view, executes the test, and extracts screenshots. This skill automates the entire workflow from test creation to screenshot extraction.
Invoke this skill when you need to:
The skill will ask you which view to screenshot if you don't specify one.
Read .xcuitest-config.json from the project root. If it exists, use its values throughout this skill:
$PROJECT = config.xcodeProject (e.g., "MyApp.xcodeproj")$SCHEME = config.scheme (e.g., "MyApp")$DESTINATION = config.destination (e.g., "platform=macOS")$UI_TEST_TARGET = config.uiTestTarget (e.g., "MyAppUITests")$TEST_CLASS = config.testClass (e.g., "InteractiveControlTests")$TEST_METHOD = config.testMethod (e.g., "testInteractiveControl")$CONTAINER = config.containerPath (e.g., "~/Library/Containers/.../Data/tmp")$PROCESS_NAME = config.processName (e.g., "MyApp")If .xcuitest-config.json doesn't exist, ask the user for these values before proceeding.
If config.appSpecificNotes is set, read that file from the project root for app-specific navigation patterns and accessibility identifiers.
Add the package to your project via SPM:
// In Package.swift or via Xcode:
.package(url: "https://github.com/gestrich/xcode-sim-automation.git", from: "1.0.0")
The CLI wrapper script (Tools/xcuitest-control) is in the xcode-sim-automation repo. To find it:
Tools/xcuitest-control wrapper script (not the .py file)~/Developer/personal/xcode-sim-automation/Tools/xcuitest-controlgit clone https://github.com/gestrich/xcode-sim-automation.gitThe wrapper auto-builds the Swift CLI binary on first run and whenever source files change — no manual build step needed.
A Python fallback (Tools/xcuitest-control.py) is also available if the Swift toolchain isn't installed.
The skill executes these steps automatically:
Creates a new Swift test file in the UI test target ($UI_TEST_TARGET) that:
XCTestCaseXCTAttachmentTest file naming: ScreenshotTest_<ViewName>.swift
Example test structure:
import XCTest
final class ScreenshotTest_MyFeature: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
// MARK: - Helpers
/// Captures UI hierarchy at current state for debugging
func captureHierarchy(name: String) {
let hierarchy = XCTAttachment(string: app.debugDescription)
hierarchy.name = "\(name).txt"
hierarchy.lifetime = .keepAlways
add(hierarchy)
}
/// Finds a tappable element by trying Button, StaticText, then Cell
func findTappable(_ identifier: String) -> XCUIElement {
let button = app.buttons[identifier]
if button.waitForExistence(timeout: 2.0) { return button }
let staticText = app.staticTexts[identifier]
if staticText.waitForExistence(timeout: 2.0) { return staticText }
let cell = app.cells[identifier]
if cell.waitForExistence(timeout: 2.0) { return cell }
return button // Fallback - assertion will fail with clear message
}
// MARK: - Test
func testMyFeatureScreenshot() throws {
// Step 1: Navigate to first view
let someTab = app.buttons["SomeTab"]
XCTAssertTrue(someTab.waitForExistence(timeout: 10.0), "SomeTab should exist")
someTab.tap()
sleep(2)
captureHierarchy(name: "Step1-AfterSomeTab")
// Step 2: Navigate deeper - try both Button and StaticText
let targetElement = findTappable("TargetView")
XCTAssertTrue(targetElement.exists, "TargetView should exist")
targetElement.tap()
sleep(2)
captureHierarchy(name: "Step2-AfterTargetView")
// Verify we reached the correct view before taking screenshot
let viewIdentifier = app.staticTexts["ExpectedViewTitle"]
XCTAssertTrue(viewIdentifier.waitForExistence(timeout: 5.0), "Should be on MyFeature view")
// Take screenshot
let screenshot = app.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "MyFeature"
attachment.lifetime = .keepAlways
add(attachment)
// Final hierarchy capture
captureHierarchy(name: "Final-MyFeatureView")
}
}
See test-patterns.md for detailed helper functions and navigation patterns.
Always build first (catches errors without hanging), then run the specific test:
# Step 1: Build (catches errors without hanging)
xcodebuild build-for-testing \
-project $PROJECT \
-scheme $SCHEME \
-destination '$DESTINATION'
# Step 2: Run the specific test
xcodebuild test \
-project $PROJECT \
-scheme $SCHEME \
-destination '$DESTINATION' \
-only-testing:"$UI_TEST_TARGET/ScreenshotTest_MyFeature/testMyFeatureScreenshot"
macOS note: No simulator is needed — the app runs natively. Always kill stale processes first:
pkill -f "$PROCESS_NAME" 2>/dev/null; sleep 2
Extract screenshots from the .xcresult bundle:
# Find the latest xcresult
RESULT_BUNDLE=$(ls -td ~/Library/Developer/Xcode/DerivedData/*/Logs/Test/*.xcresult | head -1)
# Extract attachments
xcrun xcresulttool get --path "$RESULT_BUNDLE" --list
Extracted files include:
captureHierarchy callsIf your app requires login, handle it in your test's setUp method or as the first navigation step. Common patterns:
// Option 1: Launch argument to bypass login
app.launchArguments = ["--skip-login"]
app.launch()
// Option 2: Navigate through login UI
app.launch()
let usernameField = app.textFields["username"]
usernameField.tap()
usernameField.typeText("testuser")
// ...continue login flow
If your tests need credentials, consider:
-destination 'platform=macOS'./tmp/.pkill -f "$PROCESS_NAME" before running tests — stale app processes cause "Failed to terminate" errors.pinch command is iOS-only and will not work on macOS.User request: "Create a screenshot test for the SettingsView"
Skill will:
.xcuitest-config.json for project configurationappSpecificNotes file for navigation patterns (if configured)ScreenshotTest_Settings.swift in the UI test target (includes hierarchy capture)If navigation fails: Check hierarchy attachments to find correct element identifiers, then update the test with proper accessibility IDs.
Common issues:
if statements were used instead of XCTAssertTrue. Always use assertions for every navigation step.app.buttons["Item"] won't find a StaticText. Check the UI hierarchy to find the actual element type, or use the findTappable helper.xcodebuild build-for-testing as a separate step before xcodebuild test.