Skill: foscomputerservices/fosmvvm-ui-tests-generator (version 2.0.6)

--- name: fosmvvm-ui-tests-generator description: Generate UI tests for FOSMVVM SwiftUI views using XCTest and FOSTestingUI. Covers accessibility identifiers, ViewModelOperations, and test data transport. homepage: https://github.com/foscomputerservices/FOSUtilities metadata: {"clawdbot": {"emoji": "🖥️", "os": ["darwin"]}} --- # FOSMVVM UI Tests Generator Generate comprehensive UI tests for ViewModelViews in FOSMVVM applications. ## Conceptual Foundation > For full architecture context, see [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) | [OpenClaw reference]({baseDir}/references/FOSMVVMArchitecture.md) UI testing in FOSMVVM follows a specific pattern that leverages: - **FOSTestingUI** framework for test infrastructure - **ViewModelOperations** for verifying business logic was invoked - **Accessibility identifiers** for finding UI elements - **Test data transporter** for passing operation stubs to the app ``` ┌─────────────────────────────────────────────────────────────┐ │ UI Test Architecture │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Test File (XCTest) App Under Test │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ MyViewUITests │ │ MyView │ │ │ │ │ │ │ │ │ │ presentView() ───┼─────────────►│ Show view with │ │ │ │ with stub VM │ │ stubbed data │ │ │ │ │ │ │ │ │ │ Interact via ────┼─────────────►│ UI elements with │ │ │ │ identifiers │ │ .uiTestingId │ │ │ │ │ │ │ │ │ │ Assert on UI │ │ .testData────────┼──┐ │ │ │ state │ │ Transporter │ │ │ │ │ │ └──────────────────┘ │ │ │ │ viewModelOps() ◄─┼─────────────────────────────────────┘ │ │ │ verify calls │ Stub Operations │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ## Core Components ### 1. Base Test Case Class Every project should have a base test case that inherits from `ViewModelViewTestCase`: ```swift class MyAppViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations>: ViewModelViewTestCase<VM, VMO>, @unchecked Sendable { @MainActor func presentView( configuration: TestConfiguration, viewModel: VM = .stub(), timeout: TimeInterval = 3 ) throws -> XCUIApplication { try presentView( testConfiguration: configuration.toJSON(), viewModel: viewModel, timeout: timeout ) } override func setUp() async throws { try await super.setUp( bundle: Bundle.main, resourceDirectoryName: "", appBundleIdentifier: "com.example.MyApp" ) continueAfterFailure = false } } ``` **Key points:** - Generic over `ViewModel` and `ViewModelOperations` - Wraps FOSTestingUI's `presentView()` with project-specific configuration - Sets up bundle and app bundle identifier - `continueAfterFailure = false` stops tests immediately on failure ### 2. Individual UI Test Files Each ViewModelView gets a corresponding UI test file. **For views WITH operations:** ```swift final class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewOps> { // UI Tests - verify UI state func testButtonEnabled() async throws { let app = try presentView(viewModel: .stub(enabled: true)) XCTAssertTrue(app.myButton.isEnabled) } // Operation Tests - verify operations were called func testButtonTap() async throws { let app = try presentView(configuration: .requireSomeState()) app.myButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.myOperationCalled) } } private extension XCUIApplication { var myButton: XCUIElement { buttons.element(matching: .button, identifier: "myButtonIdentifier") } } ``` **For views WITHOUT operations** (display-only): Use an empty stub operations protocol: ```swift // In your test file protocol MyViewStubOps: ViewModelOperations {} struct MyViewStubOpsImpl: MyViewStubOps {} final class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewStubOpsImpl> { // UI Tests only - no operation verification func testDisplaysCorrectly() async throws { let app = try presentView(viewModel: .stub(title: "Test")) XCTAssertTrue(app.titleLabel.exists) } } ``` **When to use each:** - **With operations**: Interactive views that perform actions (forms, buttons that call APIs, etc.) - **Without operations**: Display-only views (cards, detail views, static content) ### 3. XCUIElement Helper Extensions Common helpers for interacting with UI elements: ```swift extension XCUIElement { var text: String? { value as? String } func typeTextAndWait(_ string: String, timeout: TimeInterval = 2) { typeText(string) _ = wait(for: \.text, toEqual: string, timeout: timeout) } func tapMenu() { if isHittable { tap() } else { coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } } } ``` ### 4. View Requirements **For views WITH operations:** ```swift public struct MyView: ViewModelView { #if DEBUG @State private var repaintToggle = false #endif private let viewModel: MyViewModel private let operations: MyViewModelOperations public var body: some View { Button(action: doSomething) { Text(viewModel.buttonLabel) } .uiTestingIdentifier("myButtonIdentifier") #if DEBUG .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle) #endif } public init(viewModel: MyViewModel) { self.viewModel = viewModel self.operations = viewModel.operations } private func doSomething() { operations.doSomething() toggleRepaint() } private func toggleRepaint() { #if DEBUG repaintToggle.toggle() #endif } } ``` **For views WITHOUT operations** (display-only): ```swift public struct MyView: ViewModelView { private let viewModel: MyViewModel public var body: some View { VStack { Text(viewModel.title) Text(viewModel.description) } .uiTestingIdentifier("mainContent") } public init(viewModel: MyViewModel) { self.viewModel = viewModel } } ``` **Critical patterns (for views WITH operations):** - `@State private var repaintToggle = false` for triggering test data transport - `.testDataTransporter(viewModelOps:repaintToggle:)` modifier in DEBUG - `toggleRepaint()` called after every operation invocation - `operations` stored as property from `viewModel.operations` **Display-only views:** - No `repaintToggle` needed - No `.testDataTransporter()` modifier needed - Just add `.uiTestingIdentifier()` to elements you want to test ## ViewModelOperations: Optional Not all views need ViewModelOperations: **Views that NEED operations:** - Forms with submit/cancel actions - Views that call business logic or APIs - Interactive views that trigger app state changes - Views with user-initiated async operations **Views that DON'T NEED operations:** - Display-only cards or detail views - Static content views - Pure navigation containers - Server-hosted views that just render data **For views without operations:** Create an empty operations file alongside your ViewModel: ```swift // MyDisplayViewModelOperations.swift import FOSMVVM import Foundation public protocol MyDisplayViewModelOperations: ViewModelOperations {} #if canImport(SwiftUI) public final class MyDisplayViewStubOps: MyDisplayViewModelOperations, @unchecked Sendable { public init() {} } #endif ``` Then use it in tests: ```swift final class MyDisplayViewUITests: MyAppViewModelViewTestCase< MyDisplayViewModel, MyDisplayViewStubOps > { // Only test UI state, no operation verification } ``` The view itself doesn't need: - `repaintToggle` state - `.testDataTransporter()` modifier - `operations` property - `toggleRepaint()` function Just add `.uiTestingIdentifier()` to elements you want to verify. ## Test Categories ### UI State Tests Verify that the UI displays correctly based on ViewModel state: ```swift func testButtonDisabledWhenNotReady() async throws { let app = try presentView(viewModel: .stub(ready: false)) XCTAssertFalse(app.submitButton.isEnabled) } func testButtonEnabledWhenReady() async throws { let app = try presentView(viewModel: .stub(ready: true)) XCTAssertTrue(app.submitButton.isEnabled) } ``` ### Operation Tests Verify that user interactions invoke the correct operations: ```swift func testSubmitButtonInvokesOperation() async throws { let app = try presentView(configuration: .requireAuth()) app.submitButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.submitCalled) XCTAssertFalse(stubOps.cancelCalled) } ``` ### Navigation Tests Verify navigation flows work correctly: ```swift func testNavigationToDetailView() async throws { let app = try presentView() app.itemRow.tap() XCTAssertTrue(app.detailView.exists) } ``` ## When to Use This Skill - Adding UI tests for a new ViewModelView - Setting up UI test infrastructure for a FOSMVVM project - Following an implementation plan that requires test coverage - Validating user interaction flows ## What This Skill Generates ### Initial Setup (once per project) | File | Location | Purpose | |------|----------|---------| | `{ProjectName}ViewModelViewTestCase.swift` | `Tests/UITests/Support/` | Base test case for all UI tests | | `XCUIElement.swift` | `Tests/UITests/Support/` | Helper extensions for XCUIElement | ### Per ViewModelView | File | Location | Purpose | |------|----------|---------| | `{ViewName}ViewModelOperations.swift` | `Sources/{ViewModelsTarget}/{Feature}/` | Operations protocol and stub (if view has interactions) | | `{ViewName}UITests.swift` | `Tests/UITests/Views/{Feature}/` | UI tests for the view | **Note:** Views without user interactions use an empty operations file with just the protocol and minimal stub. ## Project Structure Configuration | Placeholder | Description | Example | |-------------|-------------|---------| | `{ProjectName}` | Your project/app name | `MyApp`, `TaskManager` | | `{ViewName}` | The ViewModelView name (without "View" suffix) | `TaskList`, `Dashboard` | | `{Feature}` | Feature/module grouping | `Tasks`, `Settings` | ## How to Use This Skill **Invocation:** /fosmvvm-ui-tests-generator **Prerequisites:** - View and ViewModel structure understood from conversation context - ViewModelOperations type identified (or confirmed as display-only) - Interactive elements and user flows discussed **Workflow integration:** This skill is typically used after implementing ViewModelViews. The skill references conversation context automatically—no file paths or Q&A needed. Often follows fosmvvm-swiftui-view-generator or fosmvvm-react-view-generator. ## Pattern Implementation This skill references conversation context to determine test structure: ### Test Type Detection From conversation context, the skill identifies: - **First test vs additional test** (whether base test infrastructure exists) - **ViewModel type** (from prior discussion or View implementation) - **ViewModelOperations type** (from View implementation or context) - **Interactive vs display-only** (whether operations need verification) ### View Analysis From requirements already in context: - **Interactive elements** (buttons, fields, controls requiring test coverage) - **User flows** (navigation paths, form submission, drag-and-drop) - **State variations** (enabled/disabled, visible/hidden, error states) - **Operation triggers** (which UI actions invoke which operations) ### Infrastructure Planning Based on project state: - **Base test case** (create if first test, reuse if exists) - **XCUIElement extensions** (helper methods for common interactions) - **App bundle identifier** (for launching test host) ### Test File Generation For the specific view: 1. Test class inheriting from base test case 2. UI state tests (verify display based on ViewModel) 3. Operation tests (verify user interactions invoke operations) 4. XCUIApplication extension with element accessors ### View Requirements Ensure test identifiers and data transport: 1. `.uiTestingIdentifier()` on all interactive elements 2. `@State private var repaintToggle` (if has operations) 3. `.testDataTransporter()` modifier (if has operations) 4. `toggleRepaint()` calls after operations (if has operations) ### Context Sources Skill references information from: - **Prior conversation**: View requirements, user flows discussed - **View implementation**: If Claude has read View code into context - **ViewModelOperations**: From codebase or discussion ## Key Patterns ### Test Configuration Pattern Use `TestConfiguration` for tests that need specific app state: ```swift func testWithSpecificState() async throws { let app = try presentView( configuration: .requireAuth(userId: "123") ) // Test with authenticated state } ``` ### Element Accessor Pattern Define element accessors in a private extension: ```swift private extension XCUIApplication { var submitButton: XCUIElement { buttons.element(matching: .button, identifier: "submitButton") } var cancelButton: XCUIElement { buttons.element(matching: .button, identifier: "cancelButton") } var firstItem: XCUIElement { buttons.element(matching: .button, identifier: "itemButton").firstMatch } } ``` ### Operation Verification Pattern After user interactions, verify operations were called: ```swift func testDecrementButton() async throws { let app = try presentView(configuration: .requireDevice()) app.decrementButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.decrementCalled) XCTAssertFalse(stubOps.incrementCalled) } ``` ### Orientation Setup Pattern Set device orientation in `setUp()` if needed: ```swift override func setUp() async throws { try await super.setUp() #if os(iOS) XCUIDevice.shared.orientation = .portrait #endif } ``` ## View Testing Checklist **All views:** - [ ] `.uiTestingIdentifier()` on all elements you want to test **Views WITH operations (interactive views):** - [ ] `@State private var repaintToggle = false` property - [ ] `.testDataTransporter(viewModelOps:repaintToggle:)` modifier - [ ] `toggleRepaint()` helper function - [ ] `toggleRepaint()` called after every operation invocation - [ ] `operations` stored from `viewModel.operations` in init **Views WITHOUT operations (display-only):** - [ ] No `repaintToggle` needed - [ ] No `.testDataTransporter()` needed - [ ] No `operations` property needed - [ ] `operations` stored from `viewModel.operations` in init ## Common Test Patterns ### Testing Async Operations ```swift func testAsyncOperation() async throws { let app = try presentView() app.loadButton.tap() // Wait for UI to update _ = app.waitForExistence(timeout: 3) let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.loadCalled) } ``` ### Testing Form Input ```swift func testFormInput() async throws { let app = try presentView() let emailField = app.emailTextField emailField.tap() emailField.typeTextAndWait("[REDACTED_SECRET]") app.submitButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.submitCalled) } ``` ### Testing Error States ```swift func testErrorDisplay() async throws { let app = try presentView(viewModel: .stub(hasError: true)) XCTAssertTrue(app.errorAlert.exists) XCTAssertEqual(app.errorMessage.text, "An error occurred") } ``` ## File Templates See [reference.md](reference.md) for complete file templates. ## Naming Conventions | Concept | Convention | Example | |---------|------------|---------| | Base test case | `{ProjectName}ViewModelViewTestCase` | `MyAppViewModelViewTestCase` | | UI test file | `{ViewName}UITests` | `TaskListViewUITests` | | Test method (UI state) | `test{Condition}` | `testButtonEnabled` | | Test method (operation) | `test{Action}` | `testSubmitButton` | | Element accessor | `{elementName}` | `submitButton`, `emailTextField` | | UI testing identifier | `{elementName}Identifier` or `{elementName}` | `"submitButton"`, `"emailTextField"` | ## See Also - [Architecture Patterns](../shared/architecture-patterns.md) - Mental models and patterns - [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) - Full FOSMVVM architecture - [fosmvvm-viewmodel-generator](../fosmvvm-viewmodel-generator/SKILL.md) - For creating ViewModels - [fosmvvm-swiftui-app-setup](../fosmvvm-swiftui-app-setup/SKILL.md) - For app test host setup - [reference.md](reference.md) - Complete file templates ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2026-01-23 | Initial skill for UI tests | | 1.1 | 2026-01-24 | Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths. |

path: reference.md
content: # FOSMVVM UI Tests Generator - Reference Templates

Complete file templates for generating UI tests for FOSMVVM ViewModelViews.

> **Conceptual context:** See [SKILL.md](SKILL.md) for when and why to use this skill.
> **Architecture context:** See [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) for full FOSMVVM understanding.

## Placeholders

| Placeholder | Replace With | Example |
|-------------|--------------|---------|
| `{ProjectName}` | Your project/app name | `MyApp`, `TaskManager` |
| `{ViewName}` | ViewModelView name (without "View" suffix) | `TaskList`, `Dashboard` |
| `{ViewModel}` | Full ViewModel type name | `TaskListViewModel` |
| `{Operations}` | Full ViewModelOperations type name | `TaskListViewModelOperations` |
| `{Feature}` | Feature/module grouping | `Tasks`, `Settings` |
| `{BundleId}` | App bundle identifier | `com.example.MyApp` |

---

# Template 1: Base Test Case Class

**One per project** - All UI tests inherit from this.

**Location:** `Tests/UITests/Support/{ProjectName}ViewModelViewTestCase.swift`

```swift
// {ProjectName}ViewModelViewTestCase.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import FOSTestingUI
import Foundation
import XCTest

class {ProjectName}ViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations>:
    ViewModelViewTestCase<VM, VMO>, @unchecked Sendable {

    @MainActor func presentView(
        configuration: TestConfiguration,
        viewModel: VM = .stub(),
        timeout: TimeInterval = 3
    ) throws -> XCUIApplication {
        try presentView(
            testConfiguration: configuration.toJSON(),
            viewModel: viewModel,
            timeout: timeout
        )
    }

    override func setUp() async throws {
        try await super.setUp(
            bundle: Bundle.main,
            resourceDirectoryName: "",
            appBundleIdentifier: "{BundleId}"
        )

        continueAfterFailure = false // Stop the test and move on
    }
}
```

---

# Template 2: XCUIElement Extensions

**One per project** - Helper methods for XCUIElement interactions.

**Location:** `Tests/UITests/Support/XCUIElement.swift`

```swift
// XCUIElement.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import XCTest

extension XCUIElement {
    /// Get the text value of the element
    var text: String? {
        value as? String
    }

    /// Type text and wait for it to appear
    func typeTextAndWait(_ string: String, timeout: TimeInterval = 2) {
        typeText(string)
        _ = wait(for: \.text, toEqual: string, timeout: timeout)
    }

    /// Tap, then type text and wait
    func selectTypeTextAndWait(_ string: String, timeout: TimeInterval = 2) {
        tap()
        typeTextAndWait(string, timeout: timeout)
    }

    /// Tap SwiftUI Menu elements
    ///
    /// SwiftUI Menu elements are often not marked as 'isHittable', so tap() will not work.
    /// This method taps the coordinate of the menu to skip the 'isHittable' test.
    func tapMenu() {
        if isHittable {
            tap()
        } else {
            coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
        }
    }
}
```

---

# ViewModelOperations: When to Use

Not all views need ViewModelOperations. The decision depends on whether the view has user interactions that trigger business logic.

**Views that NEED operations:**
- Forms with submit/cancel actions
- Views that call business logic or APIs
- Interactive views that trigger app state changes
- Views with user-initiated async operations

**Views that DON'T NEED operations:**
- Display-only cards or detail views
- Static content views
- Pure navigation containers
- Server-hosted views that just render data

**For views without operations**, create an empty operations file alongside your ViewModel.

See **Template 10: Empty ViewModelOperations File** for the complete pattern.

---

# Template 3: Display-Only View Test (No Operations)

**For views that don't have ViewModelOperations** - Display-only, no user interactions.

**Location:** `Tests/UITests/Views/{Feature}/{ViewName}UITests.swift`

```swift
// {ViewName}UITests.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import FOSTestingUI
import Foundation
import ViewModels
import XCTest

final class {ViewName}UITests: {ProjectName}ViewModelViewTestCase<
    {ViewModel},
    {ViewName}StubOps
>, @unchecked Sendable {
    // MARK: UI Tests

    func testDisplaysTitle() async throws {
        let app = try presentView(
            viewModel: .stub(title: "Test Title")
        )

        XCTAssertTrue(app.titleLabel.exists)
    }

    func testDisplaysContent() async throws {
        let app = try presentView(
            viewModel: .stub(content: "Test Content")
        )

        XCTAssertTrue(app.contentText.exists)
    }

    func testDisplaysImage() async throws {
        let app = try presentView()

        XCTAssertTrue(app.mainImage.exists)
    }

    // MARK: Setup

    override func setUp() async throws {
        try await super.setUp()

        #if os(iOS)
        XCUIDevice.shared.orientation = .portrait
        #endif
    }
}

// MARK: - XCUIApplication Extensions

private extension XCUIApplication {
    var titleLabel: XCUIElement {
        staticTexts["titleLabel"]
    }

    var contentText: XCUIElement {
        staticTexts["contentText"]
    }

    var mainImage: XCUIElement {
        images["mainImage"]
    }
}
```

---

# Template 4: Basic UI Test File (With Operations)

**One per ViewModelView** - Tests for a simple interactive view.

**Location:** `Tests/UITests/Views/{Feature}/{ViewName}UITests.swift`

```swift
// {ViewName}UITests.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import FOSTestingUI
import Foundation
import ViewModels
import XCTest

final class {ViewName}UITests: {ProjectName}ViewModelViewTestCase<
    {ViewModel},
    {Operations}
>, @unchecked Sendable {
    // MARK: UI Tests

    func testInitialState() async throws {
        let app = try presentView()

        XCTAssertTrue(app.mainContent.exists)
    }

    func testButtonEnabled() async throws {
        let app = try presentView(
            viewModel: .stub(enabled: true)
        )

        XCTAssertTrue(app.actionButton.isEnabled)
    }

    func testButtonDisabled() async throws {
        let app = try presentView(
            viewModel: .stub(enabled: false)
        )

        XCTAssertFalse(app.actionButton.isEnabled)
    }

    // MARK: Operation Tests

    func testActionButton() async throws {
        let app = try presentView(
            configuration: .default
        )

        app.actionButton.tap()

        let stubOps = try viewModelOperations()
        XCTAssertTrue(stubOps.actionCalled)
    }

    // MARK: Setup

    override func setUp() async throws {
        try await super.setUp()

        #if os(iOS)
        XCUIDevice.shared.orientation = .portrait
        #endif
    }
}

// MARK: - XCUIApplication Extensions

private extension XCUIApplication {
    var mainContent: XCUIElement {
        otherElements["mainContent"]
    }

    var actionButton: XCUIElement {
        buttons.element(matching: .button, identifier: "actionButton")
    }
}
```

---

# Template 5: Advanced UI Test File (With Operations)

**For views with multiple interactions** - Comprehensive test coverage.

**Location:** `Tests/UITests/Views/{Feature}/{ViewName}UITests.swift`

```swift
// {ViewName}UITests.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import FOSTestingUI
import Foundation
import ViewModels
import XCTest

final class {ViewName}UITests: {ProjectName}ViewModelViewTestCase<
    {ViewModel},
    {Operations}
>, @unchecked Sendable {
    // MARK: UI State Tests

    func testEmptyState() async throws {
        let app = try presentView(
            viewModel: .stub(items: [])
        )

        XCTAssertTrue(app.emptyStateMessage.exists)
        XCTAssertFalse(app.itemList.exists)
    }

    func testItemsDisplayed() async throws {
        let app = try presentView(
            viewModel: .stub(
                items: [
                    .stub(id: .init(), title: "Item 1"),
                    .stub(id: .init(), title: "Item 2")
                ]
            )
        )

        XCTAssertFalse(app.emptyStateMessage.exists)
        XCTAssertTrue(app.itemList.exists)
    }

    func testLoadingState() async throws {
        let app = try presentView(
            viewModel: .stub(isLoading: true)
        )

        XCTAssertTrue(app.loadingIndicator.exists)
    }

    // MARK: Interaction Tests

    func testSelectItem() async throws {
        let app = try presentView()

        XCTAssertFalse(app.detailButton.isEnabled)

        app.firstItemButton.tap()

        XCTAssertTrue(app.detailButton.isEnabled)
    }

    func testFormInput() async throws {
        let app = try presentView()

        app.nameTextField.tap()
        app.nameTextField.typeTextAndWait("Test Name")

        app.emailTextField.tap()
        app.emailTextField.typeTextAndWait("[REDACTED_SECRET]")

        app.submitButton.tap()

        let stubOps = try viewModelOperations()
        XCTAssertTrue(stubOps.submitCalled)
    }

    // MARK: Operation Tests

    func testRefresh() async throws {
        let app = try presentView(
            configuration: .requireAuth()
        )

        app.refreshButton.tap()

        let stubOps = try viewModelOperations()
        XCTAssertTrue(stubOps.refreshCalled)
    }

    func testDelete() async throws {
        let app = try presentView(
            configuration: .requireAuth()
        )

        app.firstItemButton.tap()
        app.deleteButton.tap()

        let stubOps = try viewModelOperations()
        XCTAssertTrue(stubOps.deleteCalled)
        XCTAssertFalse(stubOps.submitCalled)
    }

    func testCancel() async throws {
        let app = try presentView()

        app.cancelButton.tap()

        let stubOps = try viewModelOperations()
        XCTAssertTrue(stubOps.cancelCalled)
    }

    // MARK: Navigation Tests

    func testNavigationToDetail() async throws {
        let app = try presentView()

        app.firstItemButton.tap()
        app.viewDetailButton.tap()

        XCTAssertTrue(app.detailView.exists)
    }

    // MARK: Error Handling Tests

    func testErrorDisplayed() async throws {
        let app = try presentView(
            viewModel: .stub(hasError: true)
        )

        XCTAssertTrue(app.errorAlert.exists)
    }

    func testErrorDismissal() async throws {
        let app = try presentView(
            viewModel: .stub(hasError: true)
        )

        app.dismissErrorButton.tap()

        XCTAssertFalse(app.errorAlert.exists)
    }

    // MARK: Setup

    override class var runsForEachTargetApplicationUIConfiguration: Bool {
        false
    }

    override func setUp() async throws {
        try await super.setUp()

        #if os(iOS)
        XCUIDevice.shared.orientation = .portrait
        #endif
    }
}

// MARK: - XCUIApplication Extensions

private extension XCUIApplication {
    // Main Content
    var itemList: XCUIElement {
        tables["itemList"]
    }

    var emptyStateMessage: XCUIElement {
        staticTexts["emptyStateMessage"]
    }

    var loadingIndicator: XCUIElement {
        activityIndicators["loadingIndicator"]
    }

    // Buttons
    var refreshButton: XCUIElement {
        buttons.element(matching: .button, identifier: "refreshButton")
    }

    var submitButton: XCUIElement {
        buttons.element(matching: .button, identifier: "submitButton")
    }

    var cancelButton: XCUIElement {
        buttons.element(matching: .button, identifier: "cancelButton")
    }

    var deleteButton: XCUIElement {
        buttons.element(matching: .button, identifier: "deleteButton")
    }

    var detailButton: XCUIElement {
        buttons.element(matching: .button, identifier: "detailButton")
    }

    var viewDetailButton: XCUIElement {
        buttons.element(matching: .button, identifier: "viewDetailButton")
    }

    var dismissErrorButton: XCUIElement {
        buttons.element(matching: .button, identifier: "dismissErrorButton")
    }

    // Items
    var firstItemButton: XCUIElement {
        buttons.element(matching: .button, identifier: "itemButton").firstMatch
    }

    // Text Fields
    var nameTextField: XCUIElement {
        textFields["nameTextField"]
    }

    var emailTextField: XCUIElement {
        textFields["emailTextField"]
    }

    // Views
    var detailView: XCUIElement {
        otherElements["detailView"]
    }

    var errorAlert: XCUIElement {
        alerts["errorAlert"]
    }
}
```

---

# Template 6: Display-Only View (No Operations)

**For views that only display data** - No user interactions, no operations.

**Location:** `Sources/{ViewsTarget}/{Feature}/{ViewName}View.swift`

```swift
// {ViewName}View.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import Foundation
import SwiftUI
import ViewModels

public struct {ViewName}View: ViewModelView {
    private let viewModel: {ViewModel}

    public var body: some View {
        VStack {
            Text(viewModel.title)
                .font(.headline)
                .uiTestingIdentifier("titleLabel")

            Text(viewModel.description)
                .uiTestingIdentifier("descriptionText")

            if let imageURL = viewModel.imageURL {
                AsyncImage(url: imageURL)
                    .uiTestingIdentifier("mainImage")
            }
        }
        .padding()
    }

    public init(viewModel: {ViewModel}) {
        self.viewModel = viewModel
    }
}

#if DEBUG
#Preview {
    {ViewName}View.previewHost(
        bundle: MyAppResourceAccess.localizationBundle
    )
}
#endif
```

---

# Template 7: View with Test Infrastructure (With Operations)

**The view being tested** - Includes test support for operations.

**Location:** `Sources/{ViewsTarget}/{Feature}/{ViewName}View.swift`

```swift
// {ViewName}View.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import Foundation
import SwiftUI
import ViewModels

public struct {ViewName}View: ViewModelView {
    #if DEBUG
    @State private var repaintToggle = false
    #endif

    private let viewModel: {ViewModel}
    private let operations: {Operations}

    public var body: some View {
        VStack {
            Text(viewModel.title)
                .font(.headline)

            Button(action: performAction) {
                Text(viewModel.actionButtonLabel)
            }
            .uiTestingIdentifier("actionButton")

            Button(role: .cancel, action: cancel) {
                Text(viewModel.cancelButtonLabel)
            }
            .uiTestingIdentifier("cancelButton")
        }
        #if DEBUG
        .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle)
        #endif
    }

    public init(viewModel: {ViewModel}) {
        self.viewModel = viewModel
        self.operations = viewModel.operations
    }
}

private extension {ViewName}View {
    func performAction() {
        operations.performAction()
        toggleRepaint()
    }

    func cancel() {
        operations.cancel()
        toggleRepaint()
    }

    func toggleRepaint() {
        #if DEBUG
        repaintToggle.toggle()
        #endif
    }
}

#if DEBUG
#Preview {
    {ViewName}View.previewHost(
        bundle: MyAppResourceAccess.localizationBundle
    )
}
#endif
```

---

# Template 8: View with Async Operations

**For views with async operations** - Includes error handling.

**Location:** `Sources/{ViewsTarget}/{Feature}/{ViewName}View.swift`

```swift
// {ViewName}View.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import Foundation
import SwiftUI
import ViewModels

public struct {ViewName}View: ViewModelView {
    @State private var error: Error?
    @State private var isLoading = false

    #if DEBUG
    @State private var repaintToggle = false
    #endif

    private let viewModel: {ViewModel}
    private let operations: {Operations}

    public var body: some View {
        VStack {
            if isLoading {
                ProgressView()
                    .uiTestingIdentifier("loadingIndicator")
            } else {
                contentView
            }
        }
        .task(errorBinding: $error) {
            try await loadData()
        }
        .alert(
            errorBinding: $error,
            title: viewModel.errorTitle,
            message: viewModel.errorMessage,
            dismissButtonLabel: viewModel.dismissButtonLabel
        )
        #if DEBUG
        .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle)
        #endif
    }

    private var contentView: some View {
        VStack {
            Text(viewModel.title)

            Button(errorBinding: $error, asyncAction: submit) {
                Text(viewModel.submitButtonLabel)
            }
            .uiTestingIdentifier("submitButton")
        }
    }

    public init(viewModel: {ViewModel}) {
        self.viewModel = viewModel
        self.operations = viewModel.operations
    }
}

private extension {ViewName}View {
    func loadData() async throws {
        isLoading = true
        try await operations.loadData()
        isLoading = false
        toggleRepaint()
    }

    @Sendable func submit() async throws {
        try await operations.submit()
        toggleRepaint()
    }

    func toggleRepaint() {
        #if DEBUG
        repaintToggle.toggle()
        #endif
    }
}

#if DEBUG
#Preview {
    {ViewName}View.previewHost(
        bundle: MyAppResourceAccess.localizationBundle
    )
}
#endif
```

---

# Template 9: View with Form and List

**Complex view example** - Form input and list display.

**Location:** `Sources/{ViewsTarget}/{Feature}/{ViewName}View.swift`

```swift
// {ViewName}View.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import Foundation
import SwiftUI
import ViewModels

public struct {ViewName}View: ViewModelView {
    @State private var items: [ItemViewModel] = []
    @State private var selectedId: ModelIdType?
    @State private var error: Error?

    #if DEBUG
    @State private var repaintToggle = false
    #endif

    private let viewModel: {ViewModel}
    private let operations: {Operations}

    public var body: some View {
        VStack {
            Text(viewModel.title)
                .font(.headline)

            if items.isEmpty {
                Text(viewModel.emptyStateMessage)
                    .uiTestingIdentifier("emptyStateMessage")
            } else {
                ScrollView {
                    VStack {
                        ForEach(items) { item in
                            Button { selectItem(item.id) } label: {
                                HStack {
                                    Text(item.title)
                                    if item.id == selectedId {
                                        Spacer()
                                        Image(systemName: "checkmark")
                                    }
                                }
                            }
                            .uiTestingIdentifier("itemButton")
                        }
                    }
                }
                .uiTestingIdentifier("itemList")
            }

            Spacer()

            HStack {
                Button(role: .cancel, action: cancel) {
                    Text(viewModel.cancelButtonLabel)
                }
                .uiTestingIdentifier("cancelButton")

                Spacer()

                Button(errorBinding: $error, asyncAction: submit) {
                    Text(viewModel.submitButtonLabel)
                }
                .buttonStyle(PrimaryButtonStyle())
                .uiTestingIdentifier("submitButton")
                .disabled(selectedId == nil)
            }
        }
        .task(errorBinding: $error) {
            try await loadItems()
        }
        .alert(
            errorBinding: $error,
            title: viewModel.errorTitle,
            message: viewModel.errorMessage,
            dismissButtonLabel: viewModel.dismissButtonLabel
        )
        #if DEBUG
        .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle)
        #endif
    }

    public init(viewModel: {ViewModel}) {
        self.viewModel = viewModel
        self.operations = viewModel.operations
    }
}

private extension {ViewName}View {
    func loadItems() async throws {
        let stream = operations.loadItems()
        toggleRepaint()

        for try await item in stream {
            items.append(item)
        }
    }

    func selectItem(_ id: ModelIdType) {
        if selectedId == id {
            selectedId = nil
        } else {
            selectedId = id
        }
        toggleRepaint()
    }

    @Sendable func submit() async throws {
        guard let selectedId else { return }
        try await operations.submit(itemId: selectedId)
        toggleRepaint()
    }

    func cancel() {
        operations.cancel()
        toggleRepaint()
    }

    func toggleRepaint() {
        #if DEBUG
        repaintToggle.toggle()
        #endif
    }
}

#if DEBUG
#Preview {
    {ViewName}View.previewHost(
        bundle: MyAppResourceAccess.localizationBundle
    )
}
#endif
```

---

# Template 10: ViewModelOperations File (With Operations)

**For interactive views** - Operations protocol, real implementation, and stub for testing.

**Structure:**
- **Protocol** - Defines the operations interface
- **Real implementation** (struct) - Does the actual work (API calls, business logic)
- **Stub implementation** (class) - Tracks calls for testing, returns mock data

**Location:** `Sources/{ViewModelsTarget}/{Feature}/{ViewName}ViewModelOperations.swift`

```swift
// {ViewName}ViewModelOperations.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSFoundation
import FOSMVVM
import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

#if canImport(SwiftUI)

// MARK: - Operations Protocol

public protocol {ViewName}ViewModelOperations: ViewModelOperations {
    func performAction() async throws
    func cancel()
    func loadData() -> AsyncThrowingStream<ItemData, Error>
}

// MARK: - Real Implementation

public struct {ViewName}Ops: {ViewName}ViewModelOperations {
    public func performAction() async throws {
        // Real implementation that calls APIs, updates state, etc.
    }

    public func cancel() {
        // Real cancellation logic
    }

    public func loadData() -> AsyncThrowingStream<ItemData, Error> {
        AsyncThrowingStream { continuation in
            Task {
                do {
                    // Real data loading logic
                    let data = try await fetchFromAPI()
                    continuation.yield(data)
                    continuation.finish()
                } catch {
                    continuation.finish(throwing: error)
                }
            }
        }
    }

    public init() {}
}

#endif

// MARK: - Stubbing and Testing

#if canImport(SwiftUI)
public final class {ViewName}StubOps: {ViewName}ViewModelOperations, @unchecked Sendable {
    // Track which operations were called
    public private(set) var performActionCalled: Bool
    public private(set) var cancelCalled: Bool
    public private(set) var loadDataCalled: Bool

    public func performAction() async throws {
        performActionCalled = true
    }

    public func cancel() {
        cancelCalled = true
    }

    public func loadData() -> AsyncThrowingStream<ItemData, Error> {
        loadDataCalled = true
        return AsyncThrowingStream { continuation in
            // Return stub data for testing
            continuation.yield(.stub())
            continuation.finish()
        }
    }

    public init() {
        self.performActionCalled = false
        self.cancelCalled = false
        self.loadDataCalled = false
    }
}
#endif
```

---

# Template 11: Empty ViewModelOperations File (Display-Only)

**For display-only views** - Empty protocol and minimal stub.

**Location:** `Sources/{ViewModelsTarget}/{Feature}/{ViewName}ViewModelOperations.swift`

```swift
// {ViewName}ViewModelOperations.swift
//
// Copyright (c) 2026 Your Organization. All rights reserved.
// License: Your License

import FOSMVVM
import Foundation

// MARK: - Operations Protocol

public protocol {ViewName}ViewModelOperations: ViewModelOperations {}

// MARK: - Stubbing and Testing

#if canImport(SwiftUI)
public final class {ViewName}StubOps: {ViewName}ViewModelOperations, @unchecked Sendable {
    public init() {}
}
#endif
```

---

# Quick Reference

## Test Infrastructure Checklist

**All Views:**
- [ ] `.uiTestingIdentifier()` on ALL elements you want to test

**Views WITH Operations (interactive views):**
- [ ] `#if DEBUG` block with `@State private var repaintToggle = false`
- [ ] `.testDataTransporter(viewModelOps:repaintToggle:)` modifier on body
- [ ] `operations` property stored from `viewModel.operations`
- [ ] `toggleRepaint()` helper function
- [ ] `toggleRepaint()` called after EVERY operation invocation

**Views WITHOUT Operations (display-only):**
- [ ] No `repaintToggle` needed
- [ ] No `.testDataTransporter()` needed
- [ ] No `operations` property needed
- [ ] No `toggleRepaint()` needed

## UI Testing Identifier Conventions

| Element Type | Accessor Pattern | Identifier Example |
|--------------|------------------|-------------------|
| Button | `buttons.element(matching: .button, identifier: "...")` | `"submitButton"` |
| Text Field | `textFields["..."]` | `"emailTextField"` |
| Static Text | `staticTexts["..."]` | `"errorMessage"` |
| Activity Indicator | `activityIndicators["..."]` | `"loadingIndicator"` |
| Table | `tables["..."]` | `"itemList"` |
| Alert | `alerts["..."]` | `"errorAlert"` |
| Other Elements | `otherElements["..."]` | `"mainContent"` |

## Common XCUIElement Patterns

```swift
// First match in a list
var firstItem: XCUIElement {
    buttons.element(matching: .button, identifier: "itemButton").firstMatch
}

// Wait for existence
XCTAssertTrue(app.someElement.waitForExistence(timeout: 3))

// Check enabled state
XCTAssertTrue(app.submitButton.isEnabled)
XCTAssertFalse(app.submitButton.isEnabled)

// Check existence
XCTAssertTrue(app.errorAlert.exists)
XCTAssertFalse(app.errorAlert.exists)

// Get text value
XCTAssertEqual(app.label.text, "Expected Text")

// Tap menu (for non-hittable SwiftUI menus)
app.menuButton.tapMenu()
```

## Operation Verification Pattern

```swift
func testSomeOperation() async throws {
    let app = try presentView(configuration: .default)

    // Perform UI interaction
    app.actionButton.tap()

    // Verify operation was called
    let stubOps = try viewModelOperations()
    XCTAssertTrue(stubOps.actionCalled)
    XCTAssertFalse(stubOps.otherActionCalled)
}
```

## Test Configuration Pattern

```swift
// Basic presentation
let app = try presentView()

// With custom ViewModel
let app = try presentView(
    viewModel: .stub(enabled: false, items: [])
)

// With test configuration
let app = try presentView(
    configuration: .requireAuth(userId: "123")
)

// Both ViewModel and configuration
let app = try presentView(
    configuration: .requireDevice(),
    viewModel: .stub(connected: true)
)
```

---

# Checklists

## Base Setup (Once Per Project):
- [ ] Base test case class created
- [ ] XCUIElement extensions created
- [ ] App bundle identifier configured
- [ ] Test target created in Xcode

## Per View Test:
- [ ] Test file created with correct generic parameters
- [ ] UI state tests added
- [ ] Operation tests added
- [ ] XCUIApplication extension with element accessors
- [ ] setUp() method configured if needed
- [ ] All interactive elements have corresponding accessors

## View Preparation:
- [ ] `@State private var repaintToggle` property
- [ ] `.testDataTransporter()` modifier
- [ ] `operations` stored from `viewModel.operations`
- [ ] `toggleRepaint()` helper function
- [ ] `toggleRepaint()` called after operations
- [ ] All interactive elements have `.uiTestingIdentifier()`

---

# Common Patterns

## Pattern 1: Testing Button States

```swift
func testButtonDisabledInitially() async throws {
    let app = try presentView()
    XCTAssertFalse(app.submitButton.isEnabled)
}

func testButtonEnabledAfterInput() async throws {
    let app = try presentView()

    app.nameField.typeTextAndWait("Test")

    XCTAssertTrue(app.submitButton.isEnabled)
}
```

## Pattern 2: Testing Async Operations

```swift
func testLoadData() async throws {
    let app = try presentView()

    // Wait for loading to complete
    XCTAssertTrue(app.waitForExistence(timeout: 3))

    let stubOps = try viewModelOperations()
    XCTAssertTrue(stubOps.loadDataCalled)
}
```

## Pattern 3: Testing Error States

```swift
func testErrorHandling() async throws {
    let app = try presentView(
        viewModel: .stub(hasError: true)
    )

    XCTAssertTrue(app.errorAlert.exists)
    app.dismissButton.tap()
    XCTAssertFalse(app.errorAlert.exists)
}
```

## Pattern 4: Testing Navigation

```swift
func testNavigation() async throws {
    let app = try presentView()

    app.itemRow.tap()
    XCTAssertTrue(app.detailView.waitForExistence(timeout: 2))

    app.backButton.tap()
    XCTAssertTrue(app.listView.waitForExistence(timeout: 2))
}
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)