FOSMVVM UI Tests Generator
Generate comprehensive UI tests for ViewModelViews in FOSMVVM applications.
Conceptual Foundation
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
CODEBLOCK0
Core Components
1. Base Test Case Class
Every project should have a base test case that inherits from ViewModelViewTestCase:
CODEBLOCK1
Key points:
- - Generic over
ViewModel and INLINECODE2 - Wraps FOSTestingUI's
presentView() with project-specific configuration - Sets up bundle and app bundle identifier
- INLINECODE4 stops tests immediately on failure
2. Individual UI Test Files
Each ViewModelView gets a corresponding UI test file.
For views WITH operations:
CODEBLOCK2
For views WITHOUT operations (display-only):
Use an empty stub operations protocol:
CODEBLOCK3
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:
CODEBLOCK4
4. View Requirements
For views WITH operations:
CODEBLOCK5
For views WITHOUT operations (display-only):
CODEBLOCK6
Critical patterns (for views WITH operations):
- -
@State private var repaintToggle = false for triggering test data transport - INLINECODE6 modifier in DEBUG
- INLINECODE7 called after every operation invocation
- INLINECODE8 stored as property from INLINECODE9
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:
CODEBLOCK7
Then use it in tests:
CODEBLOCK8
The view itself doesn't need:
- -
repaintToggle state - INLINECODE14 modifier
- INLINECODE15 property
- INLINECODE16 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:
CODEBLOCK9
Operation Tests
Verify that user interactions invoke the correct operations:
CODEBLOCK10
Navigation Tests
Verify navigation flows work correctly:
CODEBLOCK11
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 |
|---|
| INLINECODE18 | INLINECODE19 | Base test case for all UI tests |
| INLINECODE20 |
Tests/UITests/Support/ | Helper extensions for XCUIElement |
Per ViewModelView
| File | Location | Purpose |
|---|
| INLINECODE22 | INLINECODE23 | Operations protocol and stub (if view has interactions) |
| INLINECODE24 |
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 |
|---|
| INLINECODE26 | Your project/app name | INLINECODE27 , INLINECODE28 |
| INLINECODE29 |
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
- UI state tests (verify display based on ViewModel)
- Operation tests (verify user interactions invoke operations)
- XCUIApplication extension with element accessors
View Requirements
Ensure test identifiers and data transport:
- 1.
.uiTestingIdentifier() on all interactive elements - INLINECODE36 (if has operations)
- INLINECODE37 modifier (if has operations)
- INLINECODE38 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:
CODEBLOCK12
Element Accessor Pattern
Define element accessors in a private extension:
CODEBLOCK13
Operation Verification Pattern
After user interactions, verify operations were called:
CODEBLOCK14
Orientation Setup Pattern
Set device orientation in setUp() if needed:
CODEBLOCK15
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
CODEBLOCK16
Testing Form Input
CODEBLOCK17
Testing Error States
CODEBLOCK18
File Templates
See reference.md for complete file templates.
Naming Conventions
| Concept | Convention | Example |
|---|
| Base test case | INLINECODE53 | INLINECODE54 |
| 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
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. |
FOSMVVM UI 测试生成器
为 FOSMVVM 应用中的 ViewModelView 生成全面的 UI 测试。
概念基础
完整架构上下文请参见 FOSMVVMArchitecture.md | OpenClaw 参考
FOSMVVM 中的 UI 测试遵循特定模式,利用:
- - FOSTestingUI 框架提供测试基础设施
- ViewModelOperations 验证业务逻辑是否被调用
- 无障碍标识 定位 UI 元素
- 测试数据传输器 将操作桩传递给应用
┌─────────────────────────────────────────────────────────────┐
│ UI 测试架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 测试文件 (XCTest) 被测试应用 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ MyViewUITests │ │ MyView │ │
│ │ │ │ │ │
│ │ presentView() ───┼─────────────►│ 显示带有桩数据 │ │
│ │ 使用桩 VM │ │ 的视图 │ │
│ │ │ │ │ │
│ │ 通过标识交互 ────┼─────────────►│ 带有 .uiTestingId│ │
│ │ │ │ 的 UI 元素 │ │
│ │ │ │ │ │
│ │ 断言 UI 状态 │ │ .testData────────┼──┐ │
│ │ │ │ 传输器 │ │ │
│ │ │ └──────────────────┘ │ │
│ │ viewModelOps() ◄─┼─────────────────────────────────────┘ │
│ │ 验证调用 │ 桩操作 │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
核心组件
1. 基础测试用例类
每个项目应有一个继承自 ViewModelViewTestCase 的基础测试用例:
swift
class MyAppViewModelViewTestCase:
ViewModelViewTestCase, @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
}
}
关键点:
- - 泛型于 ViewModel 和 ViewModelOperations
- 使用项目特定配置包装 FOSTestingUI 的 presentView()
- 设置 bundle 和应用 bundle 标识符
- continueAfterFailure = false 在失败时立即停止测试
2. 单个 UI 测试文件
每个 ViewModelView 对应一个 UI 测试文件。
对于带有操作的视图:
swift
final class MyViewUITests: MyAppViewModelViewTestCase {
// UI 测试 - 验证 UI 状态
func testButtonEnabled() async throws {
let app = try presentView(viewModel: .stub(enabled: true))
XCTAssertTrue(app.myButton.isEnabled)
}
// 操作测试 - 验证操作被调用
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)
}
}
对于不带操作的视图(仅显示):
使用空桩操作协议:
swift
// 在你的测试文件中
protocol MyViewStubOps: ViewModelOperations {}
struct MyViewStubOpsImpl: MyViewStubOps {}
final class MyViewUITests: MyAppViewModelViewTestCase {
// 仅 UI 测试 - 无操作验证
func testDisplaysCorrectly() async throws {
let app = try presentView(viewModel: .stub(title: Test))
XCTAssertTrue(app.titleLabel.exists)
}
}
何时使用每种方式:
- - 带操作:执行操作的交互式视图(表单、调用 API 的按钮等)
- 不带操作:仅显示视图(卡片、详情视图、静态内容)
3. XCUIElement 辅助扩展
与 UI 元素交互的常用辅助方法:
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. 视图要求
对于带有操作的视图:
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
}
}
对于不带操作的视图(仅显示):
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
}
}
关键模式(对于带操作的视图):
- - @State private var repaintToggle = false 用于触发测试数据传输
- 在 DEBUG 中使用 .testDataTransporter(viewModelOps:repaintToggle:) 修饰符
- 每次操作调用后调用 toggleRepaint()
- operations 作为属性从 viewModel.operations 存储
仅显示视图:
- - 不需要 repaintToggle
- 不需要 .testDataTransporter() 修饰符
- 只需在要测试的元素上添加 .uiTestingIdentifier()
ViewModelOperations:可选
并非所有视图都需要 ViewModelOperations:
需要操作的视图:
- - 带有提交/取消操作的表单
- 调用业务逻辑或 API 的视图
- 触发应用状态变化的交互式视图
- 用户发起的异步操作视图
不需要操作的视图:
- - 仅显示卡片或详情视图
- 静态内容视图
- 纯导航容器
- 仅渲染数据的服务器托管视图
对于不带操作的视图:
在 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
然后在测试中使用:
swift
final class MyDisplayViewUITests: MyAppViewModelViewTestCase<
MyDisplayViewModel,
MyDisplayViewStubOps
{
// 仅测试 UI 状态,无操作验证
}
视图本身不需要:
- - repaintToggle 状态
- .testDataTransporter() 修饰符
- operations 属性
- toggleRepaint() 函数
只需在要验证的元素上添加 .uiTestingIdentifier()。
测试类别
UI 状态测试
验证 UI 根据 ViewModel 状态正确显示