원클릭으로
ui-tests
UI tests with Robot pattern. Use when creating UI tests, implementing Robot classes, or adding accessibility identifiers.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
UI tests with Robot pattern. Use when creating UI tests, implementing Robot classes, or adding accessibility identifiers.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Swift 6 concurrency patterns. Use when working with async/await, actors, MainActor isolation, or Sendable conformance.
Creates Features for dependency injection. Use when creating features, exposing public entry points, or wiring up dependencies.
Creates Repositories that abstract data access. Use when creating repositories, transforming DTOs to domain models, or implementing local-first caching. Supports remote-only, local-only, and cached (remote + local) repositories with CachePolicy.
Creates a new feature module with minimal viable structure. Use when bootstrapping a new feature from scratch, scaffolding the Tuist module, Container, Feature entry point, DeepLinkHandler, and initial screen with placeholder Text view. Includes all unit tests, mocks, stubs, and app integration. For adding domain/data layers afterward, use /datasource, /repository, /usecase. For enhancing views, use /view, /viewmodel, /navigator.
Creates Navigator for navigation. Use when setting up navigation, adding navigation to ViewModels, or testing navigation behavior.
Creates ViewModels with state management. Use when creating ViewModels, implementing ViewState pattern, or adding state management for features. Delegates to /usecase for domain use cases and to /feature for Container/Feature wiring.
| name | ui-tests |
| description | UI tests with Robot pattern. Use when creating UI tests, implementing Robot classes, or adding accessibility identifiers. |
Guide for creating UI tests using XCTest with the Robot pattern.
App/Tests/UI/
├── HomeUITests.swift # Home screen flow (launch from home)
├── CharacterListUITests.swift # Character list (deep link)
├── CharacterDetailUITests.swift # Character detail (deep link)
├── CharacterEpisodesUITests.swift # Character episodes (deep link)
└── NotFoundUITests.swift # Invalid deep link → not found screen
App/Tests/Shared/
├── Robots/
│ ├── Robot.swift # UITestCase base class
│ ├── HomeRobot.swift
│ ├── AboutRobot.swift
│ ├── NotFoundRobot.swift
│ ├── CharacterListRobot.swift
│ ├── CharacterDetailRobot.swift
│ ├── CharacterFilterRobot.swift
│ └── CharacterEpisodesRobot.swift
└── Scenarios/
└── UITestCase+Scenarios.swift
| Rule | Description |
|---|---|
Extend UITestCase | Inherits mock server setup, teardown, and robot DSL |
async throws on test methods | Required for await serverMock.registerCatchAll |
@MainActor on test methods | Required for UI interactions (XCUIApplication) |
| Actions section | Methods that perform UI interactions (tap, swipe, type) |
| Verifications section | Methods that assert UI state |
@discardableResult | All robot methods return Self for chaining |
#filePath and line | Pass through for accurate test failure locations |
| Private AccessibilityIdentifier | Each Robot has its own copy of identifiers |
.firstMatch | Use when multiple elements may match an identifier |
Use launch() and navigate through the app via robots. Best for multi-screen flows starting from home. One comprehensive test per flow.
final class HomeUITests: UITestCase {
@MainActor
func testHomeFlowAboutSheetCharacterListAndBack() async throws {
// Given
try await givenCharacterListSucceeds()
// When
launch()
// Then
home { robot in
robot.verifyIsVisible()
robot.tapInfoButton()
}
about { robot in
robot.verifyIsVisible()
robot.swipeUp()
robot.verifyCreditsExist()
robot.tapClose()
}
home { robot in
robot.verifyIsVisible()
robot.tapCharacterButton()
}
characterList { robot in
robot.verifyIsVisible()
robot.tapBack()
}
home { robot in
robot.verifyIsVisible()
}
}
}
Use launch(deepLink: url) to navigate directly to a specific screen. Best for comprehensive single-screen tests covering error/retry, main interactions, and navigation. One test class per screen with a single comprehensive test method. Use // swiftlint:disable:next function_body_length for long test methods.
final class CharacterDetailUITests: UITestCase {
@MainActor
func testCharacterDetailErrorRetryRefreshEpisodesAndBack() async throws {
// Given — all requests fail
await givenAllRequestsFail()
let url = try XCTUnwrap(URL(string: "challenge://character/detail/1"))
// When — launch with deep link
launch(deepLink: url)
// Then — error screen
characterDetail { robot in
robot.verifyErrorIsVisible()
}
// Recovery — configure responses
try await givenCharacterDetailSucceeds()
// Retry — content loads
characterDetail { robot in
robot.tapRetry()
robot.verifyIsVisible()
robot.pullToRefresh()
robot.verifyIsVisible()
}
// Navigate forward and back
try await givenCharacterEpisodesRecovers()
characterDetail { robot in
robot.tapEpisodes()
}
characterEpisodes { robot in
robot.verifyIsVisible()
robot.tapBack()
}
characterDetail { robot in
robot.verifyIsVisible()
}
}
}
launch() + app.open(url)Use launch() then app.open(url) when testing deep links that the app handles at runtime (not at launch). This is required for invalid/unknown routes because DEEP_LINK_URL env var only resolves known routes at launch time.
final class NotFoundUITests: UITestCase {
@MainActor
func testNotFoundScreenAndGoBack() async throws {
// Given — all requests return 404
await givenAllRequestsReturnNotFound()
launch()
let url = try XCTUnwrap(URL(string: "challenge://invalid/route"))
// Verify home is visible
home { robot in
robot.verifyIsVisible()
}
// When — open invalid deep link
app.open(url)
// Then — not found screen is visible
notFound { robot in
robot.verifyIsVisible()
robot.tapGoBack()
}
// Verify home is visible after going back
home { robot in
robot.verifyIsVisible()
}
}
}
| Screen | URL |
|---|---|
| Character List | challenge://character/list |
| Character Detail | challenge://character/detail/{id} |
| Character Episodes | challenge://episode/character/{id} |
Each screen test follows the same structure:
givenAllRequestsFail() + launch(deepLink: url) → verify error{screenName}.{elementType} (e.g., home.characterButton)row(id:))accessibilityIdentifier: to DS components for child propagationWhen using accessibilityIdentifier: "characterList.row.1" on DSCardInfoRow:
characterList.row.1DSAsyncImage + SwiftUI modifier): characterList.row.1.imagecharacterList.row.1.titleDSStatusIndicator: characterList.row.1.statusRun a specific test class:
mise x -- tuist test "ChallengeUITests" -- -only-testing:ChallengeUITests/CharacterDetailUITests
Run all UI tests:
mise x -- tuist test "ChallengeUITests" 2>&1 | tee /tmp/ui-tests.txt | tail -30
let app: XCUIApplication@discardableResult methods@discardableResult methodsAccessibilityIdentifier enum#filePath and line for accurate failure locations.firstMatch for dynamic elementsRobot.swift (e.g., func myScreen(actions:))UITestCase (provides serverMock, serverBaseURL, launch())@MainActor and async throwsUITestCase+Scenarios (or create new ones)// Given / // When / // Then structurelaunch() for flow tests, launch(deepLink: url) for screen testshome, characterList, etc.)verifyIsVisible()AccessibilityIdentifier enum to View{screenName}.{elementType} for identifiers.accessibilityIdentifier() to standard SwiftUI elementsaccessibilityIdentifier: parameter to DS components for propagationrow(id:))