migrate-cypress-to-codeceptjs について
Cypress の E2E テストプロジェクトを CodeceptJS 4 に移行するためのスキルです。プロジェクトの調査、設定や共有コードの変換、テストファイルの移行を段階的に案内し、移行中も Cypress を残します。
主な機能
- 01Cypress テストと共有コードの調査
- 02Cypress と CodeceptJS の設定対応
- 03カスタムヘルパーとページオブジェクトの移行
- 04テストファイルの変換パターン
- 05未対応または制限のある機能の提示
活用シーン
- 01Cypress プロジェクト移行の計画
- 02Cypress カスタムコマンドとページオブジェクトの移植
- 03Cypress E2E テストファイルの変換
- 04移行対象外のコンポーネントテストの特定
MCP 対応
個々のテストファイルの移行は `writing-codeceptjs-tests` スキルに引き継ぎます。このスキルは MCP 経由で実ブラウザーを操作し、手順を検証します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ codeceptjs/ skills --skill migrate-cypress-to-codeceptjsインストール後、Claude Code で/migrate-cypress-to-codeceptjsを入力して読み込まれたか確認します。
詳細
Cypress から CodeceptJS 4 への移行
Cypress と CodeceptJS は、ブラウザーのエンドツーエンドテストという目標を共有していますが、根本的に異なる点が3つあります。
- ステップのキューイングとコマンドチェーン。 CodeceptJS はすべての
I.*呼び出しを内部レコーダーに自動でキューイングするため、テストは同期処理のように見えます。awaitが必要なのは取得処理(await I.grabTextFrom(...))のみです。状態を引き継ぐための.then()チェーンはありません。 - 同梱ブラウザーではなく、ヘルパーを使用。
I.*は設定済みのヘルパーに処理を振り分けます。Cypress は設計上、単一のブラウザーに限定されますが、CodeceptJS では Playwright(Cypress からの移行に推奨。Chromium との互換性があり、クロスブラウザーにも対応)、Puppeteer、または WebDriver を選択でき、テストコードはそのまま使えます。 - 第一級の抽象化。 ページオブジェクト、複数ユーザーの
session(...)、authプラグイン、カスタムヘルパーが組み込まれています。Cypress プロジェクトでは、これらを場当たり的に実装することが増えますが、移行時にフレームワークの慣用的な方法へ統合できます。
信頼できる参照先:node_modules/codeceptjs/docs/(basics、locators、playwright、custom-helpers、pageobjects)。
実行する条件
次のいずれかに該当する場合です。
- リポジトリのルートに
cypress.config.{js,ts,mjs}がある。 cypress/ディレクトリにe2e/、support/、fixtures/、plugins/、またはcomponent/のサブディレクトリがある。devDependenciesにcypressが記載されている。- テストコードで
cy.*(cy.visit、cy.get、cy.contains、cy.session、cy.intercept、cy.request、cy.task、cy.fixture、cy.origin、cy.mount)を呼び出す、Cypress.Commands.add(...)を使用する、またはCypress.env(...)を読み取る。 - ユーザーが「Cypress から移行/移植/変換」と指定する。
移行対象外
事前に明確に説明してください。
- コンポーネントテスト(
cy.mount、cypress/component/)— CodeceptJS は E2E のみを対象とします。コンポーネントテストには Cypress を引き続き使用するか、Playwright Component Testing / Vitest + Testing Library に移行してください。 cy.intercept('POST', '/api').as('save')→cy.wait('@save')— 最も近い代替手段は Playwright のI.mockRoute()です(エイリアスはなく、cy.wait('@x')に相当するものもありません)。ネットワークイベントではなく、UI の結果(I.waitForText('Saved'))を基準に待機してください。- Cypress Cloud / タイムトラベルデバッガー — ステップごとのアーティファクトを提供する
aiTraceプラグインと、ダッシュボード向けの@testomatio/reporterに置き換えます。 cy.origin()を使った複数オリジンのフロー — サポートは限定的です。対応できない点を記録し、それを踏まえて計画してください。
ワークフロー
フェーズを順番に実行してください。回帰を二分探索で特定できるよう、各境界でコミットしてください。
1. Cypress プロジェクトの棚卸し
何かに手を加える前に、全体像を把握します。2段階で行います。
プロジェクトの構成 — コストの予測材料を grep / wc -l で調べます。
cypress.config.{js,ts,mjs}— 使用中のキーcypress/e2e/**/*.cy.{js,ts}— spec の数cypress/fixtures/— 数とファイル名cypress/support/{e2e,commands}.{js,ts}— 必ず存在します。全文を読みますcypress/plugins/— レガシーなプリプロセッサー/タスクの配線cypress/component/とcy.mount(— ユーザーに対象外として伝えますcy.intercept(、cy.task(、cy.session(、cy.origin(、Cypress.Commands.add(、cy.fixture(の出現数を数えます。それぞれ既知の置き換えパターンに対応します
共有ロジックと共有ロケーター — Cypress に組み込みのページオブジェクトはありませんが、テストスイートには共有の抽象化が蓄積します。テストファイルに手を加える前に見つけてください。
- カスタムコマンド —
cypress/support/commands.{js,ts}にあるCypress.Commands.add('<name>', fn)をすべて確認します。名前 → 引数 → 本体をリストにします。ほぼすべてのテストスイートにあります(cy.login、cy.seedData、cy.dragRowToなど)。 - ページオブジェクト形式のモジュール —
cypress/support/、cypress/pages/、cypress/page-objects/、cypress/helpers/、cypress/objects/、cypress/po/、および cypress ディレクトリ外のpages//pageObjects/を調べます。セレクターバンドル({ usernameField: '#user', submitBtn: '[data-cy=submit]' })をエクスポートするモジュール、cy.*を呼び出すメソッド(login(user, pwd)、goToProfile())をエクスポートするモジュール、セレクターをフィールドとして持つクラスを探します。 - 共有セレクター定数 —
selectors.{js,ts}/locators.{js,ts}という名前のファイル、または文字列のみをエクスポートするモジュールを探します。spec を grep して、繰り返し使われているcy.get('[data-cy=...]')文字列を調べます。重複は抽象化の候補です。 - ユーティリティヘルパー — 日付フォーマッター、URL ビルダー、API ラッパー(
api.js、helpers.js、utils.js)。 - グローバルフック —
cypress/support/e2e.{js,ts}のbeforeEachブロック、Cypress.on('uncaught:exception', ...)など。
簡潔な棚卸しを作成します。すべての共有抽象化について、現在の Cypress 内の場所と、予定している CodeceptJS の移行先(フェーズ 4 の移行先一覧を参照)を記載します。コードを書く前にユーザーに確認してもらいます。
2. Cypress と並行して CodeceptJS をインストールする
npx codeceptjs init を実行し、Playwright ヘルパーを選択します。Cypress はまだ削除しないでください。移行中は両方を並行して実行するため、半分だけ変換したテストスイートでもテストをパスした状態に保てます。
3. 設定を移行する
cypress.config.{js,ts} のキーを codecept.conf.{js,ts} に対応付けます。
| Cypress | CodeceptJS 4(Playwright ヘルパー) |
|---|---|
e2e.baseUrl | helpers.Playwright.url |
viewportWidth / viewportHeight | helpers.Playwright.windowSize: '1280x720' |
defaultCommandTimeout | helpers.Playwright.waitForTimeout |
video | helpers.Playwright.video: true |
screenshotOnRunFailure | on: 'fail' を指定した screenshot プラグイン |
retries | トップレベルの retry: N |
env.* / Cypress.env('X') | process.env.X |
setupNodeEvents / cy.task | カスタムヘルパーまたは bootstrap / teardown |
4. 共有抽象化を移行する
ここが基盤です。spec を書き換える前に行ってください。必要な動詞(I.doSmth(...))がすでにあれば、spec の書き換え量を減らせます。
Cypress カスタムコマンドの絶対ルール。 すべての Cypress.Commands.add('<name>', fn) をカスタムヘルパーのメソッドにします。操作の種類に応じて、コマンドを2つのヘルパーに分けます。アクセス方法も適切な API も異なるためです。
WebExtra(lib/helpers/WebExtra.js)はブラウザー操作のコマンドに使います。開いているページ、DOM、evaluate、初期化スクリプト、ストレージ、ネットワークレスポンスの待機が必要なものが対象です。this.helpers['Playwright'].page/.browserContextにアクセスします。ApiExtras(lib/helpers/ApiExtras.js)は純粋な HTTP コマンドに使います。プログラムによるログイン、データの投入/削除、API に対する CRUD が対象です。this.helpers['REST'](またはGraphQL)にアクセスします。REST ヘルパーの設定についてはnode_modules/codeceptjs/docs/api.mdを参照してください。 Cypress の各コマンドと同じ名前の非同期メソッドを 1 つずつ用意します。たとえば、cy.doSmth(arg)→I.doSmth(arg)です。codecept.conf.{js,ts}のhelpersの下に両方のヘルパーを登録してください。
API 処理で this.helpers['Playwright'].browserContext.request.* を呼び出してはいけません。 これを使うと REST + JSONResponse のスタックを迂回してしまい、ステップのログ記録も、I.seeResponseCodeIsSuccessful によるアサーションも、共有ヘッダーも使えなくなります。また、同じ HTTP メソッドがヘルパー間で分断されてしまいます。API でブラウザーと同じ認証が必要な場合は、設定ファイルの先頭で一度だけ Cookie を共有してください。
import { setSharedCookies } from '@codeceptjs/configure'
setSharedCookies()
…または、トークンベースの認証には REST ヘルパーに defaultHeaders を設定するか、テストごとに I.amBearerAuthenticated(secret(token)) を使用してください。3 つのパターンすべてを api.md で説明しています。
WebExtra の例 — ブラウザーを操作するコマンド(ここでは login が UI フォームを操作します。API を使う場合の例は、以下の ApiExtras をご覧ください)。
import Helper from '@codeceptjs/helper'
import fs from 'node:fs/promises'
export default class WebExtra extends Helper {
async login(user, password) {
const { page } = this.helpers['Playwright']
await page.goto('/login')
await page.getByLabel('Email').fill(user)
await page.getByLabel('Password').fill(password)
await page.getByRole('button', { name: 'Sign In' }).click()
await page.waitForURL(/\/dashboard/)
}
async setLocalStorage(key, value) {
const { page } = this.helpers['Playwright']
await page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value])
}
async stubWindowOpen() {
const { page } = this.helpers['Playwright']
await page.addInitScript(() => {
window.__lastOpenUrl = null
const orig = window.open
window.open = (url, ...rest) => {
window.__lastOpenUrl = url
return orig ? orig.call(window, 'about:blank', ...rest) : null
}
})
}
async writeJsonFile(filePath, data) {
await fs.writeFile(filePath, JSON.stringify(data, null, 2))
}
}
ApiExtras の例 — REST ヘルパーに振り分けられる HTTP のみを扱うコマンド:
import Helper from '@codeceptjs/helper'
export default class ApiExtras extends Helper {
async loginViaApi(email, password) {
const REST = this.helpers['REST']
await REST.sendPostRequest('/login_ajax', { email, password, remember: false })
}
async seedCourse(courseData) {
const REST = this.helpers['REST']
const { data } = await REST.sendPostRequest('/course', courseData)
return data
}
}
ヘルパーのコードスタイル — どちらにも適用されます。
- すべての
import文はファイルの先頭に配置してください。メソッド内でconst fs = await import('node:fs/promises')とするのは禁止です。 - 組み込みアサーション(API では
I.seeResponseCodeIsSuccessful、ブラウザーではI.seeElement)、ExpectHelper、またはcodeceptjs/assertionsのファクトリーを使用してください。if (cond) throw new Error('...')は絶対に使用しないでください。 失敗は適切なアサーションエラーとして表示される必要があります。node_modules/codeceptjs/docs/assertions.mdをご覧ください。 WebExtraにユーザー名をキーとするセッションキャッシュのマップが増えている場合は、authプラグインを再実装していることになります。そこで作業を止め、セッションの再利用はauthプラグイン(フェーズ 8)に任せてください。ヘルパーではloginViaApi/loginを公開し、キャッシュはプラグインが処理します。
cy.window().then(...)、cy.wrap(...)、または命令型の DOM 操作を使用していた Cypress コードは、WebExtra 内の page.evaluate(...) にそのまま置き換えられます。cy.request(...) を使用していた Cypress コードは、ApiExtras 内の REST.sendXxxRequest(...) に置き換えられます。
フェーズ 1 のインベントリーにあるその他の移行先:
-
Cypress のページオブジェクト形式のモジュール →
pages/配下の CodeceptJS ページオブジェクトクラス。慎重に移植してください。元のモジュールにあったメソッドだけを残し、移行中に新しいラッパーを作らないでください。セレクターのまとまりはthis.fields = { ... }にし、メソッドの先頭でconst { I } = inject()を呼び出して、I.fillField、I.click、およびWebExtra/ApiExtrasヘルパーが新たに提供するI.*の動詞を使うように書き換えます。codecept.conf.{js,ts}のincludeの下に登録すると、ページオブジェクトがシナリオに自動注入されます。避けるべきページオブジェクトのアンチパターン(元の Cypress コードにすでに含まれていたものを除く):
- アサーションメソッド(
checkTitle() { I.seeElement(...) }など)— ページオブジェクトにはfillFormやsubmitOrderのようなアクションを表す動詞を置き、アサーションはテスト内に記述してください。 - 単一の
I.click/I.see*/I.grabTextFromを呼び出すだけの一行ラッパー — テストからI.*を直接呼び出す場合と比べて、ラッパーを使う利点はありません。 - 1 つのテストでしか使われないメソッド — ステップはテスト内に残してください。ページオブジェクトは再利用のためにあります。
- メソッド内の
if (cond) throw new Error(...)— 代わりにI.see*、I.seeNumberOfElements、ExpectHelper、またはcodeceptjs/assertionsのファクトリーを使ってください。
- アサーションメソッド(
-
共有セレクター定数 → 該当するページオブジェクトのフィールドにします。独立した
selectors.jsは作らないでください。 -
ブラウザーに触れない純粋なユーティリティモジュール → 通常の ES モジュールとして、必要な場所でインポートします。
-
グローバルフック → テスト内では CodeceptJS の
Before/BeforeSuite、一度限りのセットアップでは設定ファイルのbootstrap/teardownを使用します。
次に進む前に、次のコマンドで確認してください:npx codeceptjs check -c <config> が成功し、npx codeceptjs list -c <config> にすべての Cypress コマンド名が、WebExtra または ApiExtras のどちらかが提供する I.* アクションとして表示されること。
5. spec ファイルを変換する
フェーズ 4 の抽象化を活用しながら、1 ファイルずつ進めます。spec ごとの作業は writing-codeceptjs-tests スキルに任せてください。このスキルは MCP 経由で実際のブラウザーを操作し、各ステップを検証してからコミットします。
| Cypress | CodeceptJS 4 |
|---|---|
File *.cy.{js,ts} | *_test.{js,ts} |
describe('X', () => { ... }) | ファイルの先頭に Feature('X') を記述し、ファイルごとに Feature は 1 つ |
it('Y', () => { ... }) | Scenario('Y', ({ I }) => { ... }) |
beforeEach(() => { ... }) | Before(({ I }) => { ... }) |
afterEach(() => { ... }) | After(({ I }) => { ... }) |
before(...) / after(...) | BeforeSuite(...) / AfterSuite(...) |
cy.visit('/x') | I.amOnPage('/x') |
cy.login(u, p)(カスタムコマンド) | I.login(u, p)(WebExtra 由来) |
イテレーション — テスト、ページオブジェクト、ヘルパーでは、I.* 呼び出しを含むループに for...of を使用してください。Array.prototype.forEach は使用しないでください。.forEach はイテレーションコールバックの戻り値を無視するため、内部の await は外側の関数をブロックしません。その結果、CodeceptJS レコーダーがステップを順不同でキューに追加したり、ループの完了前に Scenario を終了したりする可能性があります。for...of ならループが逐次処理され、書き換えずに後から await を追加できます。
for (const sort of testSort) {
I.click(locate(this.filterFormLabel).withText(sort))
}
for (const row of await I.grabWebElements('.row')) {
const text = await row.getText()
I.expectNotEmpty(text)
}
バッチごとに:npx codeceptjs dry-run --steps -c <config> を実行します。すべての Scenario を読み込み、すべての I.* 呼び出しを解決しますが、ブラウザーは起動しません。タイプミス、インポート漏れ、include に含まれていないページオブジェクト、存在しない動詞を数秒で検出できます。実際の実行に移る前に修正してください。
その後、バッチを実行します:npx codeceptjs run --steps -c <config>。
- 最初の実行は、ほぼ必ず失敗します。ロケーターのずれ、元のフレームワークが独自のリトライで隠していたタイミングの問題、認証/セッションの違い、データに関する前提などが原因です。想定内です。修正も移行作業の一部です。
- 失敗するたびに
debugging-codeceptjs-testsを呼び出し、その場で修正してください(ブレークポイント、実際のページの検査、検証済みの修正)。闇雲な書き換えや、retryによる問題の隠蔽はしないでください。 - バッチの完了は、dry-run が問題なく終わったときではなく、実行が成功したときです。
6. ロケーター
すべてのロケーターにコンテキストを指定してください。 各アクションの最後の引数で検索範囲を絞り込みます。たとえば、I.click('Save', '.toolbar')、I.fillField('Email', '[email protected]', '#login-form')、I.click({ role: 'button', name: 'Delete' }, '.modal') のようにします。短いセマンティックロケーターや ARIA ロケーターにコンテキストを組み合わせると、長くて範囲指定のないロケーターよりも優れています。ページの内容に沿った読みやすい記述になり、重複するラベルを区別でき、記述を長くせずに済み、マークアップの変更にも強くなります。以下の表のすべての行に適用してください。元のフレームワークのチェーンは通常、領域 と ユーザーに見える内容 に簡潔に分けられます。
cy.get(sel).within(() => ...) と cy.get(parent).find(child) はどちらもコンテキスト引数に置き換えられます。Cypress チェーンの親セレクターはここに指定します。
CodeceptJS の優先順位 — 該当する中で最も優先度の高い方法を選び、コンテキストを追加してください。
- セマンティック文字列 — ボタンのテキスト、ラベル、プレースホルダー、リンクテキスト:
I.click('Save', '.toolbar')、I.fillField('Email', '[email protected]', '#login-form')。ほとんどのcy.contains(...)呼び出しを置き換えます。 単純な文字列はすでにaria-labelに一致するため、aria-label="Save"を持つアイコンのみのコントロールにはI.click('Save', <context>)を使用します。'aria-label=Save'や{ css: '[aria-label="Save"]' }は使用しないでください。 - ARIA ロール —
I.click({ role: 'button', name: 'Sign In' }, '#login-form')。 customLocatorプラグイン経由の$name— Cypress ユーザーは[data-cy=...]を使うことがよくあります。これらの属性はそのままにして、プラグインを有効にし、{ css: '[data-cy=submit]' }ではなくI.click('$submit', '.checkout')と記述できるようにします。locate()ビルダー —I.click(locate('button').withText('Edit').inside('tr').withText('Acme'))。多くの場合、I.click('Edit', locate('tr').withText('Acme'))のように分けるほうが適切です。- CSS/XPath — 最後の手段としてのみ使用します。
詳しくは writing-codeceptjs-tests の § Locators を参照してください。
7. アクション、アサーション、値の取得
| Cypress | CodeceptJS 4 |
|---|---|
cy.get(sel).click() | I.click(sel) |
cy.get(sel).type('x') | I.fillField(sel, 'x') |
cy.get(sel).clear() | I.clearField(sel) |
cy.get(sel).check() / .uncheck() | I.checkOption(sel) / I.uncheckOption(sel) |
cy.get(sel).select('A') | I.selectOption(sel, 'A') |
cy.get(sel).should('be.visible') | I.seeElement(sel) |
cy.get(sel).should('have.text', 'X') | I.see('X', sel) |
cy.get(sel).should('have.value', 'X') | I.seeInField(sel, 'X') |
cy.get(sel).should('have.length', 5) | I.seeNumberOfElements(sel, 5) |
cy.url().should('include', '/x') | I.seeInCurrentUrl('/x') |
cy.get(sel).invoke('text').then(t => ...) | const t = await I.grabTextFrom(sel) |
cy.getCookie('s') | const c = await I.grabCookie('s') |
await が必要なのは値の取得時のみです。通常のアクションは自動的にキューに追加されます。
8. セッションと認証
cy.session(id, setup, { validate }) と cy.request を使ったプログラムによるログインは、auth プラグインに置き換えます。セットアップ手順については codeceptjs-auth に引き継いでください。フェーズ 4 ですでに cy.login を WebExtra の I.login(...) に移植している場合、auth プラグインのロール定義では I.login(...) を呼び出すだけです。複数ユーザーのシナリオ(Cypress にはネイティブの同等機能がありません)では、codeceptjs/effects の session(...) を使用してください。
9. フィクスチャ、リクエスト、タスク
| Cypress | CodeceptJS 4 |
|---|---|
cy.fixture('users.json') | import users from './fixtures/users.json' with { type: 'json' } |
cy.request('POST', '/api/x', body) | REST ヘルパー経由で await I.sendPostRequest('/api/x', body) を使用します。再利用可能なフローの場合は、フェーズ 4 の ApiExtras ヘルパーでラップします |
cy.task('seedDB') | ApiExtras のメソッド(HTTP を使用する場合)、専用のヘルパー、または bootstrap / teardown |
REST ヘルパーの認証:@codeceptjs/configure の setSharedCookies() はブラウザーセッションを REST と共有するため、両方で同じユーザーがログインした状態になります。代わりに、静的トークンには defaultHeaders を設定するか、テストごとに I.amBearerAuthenticated(secret(token)) を使用できます。Zod を使った JSONResponse アサーション(I.seeResponseCodeIsSuccessful、I.seeResponseContainsKeys、I.seeResponseMatchesJsonSchema)など、設定項目の全容については node_modules/codeceptjs/docs/api.md を参照してください。
10. ネットワークのモック
cy.intercept(url, handler) は、I.mockRoute(url, route => route.fulfill({ ... }))(Playwright)に置き換えます。I.stopMockingRoute(url) で無効にできます。cy.wait('@alias') に相当する機能はありません。ネットワークイベントではなく、UI の結果(I.waitForText、I.seeElement)を基準に待機してください。
11. Cypress の廃止
すべての spec の移植が完了し、CI が成功してから、cypress/ と cypress.config.* を削除し、devDependencies から cypress を取り除き、Cypress の CI ジョブを削除してください。
検証
npx codeceptjs check -c <config>— 設定、ヘルパー、プラグインが正常であることを確認します。npx codeceptjs list -c <config>— 移植したすべての Cypress コマンドがWebExtraまたはApiExtrasのI.*アクションとして表示されること、すべてのページオブジェクトのメソッドが表示されることを確認します。npx codeceptjs dry-run --steps -c <config>— すべての Scenario が読み込まれることを確認します。- 全体を実行します:
npx codeceptjs run --steps -c <config>。初回実行では失敗が想定されます。debugging-codeceptjs-testsスキルを使って、一つずつ修正してください(retryや、やみくもな書き換えはしないでください)。変換したテストスイート全体が成功して初めて、移行が完了します。 codeceptjs-run-analysisに引き継ぎ、output/trace_*/のアーティファクトを調査します(aiTraceプラグインが有効になっている必要があります)。grep -r "cy\." cypress/—cypress/を削除する前に、出力が空であることを確認します。
関連スキル
writing-codeceptjs-tests— 仕様ごとの書き換え手順(MCP 主導の検証済みステップ)debugging-codeceptjs-tests— 最初の全件実行で失敗したテストすべてに使用しますcodeceptjs-auth—cy.session()とプログラムによるログインを置き換えますcodeceptjs-fundamentals— 移行後に実行して、配線を確認します- リファレンスドキュメント:
node_modules/codeceptjs/docs/(basics、playwright、locators、custom-helpers、api、assertions、pageobjects、data、sessions、effects)