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. 02CI 向けの録画テストを安定化
  3. 03ローカライズや外観の構成をテスト
  4. 04UI テスト失敗の調査

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/axiom-ui-recordingを入力して読み込まれたか確認します。

詳細

UI オートメーションの記録(Xcode 26 以降)

ユーザーの操作を記録して UI テストを作成する、Xcode 26 の UI オートメーション記録機能について説明します。

3 フェーズのワークフロー

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. アプリがシミュレーターで起動します
  5. 操作を行うと、Xcode がコードを生成します
  6. 完了したら記録を停止します
記録される内容
  • ボタン、セル、コントロールのタップ
  • テキストフィールドへのテキスト入力
  • スワイプとスクロール
  • ジェスチャ(ピンチ、回転)
  • ハードウェアボタンの押下(ホーム、音量)
生成されるコードの例
// 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()

アプリのコードに識別子を追加します:

// 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構成に使用するアプリターゲット
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. レポートナビゲーターでテスト結果を表示する
  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. シミュレーターが利用可能か確認します
  3. アプリがビルドされ、実行できることを確認します
  4. Xcode を再起動してみます
記録したコードが動作しない
  1. 操作の前に waitForExistence を追加します
  2. アクセシビリティ識別子が設定されているか確認します
  3. クエリを可能な限り短い形式に簡略化します
  4. アプリを手動で実行して、フローが動作することを確認します
ローカルではテストが成功するが、CI では失敗する
  1. CI マシンの動作が遅い場合に備えて、タイムアウトを延長します
  2. アニメーションに対する明示的な待機を追加します
  3. シミュレーターの構成が一致しているか確認します
  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

コメント