Token导航 LogoToken导航TokenDH.com
研究检索需要联网clawhub未标认证来源可访问clear审计通过

fosmvvm-ui-tests-generatorfosmvvm ui 测试生成器

Agent Skill

用于辅助界面设计、视觉规范、排版、配色、布局和交互体验优化。它适合让 Agent 根据产品场景整理页面结构、生成 UI 方案、检查视觉一致性或改进组件层级。使用时需要结合现有品牌、设计系统和用户任务,不应只堆装饰元素;涉及真实页面改动时,应通过截图或浏览器预览检查文本溢出、对齐和响应式表现。

总安装

16,622

周安装

679

GitHub Stars

公开资料未说明

下载量

5,323
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:fosmvvm-ui-tests-generator(fosmvvm ui 测试生成器)
来源仓库:https://github.com/foscomputerservices/fosmvvm-ui-tests-generator
安装命令:
openclaw skills install fosmvvm-ui-tests-generator
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install fosmvvm-ui-tests-generator

简介

使用 XCTest 和 FOSTestingUI 为 FOSMVVM SwiftUI 视图生成 UI 测试。涵盖可访问性标识符、ViewModelOperations 和测试数据传输。

SKILL.md

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 | 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          │
│  └──────────────────┘                                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Core Components

1. Base Test Case Class

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

2. Individual UI Test Files

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)

3. XCUIElement Helper Extensions

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()
        }
    }
}

4. View Requirements

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

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:

// 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.

Test Categories

UI State Tests

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)
}

Operation Tests

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)
}

Navigation Tests

Verify navigation flows work correctly:

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)

FileLocationPurpose
{ProjectName}ViewModelViewTestCase.swiftTests/UITests/Support/Base test case for all UI tests
XCUIElement.swiftTests/UITests/Support/Helper extensions for XCUIElement

Per ViewModelView

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.

Project Structure Configuration

PlaceholderDescriptionExample
{ProjectName}Your project/app nameMyApp, TaskManager
{ViewName}The ViewModelView name (without "View" suffix)TaskList, Dashboard
{Feature}Feature/module groupingTasks, 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:

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:

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:

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:

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

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

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)
}

Testing Error States

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 for complete file templates.

Naming Conventions

ConceptConventionExample
Base test case{ProjectName}ViewModelViewTestCaseMyAppViewModelViewTestCase
UI test file{ViewName}UITestsTaskListViewUITests
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

VersionDateChanges
1.02026-01-23Initial skill for UI tests
1.12026-01-24Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths.

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

91.38%
按下载量换算4,864

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

未展示

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills