{
  "schemaVersion": "1.0",
  "item": {
    "slug": "fosmvvm-ui-tests-generator",
    "name": "FOSMVVM UI Tests Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-ui-tests-generator",
    "canonicalUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-ui-tests-generator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/fosmvvm-ui-tests-generator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fosmvvm-ui-tests-generator",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "reference.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/fosmvvm-ui-tests-generator"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/fosmvvm-ui-tests-generator",
    "agentPageUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "FOSMVVM UI Tests Generator",
        "body": "Generate comprehensive UI tests for ViewModelViews in FOSMVVM applications."
      },
      {
        "title": "Conceptual Foundation",
        "body": "For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nUI testing in FOSMVVM follows a specific pattern that leverages:\n\nFOSTestingUI framework for test infrastructure\nViewModelOperations for verifying business logic was invoked\nAccessibility identifiers for finding UI elements\nTest data transporter for passing operation stubs to the app\n\n┌─────────────────────────────────────────────────────────────┐\n│                    UI Test Architecture                      │\n├─────────────────────────────────────────────────────────────┤\n│                                                              │\n│  Test File (XCTest)                 App Under Test          │\n│  ┌──────────────────┐              ┌──────────────────┐     │\n│  │ MyViewUITests    │              │ MyView           │     │\n│  │                  │              │                  │     │\n│  │ presentView() ───┼─────────────►│ Show view with   │     │\n│  │   with stub VM   │              │   stubbed data   │     │\n│  │                  │              │                  │     │\n│  │ Interact via ────┼─────────────►│ UI elements with │     │\n│  │   identifiers    │              │   .uiTestingId   │     │\n│  │                  │              │                  │     │\n│  │ Assert on UI     │              │ .testData────────┼──┐  │\n│  │   state          │              │   Transporter    │  │  │\n│  │                  │              └──────────────────┘  │  │\n│  │ viewModelOps() ◄─┼─────────────────────────────────────┘  │\n│  │   verify calls   │              Stub Operations          │\n│  └──────────────────┘                                        │\n│                                                              │\n└─────────────────────────────────────────────────────────────┘"
      },
      {
        "title": "1. Base Test Case Class",
        "body": "Every project should have a base test case that inherits from ViewModelViewTestCase:\n\nclass MyAppViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations>:\n    ViewModelViewTestCase<VM, VMO>, @unchecked Sendable {\n\n    @MainActor func presentView(\n        configuration: TestConfiguration,\n        viewModel: VM = .stub(),\n        timeout: TimeInterval = 3\n    ) throws -> XCUIApplication {\n        try presentView(\n            testConfiguration: configuration.toJSON(),\n            viewModel: viewModel,\n            timeout: timeout\n        )\n    }\n\n    override func setUp() async throws {\n        try await super.setUp(\n            bundle: Bundle.main,\n            resourceDirectoryName: \"\",\n            appBundleIdentifier: \"com.example.MyApp\"\n        )\n\n        continueAfterFailure = false\n    }\n}\n\nKey points:\n\nGeneric over ViewModel and ViewModelOperations\nWraps FOSTestingUI's presentView() with project-specific configuration\nSets up bundle and app bundle identifier\ncontinueAfterFailure = false stops tests immediately on failure"
      },
      {
        "title": "2. Individual UI Test Files",
        "body": "Each ViewModelView gets a corresponding UI test file.\n\nFor views WITH operations:\n\nfinal class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewOps> {\n    // UI Tests - verify UI state\n    func testButtonEnabled() async throws {\n        let app = try presentView(viewModel: .stub(enabled: true))\n        XCTAssertTrue(app.myButton.isEnabled)\n    }\n\n    // Operation Tests - verify operations were called\n    func testButtonTap() async throws {\n        let app = try presentView(configuration: .requireSomeState())\n        app.myButton.tap()\n\n        let stubOps = try viewModelOperations()\n        XCTAssertTrue(stubOps.myOperationCalled)\n    }\n}\n\nprivate extension XCUIApplication {\n    var myButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"myButtonIdentifier\")\n    }\n}\n\nFor views WITHOUT operations (display-only):\n\nUse an empty stub operations protocol:\n\n// In your test file\nprotocol MyViewStubOps: ViewModelOperations {}\nstruct MyViewStubOpsImpl: MyViewStubOps {}\n\nfinal class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewStubOpsImpl> {\n    // UI Tests only - no operation verification\n    func testDisplaysCorrectly() async throws {\n        let app = try presentView(viewModel: .stub(title: \"Test\"))\n        XCTAssertTrue(app.titleLabel.exists)\n    }\n}\n\nWhen to use each:\n\nWith operations: Interactive views that perform actions (forms, buttons that call APIs, etc.)\nWithout operations: Display-only views (cards, detail views, static content)"
      },
      {
        "title": "3. XCUIElement Helper Extensions",
        "body": "Common helpers for interacting with UI elements:\n\nextension XCUIElement {\n    var text: String? {\n        value as? String\n    }\n\n    func typeTextAndWait(_ string: String, timeout: TimeInterval = 2) {\n        typeText(string)\n        _ = wait(for: \\.text, toEqual: string, timeout: timeout)\n    }\n\n    func tapMenu() {\n        if isHittable {\n            tap()\n        } else {\n            coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()\n        }\n    }\n}"
      },
      {
        "title": "4. View Requirements",
        "body": "For views WITH operations:\n\npublic struct MyView: ViewModelView {\n    #if DEBUG\n    @State private var repaintToggle = false\n    #endif\n\n    private let viewModel: MyViewModel\n    private let operations: MyViewModelOperations\n\n    public var body: some View {\n        Button(action: doSomething) {\n            Text(viewModel.buttonLabel)\n        }\n        .uiTestingIdentifier(\"myButtonIdentifier\")\n        #if DEBUG\n        .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle)\n        #endif\n    }\n\n    public init(viewModel: MyViewModel) {\n        self.viewModel = viewModel\n        self.operations = viewModel.operations\n    }\n\n    private func doSomething() {\n        operations.doSomething()\n        toggleRepaint()\n    }\n\n    private func toggleRepaint() {\n        #if DEBUG\n        repaintToggle.toggle()\n        #endif\n    }\n}\n\nFor views WITHOUT operations (display-only):\n\npublic struct MyView: ViewModelView {\n    private let viewModel: MyViewModel\n\n    public var body: some View {\n        VStack {\n            Text(viewModel.title)\n            Text(viewModel.description)\n        }\n        .uiTestingIdentifier(\"mainContent\")\n    }\n\n    public init(viewModel: MyViewModel) {\n        self.viewModel = viewModel\n    }\n}\n\nCritical patterns (for views WITH operations):\n\n@State private var repaintToggle = false for triggering test data transport\n.testDataTransporter(viewModelOps:repaintToggle:) modifier in DEBUG\ntoggleRepaint() called after every operation invocation\noperations stored as property from viewModel.operations\n\nDisplay-only views:\n\nNo repaintToggle needed\nNo .testDataTransporter() modifier needed\nJust add .uiTestingIdentifier() to elements you want to test"
      },
      {
        "title": "ViewModelOperations: Optional",
        "body": "Not all views need ViewModelOperations:\n\nViews that NEED operations:\n\nForms with submit/cancel actions\nViews that call business logic or APIs\nInteractive views that trigger app state changes\nViews with user-initiated async operations\n\nViews that DON'T NEED operations:\n\nDisplay-only cards or detail views\nStatic content views\nPure navigation containers\nServer-hosted views that just render data\n\nFor views without operations:\n\nCreate an empty operations file alongside your ViewModel:\n\n// MyDisplayViewModelOperations.swift\nimport FOSMVVM\nimport Foundation\n\npublic protocol MyDisplayViewModelOperations: ViewModelOperations {}\n\n#if canImport(SwiftUI)\npublic final class MyDisplayViewStubOps: MyDisplayViewModelOperations, @unchecked Sendable {\n    public init() {}\n}\n#endif\n\nThen use it in tests:\n\nfinal class MyDisplayViewUITests: MyAppViewModelViewTestCase<\n    MyDisplayViewModel,\n    MyDisplayViewStubOps\n> {\n    // Only test UI state, no operation verification\n}\n\nThe view itself doesn't need:\n\nrepaintToggle state\n.testDataTransporter() modifier\noperations property\ntoggleRepaint() function\n\nJust add .uiTestingIdentifier() to elements you want to verify."
      },
      {
        "title": "UI State Tests",
        "body": "Verify that the UI displays correctly based on ViewModel state:\n\nfunc testButtonDisabledWhenNotReady() async throws {\n    let app = try presentView(viewModel: .stub(ready: false))\n    XCTAssertFalse(app.submitButton.isEnabled)\n}\n\nfunc testButtonEnabledWhenReady() async throws {\n    let app = try presentView(viewModel: .stub(ready: true))\n    XCTAssertTrue(app.submitButton.isEnabled)\n}"
      },
      {
        "title": "Operation Tests",
        "body": "Verify that user interactions invoke the correct operations:\n\nfunc testSubmitButtonInvokesOperation() async throws {\n    let app = try presentView(configuration: .requireAuth())\n    app.submitButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.submitCalled)\n    XCTAssertFalse(stubOps.cancelCalled)\n}"
      },
      {
        "title": "Navigation Tests",
        "body": "Verify navigation flows work correctly:\n\nfunc testNavigationToDetailView() async throws {\n    let app = try presentView()\n    app.itemRow.tap()\n\n    XCTAssertTrue(app.detailView.exists)\n}"
      },
      {
        "title": "When to Use This Skill",
        "body": "Adding UI tests for a new ViewModelView\nSetting up UI test infrastructure for a FOSMVVM project\nFollowing an implementation plan that requires test coverage\nValidating user interaction flows"
      },
      {
        "title": "Initial Setup (once per project)",
        "body": "FileLocationPurpose{ProjectName}ViewModelViewTestCase.swiftTests/UITests/Support/Base test case for all UI testsXCUIElement.swiftTests/UITests/Support/Helper extensions for XCUIElement"
      },
      {
        "title": "Per ViewModelView",
        "body": "FileLocationPurpose{ViewName}ViewModelOperations.swiftSources/{ViewModelsTarget}/{Feature}/Operations protocol and stub (if view has interactions){ViewName}UITests.swiftTests/UITests/Views/{Feature}/UI tests for the view\n\nNote: Views without user interactions use an empty operations file with just the protocol and minimal stub."
      },
      {
        "title": "Project Structure Configuration",
        "body": "PlaceholderDescriptionExample{ProjectName}Your project/app nameMyApp, TaskManager{ViewName}The ViewModelView name (without \"View\" suffix)TaskList, Dashboard{Feature}Feature/module groupingTasks, Settings"
      },
      {
        "title": "How to Use This Skill",
        "body": "Invocation:\n/fosmvvm-ui-tests-generator\n\nPrerequisites:\n\nView and ViewModel structure understood from conversation context\nViewModelOperations type identified (or confirmed as display-only)\nInteractive elements and user flows discussed\n\nWorkflow integration:\nThis 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."
      },
      {
        "title": "Pattern Implementation",
        "body": "This skill references conversation context to determine test structure:"
      },
      {
        "title": "Test Type Detection",
        "body": "From conversation context, the skill identifies:\n\nFirst test vs additional test (whether base test infrastructure exists)\nViewModel type (from prior discussion or View implementation)\nViewModelOperations type (from View implementation or context)\nInteractive vs display-only (whether operations need verification)"
      },
      {
        "title": "View Analysis",
        "body": "From requirements already in context:\n\nInteractive elements (buttons, fields, controls requiring test coverage)\nUser flows (navigation paths, form submission, drag-and-drop)\nState variations (enabled/disabled, visible/hidden, error states)\nOperation triggers (which UI actions invoke which operations)"
      },
      {
        "title": "Infrastructure Planning",
        "body": "Based on project state:\n\nBase test case (create if first test, reuse if exists)\nXCUIElement extensions (helper methods for common interactions)\nApp bundle identifier (for launching test host)"
      },
      {
        "title": "Test File Generation",
        "body": "For the specific view:\n\nTest class inheriting from base test case\nUI state tests (verify display based on ViewModel)\nOperation tests (verify user interactions invoke operations)\nXCUIApplication extension with element accessors"
      },
      {
        "title": "View Requirements",
        "body": "Ensure test identifiers and data transport:\n\n.uiTestingIdentifier() on all interactive elements\n@State private var repaintToggle (if has operations)\n.testDataTransporter() modifier (if has operations)\ntoggleRepaint() calls after operations (if has operations)"
      },
      {
        "title": "Context Sources",
        "body": "Skill references information from:\n\nPrior conversation: View requirements, user flows discussed\nView implementation: If Claude has read View code into context\nViewModelOperations: From codebase or discussion"
      },
      {
        "title": "Test Configuration Pattern",
        "body": "Use TestConfiguration for tests that need specific app state:\n\nfunc testWithSpecificState() async throws {\n    let app = try presentView(\n        configuration: .requireAuth(userId: \"123\")\n    )\n    // Test with authenticated state\n}"
      },
      {
        "title": "Element Accessor Pattern",
        "body": "Define element accessors in a private extension:\n\nprivate extension XCUIApplication {\n    var submitButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"submitButton\")\n    }\n\n    var cancelButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"cancelButton\")\n    }\n\n    var firstItem: XCUIElement {\n        buttons.element(matching: .button, identifier: \"itemButton\").firstMatch\n    }\n}"
      },
      {
        "title": "Operation Verification Pattern",
        "body": "After user interactions, verify operations were called:\n\nfunc testDecrementButton() async throws {\n    let app = try presentView(configuration: .requireDevice())\n    app.decrementButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.decrementCalled)\n    XCTAssertFalse(stubOps.incrementCalled)\n}"
      },
      {
        "title": "Orientation Setup Pattern",
        "body": "Set device orientation in setUp() if needed:\n\noverride func setUp() async throws {\n    try await super.setUp()\n\n    #if os(iOS)\n    XCUIDevice.shared.orientation = .portrait\n    #endif\n}"
      },
      {
        "title": "View Testing Checklist",
        "body": "All views:\n\n.uiTestingIdentifier() on all elements you want to test\n\nViews WITH operations (interactive views):\n\n@State private var repaintToggle = false property\n .testDataTransporter(viewModelOps:repaintToggle:) modifier\n toggleRepaint() helper function\n toggleRepaint() called after every operation invocation\n operations stored from viewModel.operations in init\n\nViews WITHOUT operations (display-only):\n\nNo repaintToggle needed\n No .testDataTransporter() needed\n No operations property needed\n operations stored from viewModel.operations in init"
      },
      {
        "title": "Testing Async Operations",
        "body": "func testAsyncOperation() async throws {\n    let app = try presentView()\n    app.loadButton.tap()\n\n    // Wait for UI to update\n    _ = app.waitForExistence(timeout: 3)\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.loadCalled)\n}"
      },
      {
        "title": "Testing Form Input",
        "body": "func testFormInput() async throws {\n    let app = try presentView()\n\n    let emailField = app.emailTextField\n    emailField.tap()\n    emailField.typeTextAndWait(\"user@example.com\")\n\n    app.submitButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.submitCalled)\n}"
      },
      {
        "title": "Testing Error States",
        "body": "func testErrorDisplay() async throws {\n    let app = try presentView(viewModel: .stub(hasError: true))\n\n    XCTAssertTrue(app.errorAlert.exists)\n    XCTAssertEqual(app.errorMessage.text, \"An error occurred\")\n}"
      },
      {
        "title": "File Templates",
        "body": "See reference.md for complete file templates."
      },
      {
        "title": "Naming Conventions",
        "body": "ConceptConventionExampleBase test case{ProjectName}ViewModelViewTestCaseMyAppViewModelViewTestCaseUI test file{ViewName}UITestsTaskListViewUITestsTest method (UI state)test{Condition}testButtonEnabledTest method (operation)test{Action}testSubmitButtonElement accessor{elementName}submitButton, emailTextFieldUI testing identifier{elementName}Identifier or {elementName}\"submitButton\", \"emailTextField\""
      },
      {
        "title": "See Also",
        "body": "Architecture Patterns - Mental models and patterns\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-viewmodel-generator - For creating ViewModels\nfosmvvm-swiftui-app-setup - For app test host setup\nreference.md - Complete file templates"
      },
      {
        "title": "Version History",
        "body": "VersionDateChanges1.02026-01-23Initial skill for UI tests1.12026-01-24Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths."
      }
    ],
    "body": "FOSMVVM UI Tests Generator\n\nGenerate comprehensive UI tests for ViewModelViews in FOSMVVM applications.\n\nConceptual Foundation\n\nFor full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nUI testing in FOSMVVM follows a specific pattern that leverages:\n\nFOSTestingUI framework for test infrastructure\nViewModelOperations for verifying business logic was invoked\nAccessibility identifiers for finding UI elements\nTest data transporter for passing operation stubs to the app\n┌─────────────────────────────────────────────────────────────┐\n│                    UI Test Architecture                      │\n├─────────────────────────────────────────────────────────────┤\n│                                                              │\n│  Test File (XCTest)                 App Under Test          │\n│  ┌──────────────────┐              ┌──────────────────┐     │\n│  │ MyViewUITests    │              │ MyView           │     │\n│  │                  │              │                  │     │\n│  │ presentView() ───┼─────────────►│ Show view with   │     │\n│  │   with stub VM   │              │   stubbed data   │     │\n│  │                  │              │                  │     │\n│  │ Interact via ────┼─────────────►│ UI elements with │     │\n│  │   identifiers    │              │   .uiTestingId   │     │\n│  │                  │              │                  │     │\n│  │ Assert on UI     │              │ .testData────────┼──┐  │\n│  │   state          │              │   Transporter    │  │  │\n│  │                  │              └──────────────────┘  │  │\n│  │ viewModelOps() ◄─┼─────────────────────────────────────┘  │\n│  │   verify calls   │              Stub Operations          │\n│  └──────────────────┘                                        │\n│                                                              │\n└─────────────────────────────────────────────────────────────┘\n\nCore Components\n1. Base Test Case Class\n\nEvery project should have a base test case that inherits from ViewModelViewTestCase:\n\nclass MyAppViewModelViewTestCase<VM: ViewModel, VMO: ViewModelOperations>:\n    ViewModelViewTestCase<VM, VMO>, @unchecked Sendable {\n\n    @MainActor func presentView(\n        configuration: TestConfiguration,\n        viewModel: VM = .stub(),\n        timeout: TimeInterval = 3\n    ) throws -> XCUIApplication {\n        try presentView(\n            testConfiguration: configuration.toJSON(),\n            viewModel: viewModel,\n            timeout: timeout\n        )\n    }\n\n    override func setUp() async throws {\n        try await super.setUp(\n            bundle: Bundle.main,\n            resourceDirectoryName: \"\",\n            appBundleIdentifier: \"com.example.MyApp\"\n        )\n\n        continueAfterFailure = false\n    }\n}\n\n\nKey points:\n\nGeneric over ViewModel and ViewModelOperations\nWraps FOSTestingUI's presentView() with project-specific configuration\nSets up bundle and app bundle identifier\ncontinueAfterFailure = false stops tests immediately on failure\n2. Individual UI Test Files\n\nEach ViewModelView gets a corresponding UI test file.\n\nFor views WITH operations:\n\nfinal class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewOps> {\n    // UI Tests - verify UI state\n    func testButtonEnabled() async throws {\n        let app = try presentView(viewModel: .stub(enabled: true))\n        XCTAssertTrue(app.myButton.isEnabled)\n    }\n\n    // Operation Tests - verify operations were called\n    func testButtonTap() async throws {\n        let app = try presentView(configuration: .requireSomeState())\n        app.myButton.tap()\n\n        let stubOps = try viewModelOperations()\n        XCTAssertTrue(stubOps.myOperationCalled)\n    }\n}\n\nprivate extension XCUIApplication {\n    var myButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"myButtonIdentifier\")\n    }\n}\n\n\nFor views WITHOUT operations (display-only):\n\nUse an empty stub operations protocol:\n\n// In your test file\nprotocol MyViewStubOps: ViewModelOperations {}\nstruct MyViewStubOpsImpl: MyViewStubOps {}\n\nfinal class MyViewUITests: MyAppViewModelViewTestCase<MyViewModel, MyViewStubOpsImpl> {\n    // UI Tests only - no operation verification\n    func testDisplaysCorrectly() async throws {\n        let app = try presentView(viewModel: .stub(title: \"Test\"))\n        XCTAssertTrue(app.titleLabel.exists)\n    }\n}\n\n\nWhen to use each:\n\nWith operations: Interactive views that perform actions (forms, buttons that call APIs, etc.)\nWithout operations: Display-only views (cards, detail views, static content)\n3. XCUIElement Helper Extensions\n\nCommon helpers for interacting with UI elements:\n\nextension XCUIElement {\n    var text: String? {\n        value as? String\n    }\n\n    func typeTextAndWait(_ string: String, timeout: TimeInterval = 2) {\n        typeText(string)\n        _ = wait(for: \\.text, toEqual: string, timeout: timeout)\n    }\n\n    func tapMenu() {\n        if isHittable {\n            tap()\n        } else {\n            coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()\n        }\n    }\n}\n\n4. View Requirements\n\nFor views WITH operations:\n\npublic struct MyView: ViewModelView {\n    #if DEBUG\n    @State private var repaintToggle = false\n    #endif\n\n    private let viewModel: MyViewModel\n    private let operations: MyViewModelOperations\n\n    public var body: some View {\n        Button(action: doSomething) {\n            Text(viewModel.buttonLabel)\n        }\n        .uiTestingIdentifier(\"myButtonIdentifier\")\n        #if DEBUG\n        .testDataTransporter(viewModelOps: operations, repaintToggle: $repaintToggle)\n        #endif\n    }\n\n    public init(viewModel: MyViewModel) {\n        self.viewModel = viewModel\n        self.operations = viewModel.operations\n    }\n\n    private func doSomething() {\n        operations.doSomething()\n        toggleRepaint()\n    }\n\n    private func toggleRepaint() {\n        #if DEBUG\n        repaintToggle.toggle()\n        #endif\n    }\n}\n\n\nFor views WITHOUT operations (display-only):\n\npublic struct MyView: ViewModelView {\n    private let viewModel: MyViewModel\n\n    public var body: some View {\n        VStack {\n            Text(viewModel.title)\n            Text(viewModel.description)\n        }\n        .uiTestingIdentifier(\"mainContent\")\n    }\n\n    public init(viewModel: MyViewModel) {\n        self.viewModel = viewModel\n    }\n}\n\n\nCritical patterns (for views WITH operations):\n\n@State private var repaintToggle = false for triggering test data transport\n.testDataTransporter(viewModelOps:repaintToggle:) modifier in DEBUG\ntoggleRepaint() called after every operation invocation\noperations stored as property from viewModel.operations\n\nDisplay-only views:\n\nNo repaintToggle needed\nNo .testDataTransporter() modifier needed\nJust add .uiTestingIdentifier() to elements you want to test\nViewModelOperations: Optional\n\nNot all views need ViewModelOperations:\n\nViews that NEED operations:\n\nForms with submit/cancel actions\nViews that call business logic or APIs\nInteractive views that trigger app state changes\nViews with user-initiated async operations\n\nViews that DON'T NEED operations:\n\nDisplay-only cards or detail views\nStatic content views\nPure navigation containers\nServer-hosted views that just render data\n\nFor views without operations:\n\nCreate an empty operations file alongside your ViewModel:\n\n// MyDisplayViewModelOperations.swift\nimport FOSMVVM\nimport Foundation\n\npublic protocol MyDisplayViewModelOperations: ViewModelOperations {}\n\n#if canImport(SwiftUI)\npublic final class MyDisplayViewStubOps: MyDisplayViewModelOperations, @unchecked Sendable {\n    public init() {}\n}\n#endif\n\n\nThen use it in tests:\n\nfinal class MyDisplayViewUITests: MyAppViewModelViewTestCase<\n    MyDisplayViewModel,\n    MyDisplayViewStubOps\n> {\n    // Only test UI state, no operation verification\n}\n\n\nThe view itself doesn't need:\n\nrepaintToggle state\n.testDataTransporter() modifier\noperations property\ntoggleRepaint() function\n\nJust add .uiTestingIdentifier() to elements you want to verify.\n\nTest Categories\nUI State Tests\n\nVerify that the UI displays correctly based on ViewModel state:\n\nfunc testButtonDisabledWhenNotReady() async throws {\n    let app = try presentView(viewModel: .stub(ready: false))\n    XCTAssertFalse(app.submitButton.isEnabled)\n}\n\nfunc testButtonEnabledWhenReady() async throws {\n    let app = try presentView(viewModel: .stub(ready: true))\n    XCTAssertTrue(app.submitButton.isEnabled)\n}\n\nOperation Tests\n\nVerify that user interactions invoke the correct operations:\n\nfunc testSubmitButtonInvokesOperation() async throws {\n    let app = try presentView(configuration: .requireAuth())\n    app.submitButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.submitCalled)\n    XCTAssertFalse(stubOps.cancelCalled)\n}\n\nNavigation Tests\n\nVerify navigation flows work correctly:\n\nfunc testNavigationToDetailView() async throws {\n    let app = try presentView()\n    app.itemRow.tap()\n\n    XCTAssertTrue(app.detailView.exists)\n}\n\nWhen to Use This Skill\nAdding UI tests for a new ViewModelView\nSetting up UI test infrastructure for a FOSMVVM project\nFollowing an implementation plan that requires test coverage\nValidating user interaction flows\nWhat This Skill Generates\nInitial Setup (once per project)\nFile\tLocation\tPurpose\n{ProjectName}ViewModelViewTestCase.swift\tTests/UITests/Support/\tBase test case for all UI tests\nXCUIElement.swift\tTests/UITests/Support/\tHelper extensions for XCUIElement\nPer ViewModelView\nFile\tLocation\tPurpose\n{ViewName}ViewModelOperations.swift\tSources/{ViewModelsTarget}/{Feature}/\tOperations protocol and stub (if view has interactions)\n{ViewName}UITests.swift\tTests/UITests/Views/{Feature}/\tUI tests for the view\n\nNote: Views without user interactions use an empty operations file with just the protocol and minimal stub.\n\nProject Structure Configuration\nPlaceholder\tDescription\tExample\n{ProjectName}\tYour project/app name\tMyApp, TaskManager\n{ViewName}\tThe ViewModelView name (without \"View\" suffix)\tTaskList, Dashboard\n{Feature}\tFeature/module grouping\tTasks, Settings\nHow to Use This Skill\n\nInvocation: /fosmvvm-ui-tests-generator\n\nPrerequisites:\n\nView and ViewModel structure understood from conversation context\nViewModelOperations type identified (or confirmed as display-only)\nInteractive elements and user flows discussed\n\nWorkflow 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.\n\nPattern Implementation\n\nThis skill references conversation context to determine test structure:\n\nTest Type Detection\n\nFrom conversation context, the skill identifies:\n\nFirst test vs additional test (whether base test infrastructure exists)\nViewModel type (from prior discussion or View implementation)\nViewModelOperations type (from View implementation or context)\nInteractive vs display-only (whether operations need verification)\nView Analysis\n\nFrom requirements already in context:\n\nInteractive elements (buttons, fields, controls requiring test coverage)\nUser flows (navigation paths, form submission, drag-and-drop)\nState variations (enabled/disabled, visible/hidden, error states)\nOperation triggers (which UI actions invoke which operations)\nInfrastructure Planning\n\nBased on project state:\n\nBase test case (create if first test, reuse if exists)\nXCUIElement extensions (helper methods for common interactions)\nApp bundle identifier (for launching test host)\nTest File Generation\n\nFor the specific view:\n\nTest class inheriting from base test case\nUI state tests (verify display based on ViewModel)\nOperation tests (verify user interactions invoke operations)\nXCUIApplication extension with element accessors\nView Requirements\n\nEnsure test identifiers and data transport:\n\n.uiTestingIdentifier() on all interactive elements\n@State private var repaintToggle (if has operations)\n.testDataTransporter() modifier (if has operations)\ntoggleRepaint() calls after operations (if has operations)\nContext Sources\n\nSkill references information from:\n\nPrior conversation: View requirements, user flows discussed\nView implementation: If Claude has read View code into context\nViewModelOperations: From codebase or discussion\nKey Patterns\nTest Configuration Pattern\n\nUse TestConfiguration for tests that need specific app state:\n\nfunc testWithSpecificState() async throws {\n    let app = try presentView(\n        configuration: .requireAuth(userId: \"123\")\n    )\n    // Test with authenticated state\n}\n\nElement Accessor Pattern\n\nDefine element accessors in a private extension:\n\nprivate extension XCUIApplication {\n    var submitButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"submitButton\")\n    }\n\n    var cancelButton: XCUIElement {\n        buttons.element(matching: .button, identifier: \"cancelButton\")\n    }\n\n    var firstItem: XCUIElement {\n        buttons.element(matching: .button, identifier: \"itemButton\").firstMatch\n    }\n}\n\nOperation Verification Pattern\n\nAfter user interactions, verify operations were called:\n\nfunc testDecrementButton() async throws {\n    let app = try presentView(configuration: .requireDevice())\n    app.decrementButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.decrementCalled)\n    XCTAssertFalse(stubOps.incrementCalled)\n}\n\nOrientation Setup Pattern\n\nSet device orientation in setUp() if needed:\n\noverride func setUp() async throws {\n    try await super.setUp()\n\n    #if os(iOS)\n    XCUIDevice.shared.orientation = .portrait\n    #endif\n}\n\nView Testing Checklist\n\nAll views:\n\n .uiTestingIdentifier() on all elements you want to test\n\nViews WITH operations (interactive views):\n\n @State private var repaintToggle = false property\n .testDataTransporter(viewModelOps:repaintToggle:) modifier\n toggleRepaint() helper function\n toggleRepaint() called after every operation invocation\n operations stored from viewModel.operations in init\n\nViews WITHOUT operations (display-only):\n\n No repaintToggle needed\n No .testDataTransporter() needed\n No operations property needed\n operations stored from viewModel.operations in init\nCommon Test Patterns\nTesting Async Operations\nfunc testAsyncOperation() async throws {\n    let app = try presentView()\n    app.loadButton.tap()\n\n    // Wait for UI to update\n    _ = app.waitForExistence(timeout: 3)\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.loadCalled)\n}\n\nTesting Form Input\nfunc testFormInput() async throws {\n    let app = try presentView()\n\n    let emailField = app.emailTextField\n    emailField.tap()\n    emailField.typeTextAndWait(\"user@example.com\")\n\n    app.submitButton.tap()\n\n    let stubOps = try viewModelOperations()\n    XCTAssertTrue(stubOps.submitCalled)\n}\n\nTesting Error States\nfunc testErrorDisplay() async throws {\n    let app = try presentView(viewModel: .stub(hasError: true))\n\n    XCTAssertTrue(app.errorAlert.exists)\n    XCTAssertEqual(app.errorMessage.text, \"An error occurred\")\n}\n\nFile Templates\n\nSee reference.md for complete file templates.\n\nNaming Conventions\nConcept\tConvention\tExample\nBase test case\t{ProjectName}ViewModelViewTestCase\tMyAppViewModelViewTestCase\nUI test file\t{ViewName}UITests\tTaskListViewUITests\nTest method (UI state)\ttest{Condition}\ttestButtonEnabled\nTest method (operation)\ttest{Action}\ttestSubmitButton\nElement accessor\t{elementName}\tsubmitButton, emailTextField\nUI testing identifier\t{elementName}Identifier or {elementName}\t\"submitButton\", \"emailTextField\"\nSee Also\nArchitecture Patterns - Mental models and patterns\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-viewmodel-generator - For creating ViewModels\nfosmvvm-swiftui-app-setup - For app test host setup\nreference.md - Complete file templates\nVersion History\nVersion\tDate\tChanges\n1.0\t2026-01-23\tInitial skill for UI tests\n1.1\t2026-01-24\tUpdate to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-ui-tests-generator",
    "publisherUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-ui-tests-generator",
    "owner": "foscomputerservices",
    "version": "2.0.6",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator",
    "downloadUrl": "https://openagent3.xyz/downloads/fosmvvm-ui-tests-generator",
    "agentUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-ui-tests-generator/agent.md"
  }
}