Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Generate UI tests for FOSMVVM SwiftUI views using XCTest and FOSTestingUI. Covers accessibility identifiers, ViewModelOperations, and test data transport.
Generate UI tests for FOSMVVM SwiftUI views using XCTest and FOSTestingUI. Covers accessibility identifiers, ViewModelOperations, and test data transport.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
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.
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.
Generate comprehensive UI tests for ViewModelViews in FOSMVVM applications.
For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference 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 β β ββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Every project should have a base test case that inherits from ViewModelViewTestCase: 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
Each ViewModelView gets a corresponding UI test file. For views WITH operations: 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: // 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)
Common helpers for interacting with UI elements: 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() } } }
For views WITH operations: 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): 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
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: // 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: 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.
Verify that the UI displays correctly based on ViewModel state: 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) }
Verify that user interactions invoke the correct operations: func testSubmitButtonInvokesOperation() async throws { let app = try presentView(configuration: .requireAuth()) app.submitButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.submitCalled) XCTAssertFalse(stubOps.cancelCalled) }
Verify navigation flows work correctly: func testNavigationToDetailView() async throws { let app = try presentView() app.itemRow.tap() XCTAssertTrue(app.detailView.exists) }
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
FileLocationPurpose{ProjectName}ViewModelViewTestCase.swiftTests/UITests/Support/Base test case for all UI testsXCUIElement.swiftTests/UITests/Support/Helper extensions for XCUIElement
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 Note: Views without user interactions use an empty operations file with just the protocol and minimal stub.
PlaceholderDescriptionExample{ProjectName}Your project/app nameMyApp, TaskManager{ViewName}The ViewModelView name (without "View" suffix)TaskList, Dashboard{Feature}Feature/module groupingTasks, Settings
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.
This skill references conversation context to determine test structure:
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)
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)
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)
For the specific view: Test class inheriting from base test case UI state tests (verify display based on ViewModel) Operation tests (verify user interactions invoke operations) XCUIApplication extension with element accessors
Ensure test identifiers and data transport: .uiTestingIdentifier() on all interactive elements @State private var repaintToggle (if has operations) .testDataTransporter() modifier (if has operations) toggleRepaint() calls after operations (if has operations)
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
Use TestConfiguration for tests that need specific app state: func testWithSpecificState() async throws { let app = try presentView( configuration: .requireAuth(userId: "123") ) // Test with authenticated state }
Define element accessors in a private extension: 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 } }
After user interactions, verify operations were called: func testDecrementButton() async throws { let app = try presentView(configuration: .requireDevice()) app.decrementButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.decrementCalled) XCTAssertFalse(stubOps.incrementCalled) }
Set device orientation in setUp() if needed: override func setUp() async throws { try await super.setUp() #if os(iOS) XCUIDevice.shared.orientation = .portrait #endif }
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
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) }
func testFormInput() async throws { let app = try presentView() let emailField = app.emailTextField emailField.tap() emailField.typeTextAndWait("user@example.com") app.submitButton.tap() let stubOps = try viewModelOperations() XCTAssertTrue(stubOps.submitCalled) }
func testErrorDisplay() async throws { let app = try presentView(viewModel: .stub(hasError: true)) XCTAssertTrue(app.errorAlert.exists) XCTAssertEqual(app.errorMessage.text, "An error occurred") }
See reference.md for complete file templates.
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"
Architecture Patterns - Mental models and patterns FOSMVVMArchitecture.md - Full FOSMVVM architecture fosmvvm-viewmodel-generator - For creating ViewModels fosmvvm-swiftui-app-setup - For app test host setup reference.md - Complete file templates
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.
Code helpers, APIs, CLIs, browser automation, testing, and developer operations.
Largest current source with strong distribution and engagement signals.