ui4-convert-tests について
ui4-convert-tests は、UI 変更後に影響を受ける E2E テストを特定して修正するスキルです。変更の種類とテストの依存関係を分析し、セレクター、操作手順、アサーションの更新を支援して、対象テストを実行します。
主な機能
- 01UI 差分とテストへの影響を分析
- 02テスト群や共有ヘルパーから対象ケースを検索
- 03セレクター、構造、文言、属性の変更別に修正を分類
- 04個別テストより共有ヘルパーを先に更新
- 05対象の E2E テストを実行して修正を検証
活用シーン
- 01UI 変更後に CI の E2E テスト失敗を調査
- 02コンポーネントがポップアップ内へ移動した際の操作手順を更新
- 03セレクターやラベル変更後にテストのアサーションを修正
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ payloadcms/ payload --skill ui4-convert-testsインストール後、Claude Code で/ui4-convert-testsを入力して読み込まれたか確認します。
詳細
UI4 Convert Tests
概要
UI 変更を完了した後、このスキルは影響を受ける e2e テストを体系的に特定して修正します。差分を分析して、どのファイルが変更されたかだけでなく、どのような種類の 変更が行われたかを理解し、更新が必要なテストを見つけ出します。
使用するタイミング
- UI 変更が確定し、テスト修正の準備ができたとき
- UI 変更が原因で CI のテストが失敗しているとき
- テストが通ることを確認するために PR を開く前
プロセス
ステップ 1: 何が変更されたかを分析する
目標: 変更の 性質 を理解し、テストへの影響を予測します。
# Get changed UI files
git diff main --name-only -- 'packages/ui/src/**/*.tsx' 'packages/ui/src/**/*.css'
変更された各ファイルについて、変更を分類します:
A. セレクターの変更 (ID、クラス)
git diff main -- <file> | grep -E '^\-.*className|^\-.*id=|^\+.*className|^\+.*id='
B. 構造の変更 (要素の移動)
次のようなコンポーネントを探します:
- ポップアップ、ドロワー、ドロップダウンの中に移動された
- 新しい親要素でラップされた
- 条件付きになった
git diff main -- <file> | grep -E 'Popup|PopupList|Drawer|Dropdown'
C. テキスト/ラベルの変更
# Translation keys
git diff main -- <file> | grep -E "t\('|i18n\.t\("
# Hardcoded text
git diff main -- <file> | grep -E 'placeholder=|aria-label='
変更サマリーを作成します:
| 変更の種類 | 変更内容 | テストへの影響 |
|---|---|---|
| セレクター | .btn:has-text("Create") → #create-new-doc | ロケーターを更新する |
| 構造 | ボタンがポップアップ内に移動された | ポップアップを開くステップを追加する |
| テキスト | "Search by ID" → "Search" | アサーションを更新する |
ステップ 2: 影響を受けるテストを見つける
検索戦略: まず広く網を張り、その後絞り込みます。
# Search for component name references (not just selectors)
grep -rn "QueryPreset\|query-preset\|preset" test/**/*.ts --include="*.spec.ts" --include="*.ts"
# Search for specific selectors from Step 1
grep -rn "\.list-header\|Create New\|#create-new" test/**/*.ts
主なテストの場所:
| パターン | 探す場所 |
|---|---|
| コンポーネント固有 | test/<feature>/e2e.spec.ts |
| 共有ヘルパー | test/<feature>/helpers/*.ts |
| 横断的 | test/__helpers/e2e/*.ts |
| 同じコンポーネントを使用する複数の機能 | すべてのテストディレクトリを検索する |
完全一致するセレクターだけを検索しないでください! 次のものも検索します:
- コンポーネント名 (例:
QueryPreset、ListHeader) - 機能名 (例:
preset、filter、search) - 変更されたテキストコンテンツ (例:
"Create New"、"Search by")
ステップ 3: テストの依存関係を分析する
修正する前に、テストを理解します:
- テスト全体を読む - そのテストが実際に何をテストしているかを理解する
- ヘルパーを確認する - このセレクターを処理する共有ヘルパーはあるか?
- パターンを探す - 複数のテストが同じことをしていないか?
複数のテストが同じセレクターを使用している場合は、ヘルパーを作成/更新します:
// test/<feature>/helpers/togglePreset.ts
export async function openCreatePreset(page: Page) {
await page.click('#select-preset') // Open popup first
await page.click('#create-new-preset')
}
これにより修正が一元化され、将来の重複を防ぐことができます。
ステップ 4: 必要な修正を分類する
| 変更の種類 | 修正戦略 |
|---|---|
| セレクターの名前変更 | 文字列を直接置換する |
| 要素がポップアップに移動 | 要素をクリックする前にポップアップを開くクリックを追加する |
| 要素がドロワーに移動 | ドロワーの開閉処理を追加する |
| テキストの簡素化 | 新しいテキストに合わせてアサーションを更新する |
| 要素の削除 | テストロジックを作り直すか、テストを削除する |
| Props の変更 | 属性のアサーションを更新する |
| 条件付きレンダリング | 要素が表示される前に状態を設定する必要があるかもしれない |
ステップ 5: 影響を受けるテストを実行する
修正を行う前にテストを実行して、実際に失敗することを確認します:
# Use isolated port to avoid conflicts
PORT=3150 pnpm test:e2e <suite> --max-failures=1
# Run specific test by name
PORT=3150 pnpm test:e2e <suite> -g "test name" --max-failures=1
失敗パターンを記録します:
Timeout waiting for locator('.old-selector')→ セレクターが変更されたlocator resolved to 0 elements→ 要素が移動または削除されたexpected "New Text" received "Old Text"→ テキストコンテンツが変更された
ステップ 6: 修正を適用する
優先順位: まずヘルパーを修正し、次に個々のテストを修正します。
パターン 1: セレクターの名前変更
// Before
await page.click('.list-header .btn:has-text("Create")')
// After - prefer IDs when available
await page.click('#create-new-doc')
パターン 2: 要素がポップアップ内に移動された
// Before - direct click
await page.click('#edit-preset')
// After - open popup first
await page.click('#select-preset') // Opens the popup
await page.click('#edit-preset') // Now visible in popup
パターン 3: テキストコンテンツの簡素化
// Before - specific placeholder text
await expect(input).toHaveAttribute('placeholder', /(Search by ID)/)
// After - simplified text
await expect(input).toHaveAttribute('placeholder', 'Search')
パターン 4: 再利用可能なヘルパーを作成する
同じインタラクションが複数のテストで必要な場合:
// test/<feature>/helpers/interactions.ts
export async function openEditPreset(page: Page) {
await page.click('#select-preset')
await page.click('#edit-preset')
}
// In tests - import and use
import { openEditPreset } from './helpers/interactions.js'
await openEditPreset(page)
ステップ 7: 修正を検証する
# Run same tests that failed
PORT=3150 pnpm test:e2e <suite> --max-failures=1
テストが成功した後にのみコミットしてください。
一般的なパターン (実際の例)
パターン: ボタンがポップアップメニュー内に移動した
症状: 以前は直接表示されていたボタンを待機してテストがタイムアウトします。
検出: ボタンが <Popup> または <PopupList> でラップされているか確認します:
git diff main -- <file> | grep -E 'PopupList|Popup'
修正: ボタンをクリックする前にポップアップのトリガークリックを追加します:
// Before: Button was directly in toolbar
await page.click('#edit-preset')
// After: Button is now inside a popup
await page.click('#select-preset') // Opens popup
await page.click('#edit-preset') // Now visible
おまけ: 複数のテストでこれが必要な場合は、ヘルパー関数を作成してください。
パターン: クラスベースのセレクター → ID セレクター
症状: .some-class または :has-text("Button Text") が要素を見つけられなくなります。
検出: コンポーネントに id= 属性が追加されています:
git diff main -- <file> | grep -E '^\+.*id='
修正: より安定した ID を使用します:
// Before: Fragile class + text selector
await page.click('.list-header .btn:has-text("Create New")')
// After: Stable ID selector
await page.click('#create-new-doc')
パターン: プレースホルダー/ラベルのテキストが簡略化された
症状: expected "New Text" received "Old Text" でアサーションが失敗します。
検出: 翻訳キーまたはハードコードされたテキストが変更されています:
git diff main -- <file> | grep -E 'placeholder=|t\('
修正: 新しいテキストに一致するようにアサーションを更新します:
// Before: Verbose placeholder
await expect(input).toHaveAttribute('placeholder', /(Search by ID, Title)/)
// After: Simplified
await expect(input).toHaveAttribute('placeholder', 'Search')
パターン: 複数のテストで同じ修正が必要
症状: 異なるスイートの複数のテストが、類似したセレクターの問題で失敗します。
検出:
# Find all tests using the old selector
grep -rn "old-selector\|.old-class" test/**/*.ts
修正:
test/<feature>/helpers/にヘルパーがすでに存在するか確認します- 存在する場合は、ヘルパーを修正します (すべてのテストが一度に修正されます)
- 存在しない場合は、作成してテストをリファクタリングし、それを使用します
パターン: 異なるスイートのテストがコンポーネントを共有している
共有コンポーネント (ListControls、QueryPresetBar など) を変更すると、複数のテストスイートが影響を受ける可能性があります。
検出:
# Find component name references across all tests
grep -rn "QueryPreset\|ListControl" test/**/*.ts | cut -d: -f1 | sort -u
一般的なスイート横断コンポーネント:
ListControls→ すべてのリストビューテストに影響しますQueryPresetBar→ query-presets、group-by、admin テストSearch→ i18n、admin、ほとんどのコレクションテストButton→ ほぼすべて
クイックリファレンス: 一般的なペイロードテストセレクター
| コンポーネント | 一般的なセレクター |
|---|---|
| Search | .search-filter__input、#search-filter-input |
| List View | .collection-list、tbody tr、.table-row |
| Popup | .popup__content、.popup-button-list__button |
| Modal | dialog、[id^=doc-drawer_]、[id^=list-drawer_] |
| Buttons | .btn、button[type="button"] |
| Query Presets | #select-preset、.query-preset-bar__* |
テストコマンドリファレンス
# Run all e2e tests for a test suite (auto-starts dev server)
PORT=3150 pnpm test:e2e <suite-name>
# Run specific test file
PORT=3150 pnpm test:e2e test/<suite>/e2e.spec.ts
# Run with headed browser (see what's happening)
PORT=3150 pnpm test:e2e:headed test/<suite>/e2e.spec.ts
# Run in debug mode (step through)
PORT=3150 pnpm test:e2e:debug test/<suite>/e2e.spec.ts
# Run specific test by name pattern
PORT=3150 pnpm test:e2e test/<suite>/e2e.spec.ts -g "test name pattern"
# Stop on first failure (useful during debugging)
PORT=3150 pnpm test:e2e test/<suite>/e2e.spec.ts --max-failures=1
注記: pnpm test:e2e コマンドは自動的に以下を実行します:
- ポートが空いている場合は開発サーバーを起動します
- ポートが使用中の場合は既存の開発サーバーを再利用します
- そのポートに対して playwright テストを実行します
分離してテストを実行する
クイックスタート: 分離されたテスト実行
3100〜3199 の範囲で一意のポートを選択します:
# Run tests on an isolated port (MongoDB auto-starts its own in-memory server)
PORT=3150 pnpm test:e2e query-presets --max-failures=1
MongoDB (デフォルト) の場合はこれだけです。各テスト実行は独自のインメモリ MongoDB サーバーを起動するため、データベースの競合は発生しません。
Postgres の分離
Postgres テストの場合は、ワークツリー/リポジトリごとに一意のデータベースを使用します:
# Create a unique database for this worktree
PGPASSWORD=payload psql -h localhost -p 5433 -U payload -c "CREATE DATABASE payload_worktree1;"
# Run tests against that database
POSTGRES_URL="postgres://payload:payload@localhost:5433/payload_worktree1" \
PORT=3150 pnpm test:e2e query-presets --max-failures=1
または、カスタムスキーマのアプローチを使用します (別途 DB は不要です):
# Tests will use a separate schema within the same database
PAYLOAD_DATABASE=postgres-custom-schema PORT=3150 pnpm test:e2e query-presets
分離が重要な理由
| シナリオ | ポートの競合? | DB の競合? |
|---|---|---|
| MongoDB in-memory | あり (同じポート) | なし (各実行が独自のサーバーを持つ) |
| Postgres | あり (同じポート) | あり (同じテーブル) |
| 複数のワークツリー | あり | あり (Postgres のみ) |
解決策: ポートの競合を避けるために、常に PORT を設定してください。Postgres の場合は、データベースも分離してください。
よくある間違い
開発サーバーが起動していない、またはポートが間違っている:
テストは PORT 環境変数(デフォルトは 3000)を読み取ります。2つのアプローチがあります:
オプション A: 分離されたポートを使用する(並列テストスイートに推奨)
# Run tests on custom port - script handles everything
PORT=3105 pnpm test:e2e test/query-presets/e2e.spec.ts
オプション B: 既存のポートを強制終了してデフォルトを使用する
# Kill all dev server ports
lsof -ti:3000,3001,3002,3003,3004,3005,3006,3007,3008,3009 | xargs kill -9 2>/dev/null
# Tests use 3000 by default
pnpm test:e2e test/query-presets/e2e.spec.ts
間違ったテストスイートが実行されている: 各テストスイート(fields、query-presets、localization など)には独自の Payload 設定があります。間違った開発サーバーが起動していると、テストは失敗するか予期しない動作をします。
修正前にテストを実行していない: 変更を加える前に、テストが実際に失敗することを必ず確認してください。テストが成功する場合は、変更しないでください。
ヘルパーファイルを確認していない:
test/*/helpers/ 内のテストヘルパーには、複数のテストに影響する共有セレクターが含まれていることがよくあります。
ポップアップの操作が欠けている: 要素がポップアップ内に移動した場合、テストはまずポップアップを開く必要があります。
確認ダイアログを忘れている: 削除アクションには確認モーダルが追加されることがよくあります - テストは確認ステップを処理する必要があります。
プレースホルダーテキストの変更: 検索プレースホルダー、ボタンラベル、その他のテキストコンテンツは変更される可能性があります。
モーダルのスラッグの不一致:
削除や確認のアクションを行う際、モーダルのスラッグが変更される可能性があります。<Modal> やドロワーコンポーネントに渡される実際の slug プロップについては、コンポーネントのコードを確認してください。
例: QueryPresetBar の変更
古い構造(チップ):
// Direct buttons visible
await page.click('#create-new-preset')
await page.click('#edit-preset')
await page.click('#delete-preset')
await page.click('.chip__remove') // clear
新しい構造(ポップアップドロップダウン):
// Open popup first
await page.click('#select-preset')
// Then click menu items
await page.click('.popup-button-list__button:has-text("Create New")')
await page.click('.popup-button-list__button:has-text("Edit")')
await page.click('.popup-button-list__button:has-text("Delete")')
// Clear uses dedicated button
await page.click('.query-preset-bar__clear')