mcpField
M
migrate-cypress-to-codeceptjs

migrate-cypress-to-codeceptjs

github.com/codeceptjsテスト・品質251 回インストール1 週間前に更新
リポジトリ

migrate-cypress-to-codeceptjs について

Cypress の E2E テストプロジェクトを CodeceptJS 4 に移行するためのスキルです。プロジェクトの調査、設定や共有コードの変換、テストファイルの移行を段階的に案内し、移行中も Cypress を残します。

主な機能

  1. 01Cypress テストと共有コードの調査
  2. 02Cypress と CodeceptJS の設定対応
  3. 03カスタムヘルパーとページオブジェクトの移行
  4. 04テストファイルの変換パターン
  5. 05未対応または制限のある機能の提示

活用シーン

  1. 01Cypress プロジェクト移行の計画
  2. 02Cypress カスタムコマンドとページオブジェクトの移植
  3. 03Cypress E2E テストファイルの変換
  4. 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つあります。

  1. ステップのキューイングとコマンドチェーン。 CodeceptJS はすべての I.* 呼び出しを内部レコーダーに自動でキューイングするため、テストは同期処理のように見えます。await が必要なのは取得処理(await I.grabTextFrom(...))のみです。状態を引き継ぐための .then() チェーンはありません。
  2. 同梱ブラウザーではなく、ヘルパーを使用。 I.* は設定済みのヘルパーに処理を振り分けます。Cypress は設計上、単一のブラウザーに限定されますが、CodeceptJS では Playwright(Cypress からの移行に推奨。Chromium との互換性があり、クロスブラウザーにも対応)、Puppeteer、または WebDriver を選択でき、テストコードはそのまま使えます。
  3. 第一級の抽象化。 ページオブジェクト、複数ユーザーの 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} に対応付けます。

CypressCodeceptJS 4(Playwright ヘルパー)
e2e.baseUrlhelpers.Playwright.url
viewportWidth / viewportHeighthelpers.Playwright.windowSize: '1280x720'
defaultCommandTimeouthelpers.Playwright.waitForTimeout
videohelpers.Playwright.video: true
screenshotOnRunFailureon: '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 経由で実際のブラウザーを操作し、各ステップを検証してからコミットします。

CypressCodeceptJS 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 の優先順位 — 該当する中で最も優先度の高い方法を選び、コンテキストを追加してください。

  1. セマンティック文字列 — ボタンのテキスト、ラベル、プレースホルダー、リンクテキスト: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"]' } は使用しないでください。
  2. ARIA ロール — I.click({ role: 'button', name: 'Sign In' }, '#login-form')。
  3. customLocator プラグイン経由の $name — Cypress ユーザーは [data-cy=...] を使うことがよくあります。これらの属性はそのままにして、プラグインを有効にし、{ css: '[data-cy=submit]' } ではなく I.click('$submit', '.checkout') と記述できるようにします。
  4. locate() ビルダー — I.click(locate('button').withText('Edit').inside('tr').withText('Acme'))。多くの場合、I.click('Edit', locate('tr').withText('Acme')) のように分けるほうが適切です。
  5. CSS/XPath — 最後の手段としてのみ使用します。

詳しくは writing-codeceptjs-tests の § Locators を参照してください。

7. アクション、アサーション、値の取得
CypressCodeceptJS 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. フィクスチャ、リクエスト、タスク
CypressCodeceptJS 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 ジョブを削除してください。

検証

  1. npx codeceptjs check -c <config> — 設定、ヘルパー、プラグインが正常であることを確認します。
  2. npx codeceptjs list -c <config> — 移植したすべての Cypress コマンドが WebExtra または ApiExtras の I.* アクションとして表示されること、すべてのページオブジェクトのメソッドが表示されることを確認します。
  3. npx codeceptjs dry-run --steps -c <config> — すべての Scenario が読み込まれることを確認します。
  4. 全体を実行します:npx codeceptjs run --steps -c <config>。初回実行では失敗が想定されます。debugging-codeceptjs-tests スキルを使って、一つずつ修正してください(retry や、やみくもな書き換えはしないでください)。変換したテストスイート全体が成功して初めて、移行が完了します。
  5. codeceptjs-run-analysis に引き継ぎ、output/trace_*/ のアーティファクトを調査します(aiTrace プラグインが有効になっている必要があります)。
  6. 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)

コメント