test-migration について
test-migrationは、既存のカバレッジとCIの安全網を保ちながら、テストスイートを新しいフレームワークへ段階的に移行するチーム向けのスキルです。新旧スイートを並行実行し、網羅性と安定性を確認してから旧フレームワークを終了する方針です。
主な機能
- 01優先順位に沿った段階的なテスト移行
- 02CIでの新旧スイート並行実行
- 03共通テスト基盤の先行移行
- 04フレームワーク間の変換ガイド
- 05変換ツールによる初回移行
- 06カバレッジと安定性に基づく終了判断
活用シーン
- 01SeleniumテストからPlaywrightへの移行
- 02CypressテストからPlaywrightへの移行
- 03JestまたはMochaからVitestへの移行
- 04大規模な移行中にCIの安全網を維持
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill test-migrationインストール後、Claude Code で/test-migrationを入力して読み込まれたか確認します。
同梱ファイルreferences/framework-guides.mdreferences/parallel-ci.md
詳細
クイックルート
| 移行元 → 移行先 | 参照先 | 最初に使うツール |
|---|---|---|
| Selenium → Playwright | Translation Patterns + references/framework-guides.md | 手動で記述。信頼できるcodemodはありません |
| Cypress → Playwright | Translation Patterns + references/framework-guides.md | cy2pw web converter / community CLI(ツール一覧を参照) |
| Protractor → Playwright | references/framework-guides.md(緊急 — 2023年にEOL) | 手動で記述。by.model/by.bindingを対応付けます |
| Jest → Vitest | references/framework-guides.md | jest.→vi.のsed処理を行い、その後検証します |
| Mocha → Vitest | references/framework-guides.md | chai-matcher codemodを使い、その後検証します |
| 任意の移行、テスト100件以上 | Migration Workflow + Parallel Running Strategy | 常にCIで並行実行します |
確認事項
まず.agents/qa-project-context.mdを確認します。存在する場合は、そこに記載された内容をコンテキストとして使用し、回答済みの質問は省略します。
現在の状況:
- どのフレームワークからどのフレームワークへ移行しますか?(翻訳表と該当するガイドのセクションが決まります)
- テストはいくつあり、現在の不安定率/スキップ率はどの程度ですか?(不安定なテストを移行すると、不安定さも再現されます。スキップされたテストは、移行する価値がない場合があります)
- 現在のカバレッジ数値はありますか?(同等性を追跡する際の基準になります)
テストインフラストラクチャ:
- どのようなサポートインフラストラクチャがありますか?(ページオブジェクト、カスタムコマンド、フィクスチャ、データファクトリーなど。これらはテストより先に移行します)
- 認証/セッションはどのように処理していますか?(ログインフローは、気付かないうちに壊れる最も一般的な箇所です。「失敗モード」を参照してください)
- どのCIパイプラインでテストを実行していますか?また、両方のフレームワークを同時に実行できますか?(並行実行は必須です)
制約:
- スケジュールにどの程度のプレッシャーがありますか?(緊急の場合は、ProtractorのようなフレームワークのEOLが理由です。余裕がある場合は、モダナイゼーションとして進めます)
- 移行期間中に両方のテストスイートを実行する予算はありますか?(一時的にCIコストが倍になります)
- チームは移行先のフレームワークにどの程度慣れていますか?(トレーニングの不足に応じて、ワークショップやペアリングの計画が必要になります)
基本原則
1. 一括ではなく段階的に移行する
一度にすべてのテストを書き直す一括移行は、最もリスクの高い方法です。テスト開発が何週間も止まり、検証されていないテストが大量に一度に追加され、置き換えができる前に実績のある安全網が取り除かれてしまいます。必ずテストを1件ずつ、モジュールを1つずつ移行してください。
2. 同等性が確認できるまで並行実行する
新しいテストスイートが古いものと同等以上のカバレッジを提供するまで、CIで両方のテストスイートを実行します。古いテストスイートは安全網です。ブロッキングにはしなくても、残しておきます。新しいテストスイートが複数のスプリントにわたって実際のリグレッションを検出するまで、廃止しないでください。
3. 価値の高いテストから移行する
重要なユーザージャーニー、失敗頻度の高いテスト(より優れたフレームワークの利点を最も得られるもの)、リスクの高い領域から始めます。価値の低いテストは後回しにします。移行する価値がないものもあり、その判断を記録しておけば問題ありません。
4. 単に書き換えるのではなく、移行中にパターンをモダナイズする
質の悪いSeleniumテストを質の悪いPlaywrightテストに書き換えても、機会を無駄にするだけです。各テストについて、「移行先のフレームワークで一から書くなら、どう書くだろうか?」と考えてください。ユーザー向けのロケーター、自動待機、フィクスチャなどを活用します。この原則は、AI codemodの出力には特に当てはまります(「アンチパターン」を参照)。
移行ワークフロー
一般的な複数スプリントにわたる移行のための6つのフェーズです。チェックリストは本文中で使用し、詳細なコードはリファレンスにまとめます。
フェーズ1 — 既存のテストスイートを監査する(第1週)。 カテゴリ別のテスト数、スキップ率/不安定率、計測している場合はカバレッジ、実行時間、ページオブジェクト/ユーティリティファイル、カスタムプラグイン、CIのステージを数えます。次に、各テストを分類します。Critical(収益につながるフロー)→ High(中核的なジャーニー)→ Medium(二次的なもの)→ Low(管理/エッジケース)→ Skip(無効化済み、重複、廃止済み)。この優先順位が、その後のすべての作業の指針になります。
フェーズ2 — 移行先のフレームワークをセットアップする(第1~2週)。 旧フレームワークと並行してインストールします。設定ファイル(playwright.config.ts / vitest.config.ts)、独立したテストディレクトリ、ブロッキングなしのCIステージ、セットアップを検証するスモークテスト1件、既存の形式に合わせたレポーター、共有環境変数/シークレットを用意します。
フェーズ3 — 共有インフラストラクチャを移行する(第2~3週)。 テストより先に、次の順序でインフラストラクチャを移行します。ベースページオブジェクト/テストベース → 認証ヘルパー → APIクライアントヘルパー → 共通ページオブジェクト(ナビゲーション/ヘッダー/フッター)→ データファクトリー → カスタムアサーション → 機能固有のページオブジェクト(対応するテストも含む)。移行したテストでログインフローをスキップできるよう、ここで storageState を取得します。
フェーズ4 — 優先順位に沿ってテストを移行する(第3~8週以降)。 テストごとに、旧テストを読んで実際に何を検証しているのかを理解する → 最新のパターンを使って新しいテストを一から作成する(1行ずつ翻訳しない)→ ローカルで成功するまで実行する → CIで成功するまで実行する → 旧テストに「移行済み」のタグを付ける(削除しない)→ 並行実行で1スプリントにわたって成功した後、旧テストを削除します。
フェーズ5 — CIで並行実行する(全期間)。 すべてのパイプラインで両方のテストスイートを実行します。新しいテストスイートが同等の状態になるまで、旧テストスイートはブロッキングなし(continue-on-error: true)のままにします。その後、新しいテストスイートをブロッキング対象に切り替え、旧ジョブを削除します。GitHub Actionsのワークフロー全体については、references/parallel-ci.md を参照してください。
フェーズ6 — 旧フレームワークを廃止する(最終段階)。 同等性と安定性を確認できた後に限ります。重要度がCritical/Highのテストがすべて移行済みであること、新しいテストスイートがCIで2スプリント以上連続して成功していること、新しいテストスイートの不安定さが旧テストスイート以下であること、カバレッジの比較で後退がないこと、チームが2スプリント以上にわたって新しいフレームワークでテストを書いていることを確認します。その後、package.json から旧依存関係を削除し、旧テストファイルを削除(無効化するだけではなく削除)し、新しいテストスイートのみを実行するようCIを更新し、ドキュメントを更新します。
自動移行ツール
AIの魔法のボタンはありません。パスに応じて適切な初回変換ツールを選び、その後 references/framework-guides.md を使って手作業で調整します。
| ツール | 使用する場面 | 信頼しない点 |
|---|---|---|
| cy2pw web converter(demo.playwright.dev/cy2pw) | Cypress→Playwright、単純なスペック。公式ツールで、決定論的にブラウザーUIを変換 | カスタムコマンド、POMの規約、フィクスチャのセットアップ |
@11joselu/cypress-to-playwright(コミュニティ製CLI、npx @11joselu/cypress-to-playwright <dir>) | ディレクトリ内のCypress→Playwright一括初回変換 | タイミングに依存するもの全般 — すべてのファイルをレビューする |
| AIエージェント(Claude Code、Cursor) | カスタムコマンドやフィクスチャの変換など、コンバーターでは対応できない部分 | 完成したテストとして扱わない — 出力は初回案にすぎない |
Playwright 1.59+ agentic CLI(npx playwright trace、--debug=cli、AI最適化されたa11yスナップショット) | 移行後、失敗した移行済みテストのトリアージ | 移行そのもの — これらはデバッグツールであり、コンバーターではない |
避けること:
npx playwright migrate— Playwright組み込みのCLIコマンドにそのようなものはありません。この指示はターミナルで失敗します(2026年6月に確認済み)。上記のcy2pwまたはコミュニティ製CLIを使用してください。
ゴールデンリファレンスの手順。 AIやcodemodでテストスイートを一括処理する前に、代表的なテストを1件、最初から最後まで手作業で移行します。これをチームの標準パターンとしてコミットします(例:e2e/_golden/login.spec.ts)。これを少数例のリファレンスとしてAIに渡し、人間のレビュアーにも参照先として示します。その後の移行はすべてこのファイルを基準にします。AIの出力とレビュアーの一貫性を保つうえで、最も効果の高い方法です。
翻訳パターン
ロケーターの対応表
| 旧パターン | 新しいパターン(Playwright) | 備考 |
|---|---|---|
By.id('submit-btn') | page.getByRole('button', { name: 'Submit' }) | ロールベースを優先します |
By.css('.nav-item.active') | page.getByRole('link', { name: 'Dashboard' }) | ユーザーに表示されるテキストを使用します |
By.xpath('//div[@class="modal"]') | page.getByRole('dialog') | ARIA ロールのほうが安定しています |
By.css('[data-testid="user-menu"]') | page.getByTestId('user-menu') | 代替手段として testid を使用します |
cy.get('.product-card').first() | page.getByRole('article').first() | セマンティック要素を優先します |
cy.contains('Add to cart') | page.getByRole('button', { name: 'Add to cart' }) | 具体的なロールのほうが適しています |
element(by.model('username')) | page.getByLabel('Username') | Angular の model をラベルに置き換えます |
上記のロール名とラベル名は例です。アプリで実際に表示されるアクセシブル名に置き換えてください。
待機戦略の対応表
| 旧パターン | 新しいパターン(Playwright) | 備考 |
|---|---|---|
Thread.sleep(3000) | (完全に削除) | Playwright が自動で待機します |
WebDriverWait(driver, 10).until(visible) | (完全に削除) | アクション時に自動で待機します |
cy.wait(2000) | (完全に削除) | アサーション時に自動で待機します |
cy.wait('@apiCall') | page.waitForResponse(/\/api\/data/) | 明示的なネットワーク待機です(アクションの前に Promise を開始してください) |
browser.wait(EC.presenceOf(...)) | await expect(locator).toBeVisible() | Web ファーストのアサーションです |
implicitlyWait(10, SECONDS) | (削除 — config で設定) | config で actionTimeout を使用します |
ポーリングを行う FluentWait | await expect(locator).toHaveText('Done') | Web ファーストのアサーションは再試行します |
アサーションの対応表
| 旧パターン | 新しいパターン(Playwright) | 備考 |
|---|---|---|
assert element.is_displayed() | await expect(locator).toBeVisible() | 自動で再試行します |
cy.get('.msg').should('have.text', 'Done') | await expect(locator).toHaveText('Done') | 自動で再試行します |
expect(element.getText()).toBe('Done') | await expect(locator).toHaveText('Done') | 自動で再試行します |
cy.url().should('include', '/dashboard') | await expect(page).toHaveURL(/dashboard/) | 自動で再試行します |
assert len(elements) == 5 | await expect(locator).toHaveCount(5) | 自動で再試行します |
設定の対応表(Cypress → Playwright)
| 旧(Cypress) | 新(Playwright) |
|---|---|
baseUrl | use.baseURL |
defaultCommandTimeout: 10000 | use.actionTimeout: 10000 |
pageLoadTimeout: 30000 | use.navigationTimeout: 30000 |
retries: { runMode: 2 } | retries: 2 |
video: true | use.video: 'on' |
screenshotOnRunFailure: true | use.screenshot: 'only-on-failure' |
特定の移行ガイド
各移行パスの主な違い、移行前後のコード、移行に関するチェックリストは、references/framework-guides.md に記載されています。概要は次のとおりです。
- Selenium → Playwright: 明示的な待機をすべて削除します(Playwright が自動で待機します)。文字列ロケーターを
getByRole/getByLabel/getByTestIdに置き換え、WebDriver セッションをBrowserContextに置き換えます。ログイン処理を再実装する代わりに、storageStateを取得します。 - Jest → Vitest(移行先は Vitest 4.x):ほぼ API 互換です。
jest.をvi.に置き換え、jest.config.jsをvitest.config.tsに変更し、Babel/ts-jest の変換処理を削除します(ただし、emotion/styled-components のマクロなどのカスタム Babel プラグインを使う場合は、esbuild/SWC 相当の処理を残します)。実行速度は 2~10 倍になる見込みです。段階的な切り替えには Vitest 4.1 のテストtagsを使用します。 - Cypress → Playwright(移行先は PW ≥ 1.50):大きな変更点は、コマンドキューから async/await への移行です。
cy.intercept()+cy.wait()はpage.route()+page.waitForResponse()に、カスタムコマンドはフィクスチャに置き換えます。1.52 のpage.route()の glob と Cookie ヘッダーの破壊的変更に注意してください(ヘッダーのオーバーライドではなく、browserContext.addCookies()で Cookie を設定します)。 - Mocha → Vitest: ほぼ 1 対 1 で移行できます。
describe/it/フックは変更不要です。chai のマッチャー(to.equal→toBe、to.deep.equal→toEqual、to.contain→toContain)とsinonをvi.fn()/vi.spyOn()に置き換えます。 - Protractor → Playwright: 2023 年から EOL となっているため、早急な対応が必要です。
waitForAngularを削除し、by.model/by.bindingをgetByLabel/getByText/getByTestIdに対応付け、onPrepareをglobalSetupに置き換えます。
並行実行戦略
移行中のカバレッジ比較
旧スイートと新スイートの同等性をスプレッドシートで追跡し、漏れがないようにします。この成果物が、廃止時の客観的な確認基準になります。
Feature Area | Old Suite Tests | New Suite Tests | Parity | Notes
Login/Auth | 8 | 8 | 100% | Complete
Dashboard | 12 | 7 | 58% | In progress
Search | 6 | 0 | 0% | Not started
Checkout | 15 | 15 | 100% | Complete
User Settings | 4 | 4 | 100% | Complete
Admin Panel | 20 | 0 | 0% | Low priority
--- | --- | --- | --- |
Total | 65 | 34 | 52% | On track for Q2
段階的な切り替えのスケジュール
200 件のテストスイートの場合、約 10 スプリントを計画します。セットアップとインフラ整備(スプリント 1~2)、クリティカルパスの移行(スプリント 3~5、約 50 件)、大規模な移行(スプリント 6~8)、クリーンアップと廃止(スプリント 9~10)です。同等性が高まるにつれて、旧スイートはブロッキングからノンブロッキングに切り替えます。スプリント 3 以降、新しいテストは新しいフレームワークでのみ作成します。
古いテストを削除するタイミング
古いテストはすぐに削除せず、「migrated」とタグ付けします。新しいテストが CI で 2 週間以上合格し、手動レビューで固有のアサーションが失われていないことを確認した後にのみ削除します。古いテストで検出できて新しいテストでは見逃す回帰があれば、まず新しいテストを強化します。
アンチパターン
一斉移行
3 か月間すべての開発を止めて、すべてのテストを一度に書き直す方法です。書き直しの間、チームは新しいテストを追加できず、カバレッジは固定され、新しいスイートは一度に導入されるまで CI でテストされません。
対策: 並行実行を伴う段階的な移行を行います。スプリントごとに 1 つのモジュールを移行します。移行中は両方のスイートを CI で実行します。初日から新しいフレームワークでテストを作成します。
最新化せずに翻訳する
Selenium のテストを Playwright のテストに行単位で翻訳し、明示的な待機、CSS セレクター、壊れやすいパターンをそのまま残す方法です。新しいフレームワークに移行しても、古い問題をすべて抱えたままになります。
対策: 移行先フレームワークのベストプラクティスを使って、各テストを書き直します。CSS の代わりに getByRole を使い、明示的な待機をなくし、beforeEach の代わりにフィクスチャを使います。移行は、すべてのテストを改善する機会です。
AI コードモッドを最終的な答えとして信頼する
Cursor、Aider、Continue、Claude Code、cy2pw/コミュニティ製コンバーターはいずれも機械的な翻訳を行いますが、品質にはばらつきがあります。「最新化せずに翻訳する」という落とし穴は、その出力では何倍にも深刻になります。
対策: まず 1 つのファイルにツールを適用し、元のファイルとの差分を確認します。正しく最新化されていれば、残りをまとめて処理します。単に翻訳しただけなら、独自のゴールデンリファレンスファイルを作成し、少数例のプロンプトとして渡します。各実行後、元のスイートと並行して実行します。結果が異なる場合はツールの処理が誤っているため、採用する前に原因を調査します。
並行実行をしない
新しいテストスイートの実力が証明される前に、古いテストスイートを廃止します。新しいテストスイートに古いテストスイートのテストが含まれていなかったため、リグレッションを見逃してしまいます。
対策: 同等のカバレッジに達した後も、少なくとも2スプリントは両方のテストスイートをCIで実行します。古いテストスイートは手軽な保険です。新しいテストスイートだけでリグレッションを検出できるようになってから、初めて廃止してください。
移行中にカバレッジを失う
「一部は重複していた」という理由で、古いテスト20件が新しいテスト15件になります。しかし、削除したテストが独自のシナリオをカバーしていたかどうかは誰も確認していません。
対策: カバレッジのスプレッドシートで、古いテストそれぞれを新しいテストの対応するテストに明示的にマッピングします。意図的に移行しない古いテストがある場合は、その理由を記録し、カバレッジが別の箇所で確保されていることを確認します。
不安定なテストをそのまま移行する
不安定な原因がテストの設計(状態の共有、タイミングの想定、非決定的なデータ)にある場合、Seleniumで不安定なテストはPlaywrightでも不安定になります。そのまま移行すると、不安定さまで再現されます。
対策: まず不安定さを診断して根本原因を修正し、その修正を組み込んだ新しいテストを作成します。テストを書き直す機会でもあるため、移行は不安定さを解消する絶好のタイミングです。実行時にテストごとのトリアージを行う方法については、test-reliabilityを参照してください。
チームのトレーニングをしない
チームがPlaywrightを使ったことがない状態で移行すると、チャンピオンがすべてを書き、他のチームメンバーは保守できなくなります。
対策: 開始前に2時間のワークショップを実施します。移行する最初の10件のテストは、チャンピオンとチームメンバーがペアプログラミングで作成します。新しいフレームワーク向けのチームスタイルガイドを作成します。移行したテストごとに、2人以上のレビュアーを必須にします。
失敗パターン
| 症状 | 考えられる原因 | 対策または確認事項 |
|---|---|---|
| 移行したテストで毎回ログインし直す/テストの途中でセッションが失われる | storageStateがない — ログインフローが引き継がれていない | globalSetupでstorageStateを取得し、テストごとに再利用します。cy.setCookie/driver.add_cookie → browserContext.addCookies() |
npx playwright migrateで「unknown command」エラーが発生する | そのような組み込みCLIは存在しない | cy2pwウェブコンバーターまたは@11joselu/cypress-to-playwrightを使用します |
| 移行したinterceptが一致しない | 1.52のglob変更で?/[]が除外された | 文字をエスケープするか、ルートを正規表現に書き換えます |
route.continue()で設定したCookieが無視される | route.continue()のCookieヘッダーは、指定した値ではなくCookieストアから読み込まれる | browserContext.addCookies()でCookieを設定します |
| 新しいテストはローカルでは成功するが、CIでは不安定になる | 古いタイミングの想定をそのまま置き換えている | waitForTimeout/明示的な待機を、ウェブファーストのexpect(...)アサーションに置き換えます |
| codemodの実行後にテスト数が減った | codemodがサポート対象外の構文を黙ってスキップした | 昇格させる前に、カバレッジシートと照らし合わせて機能領域ごとにテスト数を比較します |
検証
移行した成果物が機能することを、小さな確認から順に証明します。
- 移行したテストが成功する:
npx playwright test <migrated-file>(またはnpx vitest run <migrated-file>)が終了コード0で終了します。テストが成功するのは最低条件であり、完了ではありません。 - テスト数に漏れがない: カバレッジのスプレッドシートと照らし合わせて、新旧のテスト数を機能領域ごとに比較します。「New Suite Tests」列の数値が、実際に実行されたテスト数と一致します(機能領域ごとに
npx playwright test --list | wc -lを実行)。 - CIで両方のテストスイートが実行される: 並行実行ワークフローで、レガシージョブが
continue-on-error: trueとして設定され、新しいジョブがブロッキングになっていることを確認します。パイプラインの実行結果に両方のジョブが表示されます。 - リグレッションが発生していない: 古いテストを削除する前に、新しいテストスイートが2回以上連続するスプリントのCI実行で成功し続けることを確認します。
完了条件
- 移行の範囲が定義されています。最初に移行するテスト(重要なパス)、最後に移行するテスト(優先度の低いもの)、および意図的に移行しないテスト(それぞれの理由をカバレッジスプレッドシートに記録)が明確になっています。
- カバレッジの同等性を示すスプレッドシートで、すべての重要度の高い機能領域が100%となっており、対応付けられた旧テストと比べてテスト数が減少していません。
- 新しいテストスイートが成功した状態で、両方のフレームワークが2スプリント以上連続してCIで実行されています。
- 不安定さの根本原因、パターンの改善点、チームのトレーニング不足をまとめた移行の振り返り文書があります。
- 古いフレームワークが完全に削除されています。
package.jsonから依存関係を削除し、古いテストファイルを(無効化するだけでなく)削除し、CI設定を更新して新しいテストスイートのみを実行します。
参考ファイル(references/内)
- framework-guides.md — 5つすべての移行パス(Selenium→Playwright、Jest→Vitest、Cypress→Playwright、Mocha→Vitest、Protractor→Playwright)について、移行前後のコード全文と移行時の確認事項をまとめたチェックリストです。
- parallel-ci.md — 移行中に旧フレームワークと新しいフレームワークを並行して実行するためのGitHub Actionsワークフローです。
関連スキル
- playwright-automation — Selenium/Cypress/Protractorからの移行における、移行先フレームワークのベストプラクティスです。翻訳後に、Playwrightに適した書き方でコードを作成する際に参照してください。
- cypress-automation — Cypressへの移行、または移行元としてのCypressの詳細が必要な場合に参照してください。
- unit-testing — Jest→Vitestのパターン、モック、カバレッジ設定を詳しく解説します。
- test-reliability — 実行時に不安定なテストを1つ修復します。移行によって不安定さが見つかり、書き換えるのではなく原因を切り分ける必要がある場合に使用してください。
- selector-drift-recovery — 同じフレームワーク上でUIをリファクタリングした後に、セレクターを一括で再生成します。フレームワークの変更には対応しません。
- ci-cd-integration — 移行中に両方のテストスイートを実行するための並列CI設定です。
- qa-metrics — テスト数の同等性、不安定さの比較、カバレッジの差分など、移行の進捗を追跡します。
- test-strategy — 移行に関する判断は、四半期をまたぐ全体的なテスト戦略と整合させる必要があります。