mcpField
A
axiom-ui-recording

axiom-ui-recording

github.com/charleswiltgen测试与质量1.2k169 次安装今天更新
代码仓库

关于 axiom-ui-recording

面向编写 UI 测试的开发者,介绍 Xcode 26 的 UI Automation 录制功能。内容涵盖在 Simulator 中录制交互、增强生成的测试、通过测试计划运行多种配置并检查结果。

主要功能

  1. 01将应用交互录制为 Swift 测试
  2. 02用标识符和等待机制增强生成测试
  3. 03通过测试计划配置运行测试
  4. 04查看视频、截图和操作时间线

适用场景

  1. 01根据应用交互创建 UI 测试
  2. 02稳定用于 CI 的录制测试
  3. 03测试本地化和外观配置
  4. 04调查 UI 测试失败

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-ui-recording

安装后在 Claude Code 中输入/axiom-ui-recording确认已加载。

详细介绍

录制 UI 自动化(Xcode 26+)

Xcode 26 的 UI 自动化录制功能指南:通过录制用户交互来创建 UI 测试。

三阶段工作流

摘自 WWDC 2025-344:

┌─────────────────────────────────────────────────────────────┐
│                   UI Automation Workflow                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. RECORD ──────► Interact with app in Simulator           │
│                    Xcode captures as Swift test code        │
│                                                             │
│  2. REPLAY ──────► Run across devices, languages, configs   │
│                    Using test plans for multi-config        │
│                                                             │
│  3. REVIEW ──────► Watch video recordings in test report    │
│                    Analyze failures with screenshots        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

阶段 1:录制

开始录制
  1. 在 Xcode 中打开 UI 测试文件
  2. 将光标置于测试方法内
  3. Debug → Record UI Automation(或使用录制按钮)
  4. App 会在 Simulator 中启动
  5. 执行交互操作,Xcode 会生成代码
  6. 完成后停止录制
录制内容
  • 按钮、单元格和控件上的轻点
  • 在文本字段中输入文本
  • 轻扫和滚动
  • 手势(捏合、旋转)
  • 硬件按钮按压(Home、音量)
生成的代码示例
// Xcode generates this from your interactions
func testLoginFlow() {
    let app = XCUIApplication()
    app.launch()

    // Recorded: Tap email field, type email
    app.textFields["Email"].tap()
    app.textFields["Email"].typeText("[email protected]")

    // Recorded: Tap password field, type password
    app.secureTextFields["Password"].tap()
    app.secureTextFields["Password"].typeText("password123")

    // Recorded: Tap login button
    app.buttons["Login"].tap()
}

改进录制的代码

重要:录制的代码往往不够稳定。务必进行改进,以提高稳定性。

1. 添加辅助功能标识符

录制的代码使用标签,在本地化后可能失效:

// RECORDED (fragile - breaks with localization)
app.buttons["Login"].tap()

// ENHANCED (stable - uses identifier)
app.buttons["loginButton"].tap()

在 App 代码中添加标识符:

// SwiftUI
Button("Login") { ... }
    .accessibilityIdentifier("loginButton")

// UIKit
loginButton.accessibilityIdentifier = "loginButton"
2. 添加 waitForExistence

录制的代码假定元素会立即出现:

// RECORDED (may fail if app is slow)
app.buttons["Login"].tap()

// ENHANCED (waits for element)
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
loginButton.tap()
3. 添加断言

录制的代码只执行操作,不进行验证:

// RECORDED (no verification)
app.buttons["Login"].tap()

// ENHANCED (with assertion)
app.buttons["loginButton"].tap()
let welcomeLabel = app.staticTexts["welcomeLabel"]
XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 10),
              "Welcome screen should appear after login")
4. 使用更短的查询

录制的代码可能包含过于具体的查询:

// RECORDED (too specific)
app.tables.cells.element(boundBy: 0).buttons["Action"].tap()

// ENHANCED (simpler)
app.buttons["actionButton"].tap()

查询选择指南

摘自 WWDC 2025-344:

场景问题解决方案
本地化字符串“Login” 会随语言变化使用 accessibilityIdentifier
深层嵌套视图长查询链容易失效使用尽可能短的查询
动态内容单元格内容会变化使用标识符或通用查询
匹配结果过多查询返回许多元素添加唯一标识符
最佳实践
  1. 优先使用标识符,而非标签
  2. 使用能够正常工作的最短查询
  3. 避免基于索引的查询(element(boundBy: 0))
  4. 为动态内容添加标识符

阶段 2:使用测试计划重放

测试计划可让你在多种配置下运行相同的测试。

创建测试计划
  1. File → New → File → Test Plan
  2. 添加测试目标
  3. 配置各项设置
测试计划结构
{
  "configurations": [
    {
      "name": "iPhone - English",
      "options": {
        "targetForVariableExpansion": {
          "containerPath": "container:MyApp.xcodeproj",
          "identifier": "MyApp"
        },
        "language": "en",
        "region": "US"
      }
    },
    {
      "name": "iPhone - Spanish",
      "options": {
        "language": "es",
        "region": "ES"
      }
    },
    {
      "name": "iPhone - Dark Mode",
      "options": {
        "userInterfaceStyle": "dark"
      }
    },
    {
      "name": "iPad - Landscape",
      "options": {
        "defaultTestExecutionTimeAllowance": 120,
        "testTimeoutsEnabled": true
      }
    }
  ],
  "defaultOptions": {
    "targetForVariableExpansion": {
      "containerPath": "container:MyApp.xcodeproj",
      "identifier": "MyApp"
    }
  },
  "testTargets": [
    {
      "target": {
        "containerPath": "container:MyApp.xcodeproj",
        "identifier": "MyAppUITests",
        "name": "MyAppUITests"
      }
    }
  ],
  "version": 1
}
配置选项
选项用途
language测试本地化
region测试区域格式
userInterfaceStyle测试深色/浅色模式
targetForVariableExpansion用于配置的 App 目标
testTimeoutsEnabled启用超时限制
defaultTestExecutionTimeAllowance超时时间(秒)
使用测试计划运行
# Command line
xcodebuild test \
  -scheme "MyApp" \
  -testPlan "MyTestPlan" \
  -destination "platform=iOS Simulator,name=iPhone 16" \
  -resultBundlePath /tmp/results.xcresult

# In Xcode
# Product → Test Plan → Select your plan
# Then Cmd+U to run tests

阶段 3:审查

测试报告功能

测试完成后:

  1. 在 Report Navigator 中查看测试结果
  2. 观看每项测试的录制视频
  3. 查看失败时的屏幕截图
  4. 分析操作的时间线
启用附件

在测试计划或方案中:

"options": {
  "systemAttachmentLifetime": "keepAlways",
  "userAttachmentLifetime": "keepAlways"
}
捕获自定义屏幕截图
func testCheckout() {
    // ... actions ...

    // Manual screenshot at specific point
    let screenshot = app.screenshot()
    let attachment = XCTAttachment(screenshot: screenshot)
    attachment.name = "Checkout Confirmation"
    attachment.lifetime = .keepAlways
    add(attachment)
}

常见模式

登录流程模板
func testLoginWithValidCredentials() throws {
    let app = XCUIApplication()
    app.launch()

    // Navigate to login
    let showLoginButton = app.buttons["showLoginButton"]
    XCTAssertTrue(showLoginButton.waitForExistence(timeout: 5))
    showLoginButton.tap()

    // Enter credentials
    let emailField = app.textFields["emailTextField"]
    XCTAssertTrue(emailField.waitForExistence(timeout: 5))
    emailField.tap()
    emailField.typeText("[email protected]")

    let passwordField = app.secureTextFields["passwordTextField"]
    passwordField.tap()
    passwordField.typeText("password123")

    // Submit
    app.buttons["loginButton"].tap()

    // Verify success
    let welcomeScreen = app.staticTexts["welcomeLabel"]
    XCTAssertTrue(welcomeScreen.waitForExistence(timeout: 10))
}
导航流程模板
func testNavigateToSettings() throws {
    let app = XCUIApplication()
    app.launch()

    // Open tab bar item
    app.tabBars.buttons["Settings"].tap()

    // Verify navigation
    let settingsTitle = app.navigationBars["Settings"]
    XCTAssertTrue(settingsTitle.waitForExistence(timeout: 5))

    // Navigate deeper
    app.tables.cells["Account"].tap()
    XCTAssertTrue(app.navigationBars["Account"].exists)
}
表单验证模板
func testFormValidation() throws {
    let app = XCUIApplication()
    app.launch()

    // Submit empty form
    app.buttons["submitButton"].tap()

    // Verify error appears
    let errorAlert = app.alerts["Error"]
    XCTAssertTrue(errorAlert.waitForExistence(timeout: 5))
    XCTAssertTrue(errorAlert.staticTexts["Please fill all fields"].exists)

    // Dismiss alert
    errorAlert.buttons["OK"].tap()
}

故障排查

无法开始录制
  1. 确保当前位于测试方法中
  2. 检查 Simulator 是否可用
  3. 确认 App 能够构建并运行
  4. 尝试重启 Xcode
录制的代码无法运行
  1. 在交互操作前添加 waitForExistence
  2. 检查是否设置了辅助功能标识符
  3. 简化查询,使用最短形式
  4. 手动运行 App,确认流程正常
测试在本地通过,在 CI 中失败
  1. 为运行较慢的 CI 机器增加超时时间
  2. 为动画添加显式等待
  3. 检查 Simulator 配置是否一致
  4. 在测试设置中停用动画:
    app.launchArguments = ["--disable-animations"]
    

反模式

不要在 CI 中使用未经修改的录制代码
// BAD - Raw recorded code
app.buttons["Login"].tap()
app.textFields["Email"].typeText("[email protected]")

// GOOD - Enhanced for CI
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 10))
loginButton.tap()
不要硬编码坐标
// BAD - Coordinates from recording
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()

// GOOD - Use element queries
app.buttons["centerButton"].tap()
不要跳过断言
// BAD - Actions only
app.buttons["Login"].tap()
sleep(2)  // Hope it works

// GOOD - Verify outcomes
app.buttons["loginButton"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 10))

资源

WWDC:2025-344、2024-10206、2019-413

文档:/xcode/testing/recording-ui-tests、/xctest/xcuiapplication

技能:axiom-xctest-automation、axiom-ui-testing

评论

支持 Markdown