Cypress(サイプレス)は、テストコードをアプリと同じブラウザの中で動かすJavaScript/TypeScript製のE2Eテストフレームワークです。本体はMITライセンスで無料です。この記事では、2026年9月29日公開の最新版Cypress 16.1.1を使い、インストールからテストの書き方、APIのモック、ログインの共通化、GitHub Actionsでの実行までを順に説明します。
掲載コードは、検証用の小さなWebアプリに対してCypress 16.1.1とChrome 154で実行し、全件パスすることを確認したものです。Cypress 16では Cypress.env() と cy.exec() が削除されたため、2025年以前の解説記事のコードはそのままでは動かない箇所があります。
まとめ:Cypress 16でE2Eテストを始めるときの要点
- 最新版は16.1.1(2026年9月29日)です。16.0.0(2026年9月1日)でNode.js 20のサポートが終わり、Node.js 22・24・26以上が必須になりました。
- npm 12は依存パッケージのpostinstallを既定でブロックするため、
npm install cypressだけではバイナリが入りません。npm install-scripts approve cypressで許可してからnpm rebuild cypressを実行します。 - テストは
describe・itの中でcy.visit・cy.get・shouldをつなげて書きます。要素はdata-cy属性で指定します。 - 待ち時間は固定ミリ秒の
cy.wait(3000)で書かず、コマンドの自動リトライ(既定4秒)とcy.interceptの別名待ちに任せます。 - ログインは
cy.sessionでキャッシュし、パスワードやAPIキーはcy.env()で読みます。 - CIは公式の
cypress-io/github-action@v7を使います。動画の録画はCloudなしでもできます。テストを複数マシンへ自動で振り分ける並列実行とTest ReplayはCypress Cloudの機能で、無料のStarterプランは月500テスト結果までです。 - 別タブの操作、2つのブラウザの同時操作、Python・Javaでのテスト記述が必要な場合はPlaywrightやSeleniumの方が向いています。
Cypressの仕組みとPlaywright・Seleniumとの違い
Seleniumは、ブラウザの外からW3C WebDriverプロトコルで命令を送ります。Cypressはテストコードをアプリと同じブラウザの中で実行します。公式のTrade-offsページも「Cypress commands run inside of a browser」と説明しています。そのためDOMを直接読め、cy.intercept で通信を書き換えられ、テストランナーのコマンドログで各ステップ時点の画面に戻って確認できます。
コマンドの自動リトライと既定4秒のタイムアウト
cy.get や cy.contains などのクエリは、後ろに続くアサーションが通るまで要素の取得をやり直します。待つ上限は設定の defaultCommandTimeout で、既定値は4000ミリ秒です。描画が遅い要素があっても、固定時間の待機は要りません。足りない箇所だけ cy.get('[data-cy=list]', { timeout: 10000 }) のように個別に延ばします。
Playwright・Seleniumとの比較
| 項目 | Cypress 16 | Playwright | Selenium 4 |
|---|---|---|---|
| テストの実行場所 | ブラウザの中 | ブラウザの外 | ブラウザの外(WebDriver) |
| テストの記述言語 | JavaScript・TypeScript | JS・TS・Python・Java・.NET | Java・Python・C#・Ruby・JSなど |
| 対応ブラウザ | Chrome・Edge・Firefox(WebKitは実験的) | Chromium・Firefox・WebKit | 主要ブラウザ |
| 別タブの操作 | @cypress/puppeteerが必要 | 標準で可 | 標準で可 |
| 並列実行 | Cypress Cloudで分散 | 標準(workers) | Selenium Grid |
| ライセンス | MIT | Apache-2.0 | Apache-2.0 |
Playwrightの現行版と更新履歴はPlaywrightの最新バージョンは1.63|確認・更新方法とリリース履歴【2026年9月】、WebDriverの仕組みはSeleniumとは?WebDriverの仕組みと4系の実行手順・ツール選定を解説で扱っています。
Cypressを選ばない方がよい場面
公式Trade-offsが恒久的な制約として挙げているのは、2つのブラウザを同時に動かせないこと、ブラウザの外の処理には cy.task やHTTP経由の cy.request を使うこと、などです。チャットのように2人のユーザーの画面を同時に操作して検証するテストはCypressでは書けません。iPhoneのSafariでの動作が品質の要になる案件も、WebKit対応が実験的な段階なので不向きです。テストを書くのがPythonやJavaのチームなら、記述言語の制約だけでPlaywrightかSeleniumを選んでかまいません。逆に、社内向けの業務Webアプリやシングルページアプリを、フロントエンドのエンジニア自身がJavaScriptで検証する体制なら、失敗した操作をコマンドログで追え、描画待ちを自動リトライに任せられるCypressが合います。
Cypress 16のインストール手順と動作要件
動作要件:Node.js 22・24・26以上
Cypress 16.1.1の package.json の engines は ^22.0.0 || ^24.0.0 || >=26.0.0 です。Node.js 20と奇数版の25では動きません。OSはmacOS 13.5以上、Ubuntu 22.04以上(arm64は24.04以上)、Debian 12以上(arm64は13以上)、Windows 10・11(x64)、Windows Server 2019・2022・2025が対象です。ブラウザはChrome・Edge・Firefoxそれぞれの最新3メジャー版をサポートします。
npm 12でCypressのバイナリが入らない問題
Cypressは、npmパッケージとは別に約数百MBのアプリ本体(バイナリ)を postinstall スクリプトでダウンロードします。npm 12.2.0で npm install cypress --save-dev を実行すると「install scripts blocked because they are not covered by allowScripts」という警告が出て、バイナリはダウンロードされません。公式の手順は次の3行です。
npm install cypress --save-dev
npm install-scripts approve cypress --no-allow-scripts-pin
npm rebuild cypress
--no-allow-scripts-pin を付けないと、package.json の allowScripts に "[email protected]": true とバージョン付きで記録されます。この状態で16.2に上げると、許可の対象外になって再びブロックされます。npm 11.16から11系の最後までは警告だけでバイナリは入ります。npm 11.17.0の警告に表示されるコマンドは npm approve-scripts で、npm 12でも同じ名前で使えます。許可リストを触りたくない場合は、インストール後に npx cypress install でバイナリだけを取得する方法もあります。
cypress openでの初回起動と設定ファイル
npx cypress open
初回はLaunchpadという画面が開きます。「E2E Testing」を選ぶと cypress.config.js、サポートファイル、サンプルのフィクスチャが生成されます。テストファイル(spec)は既定で cypress/e2e/**/*.cy.{js,jsx,ts,tsx} に一致するものが読み込まれます。設定ファイルは cypress.config.ts にすればTypeScriptで書けます。古い記事に出てくる cypress.json はCypress 10で廃止されました。
最初のE2Eテストの書き方:describe・it・cy.visit・should
例として、メールアドレスとパスワードを入れてダッシュボードへ進むログイン画面をテストします。以下は専用の検証アプリ向けの例です。実行前に localhost:3000 でアプリを起動し、テストユーザーと環境変数 CYPRESS_password を用意します。data-cy 属性・APIのパス・Cookie名は、対象アプリに合わせて書き換えてください。まず設定ファイルに baseUrl を書きます。これで cy.visit('/') のように相対パスで開けます。
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
})
テストは cypress/e2e/login.cy.js に書きます。
describe('ログイン画面', () => {
beforeEach(() => {
cy.visit('/')
})
it('正しい認証情報でダッシュボードへ遷移する', () => {
cy.env(['password']).then(({ password }) => {
cy.get('[data-cy=email]').type('[email protected]')
cy.get('[data-cy=password]').type(password, { log: false })
})
cy.get('[data-cy=submit]').click()
cy.location('pathname').should('eq', '/dashboard')
cy.contains('h1', 'ダッシュボード')
})
it('パスワードが違うとエラーを表示する', () => {
cy.get('[data-cy=email]').type('[email protected]')
cy.get('[data-cy=password]').type('wrong-password')
cy.get('[data-cy=submit]').click()
cy.get('[data-cy=error]').should('be.visible')
})
})
describe がテストのまとまり、it が1件のテストです。context は describe の別名で、条件ごとに入れ子を分けたいときに使います。type(password, { log: false }) は、入力コマンドをコマンドログに表示しない指定です。画面上の値を隠す機能ではないため、録画への映り込みまで防ぐものではありません。cy.contains('h1', 'ダッシュボード') は見出しの文言そのものを検証しており、文言が変わればテストが落ちます。
要素の選び方:data-cy属性を優先する理由
公式のBest Practicesは、クラス名・ID・タグ名ではなく data-cy などのテスト専用属性で要素を選ぶよう勧めています。クラス名はCSSの変更で、IDはJavaScriptの都合で変わるため、見た目を直しただけでテストが壊れるからです。文言が仕様そのもので、変わったら落ちてほしい箇所に限って cy.contains を使います。
beforeEachとテスト分離(testIsolation)
設定 testIsolation の既定値は true です。各テストの前に、ページを about:blank に戻し、全ドメインのCookie・localStorage・sessionStorage を消します。そのため画面を開く処理は beforeEach に置きます。片付けを afterEach に書く必要はなく、公式も状態のリセットは各テストの前に行うよう説明しています。注意点として、IndexedDBはテスト分離でも消えません。オフラインキャッシュなどでIndexedDBを使うアプリでは、テスト側で明示的に消去します。
cy.interceptとfixtureファイルによるAPI応答のモック
APIの応答を固定すると、バックエンドのデータに左右されずに画面の表示を検証できます。フィクスチャは cypress/fixtures/todos.json に置きます。
[
{ "id": 1, "title": "請求書を送る" },
{ "id": 2, "title": "議事録を共有する" }
]
describe('ToDo一覧', () => {
beforeEach(() => {
cy.login('[email protected]')
})
it('APIの応答をフィクスチャで差し替える', () => {
cy.intercept('GET', '/api/todos', { fixture: 'todos.json' }).as('getTodos')
cy.visit('/dashboard')
cy.wait('@getTodos')
cy.get('[data-cy=todo-list] li')
.should('have.length', 2)
.first()
.should('have.text', '請求書を送る')
})
it('実APIの応答をそのまま検証する', () => {
cy.intercept('GET', '/api/todos').as('getTodos')
cy.visit('/dashboard')
cy.wait('@getTodos').its('response.statusCode').should('eq', 200)
cy.get('[data-cy=todo-list] li').should('have.length', 1)
})
})
cy.intercept は cy.visit より前に書きます。後に書くと、ページ読み込み直後のリクエストを取り逃します。.as('getTodos') で付けた別名を cy.wait('@getTodos') で待つと、応答が返るまでだけ待機します。2件目のテストのように応答を差し替えず、実際のステータスコードだけを確かめる使い方もできます。モックとスタブの使い分けやモックサーバーの方式はAPIモッキングとは?モックサーバーの方式選定と契約ドリフト対策を実装目線で解説で整理しています。
Cypress 16から、Chrome・Chromium・Edgeではテストの通信をCypress経由ではなくブラウザ本来のネットワークで傍受します。HTTP/2・HTTP/3で通信するようになった一方で、req.httpVersion は undefined になり、傍受した応答から content-encoding ヘッダーが見えなくなりました。これらの値が取得できる前提のアサーションは、Chrome・Chromium・Edgeで失敗する可能性があります。Firefox・WebKit・Electronは従来の経路のままです。移行期間用に設定 forceHttp1 で全ブラウザを従来経路に戻せますが、この設定は追加時点で非推奨扱いです。
ログイン処理の共通化:cy.sessionとカスタムコマンド
すべてのテストでログイン画面を操作すると、その分だけ実行時間が延びます。ログイン画面そのものは前章の login.cy.js で検証し、ほかのテストはAPIでログインして結果をキャッシュします。cypress/support/commands.js にカスタムコマンドを定義します。cypress/support/e2e.js から import './commands' で読み込まれていることも確認します。
Cypress.Commands.add('login', (email) => {
cy.session(email, () => {
cy.env(['password']).then(({ password }) => {
cy.request({
method: 'POST',
url: '/api/login',
body: { email, password },
log: false,
})
})
}, {
validate() {
cy.getCookie('sid').should('exist')
},
})
})
cy.session は第1引数のIDごとに、Cookie・localStorage・sessionStorage を保存し、2回目以降は保存した値を戻します。validate はセッションの初回作成後と復元後に実行されます。復元後に失敗するとログイン処理をやり直しますが、初回作成直後に失敗した場合はテストが失敗します。Cypress 16では cy.getCookie もリトライするクエリになったため、should('exist') は既定4秒まで再試行します。この例はCookieの有無だけを確かめる簡略版です。実アプリでは、ログイン中のユーザー情報を返すAPIを cy.request で呼び、ステータス200を確かめる方が、サーバー側で失効したセッションも見抜けます。
環境変数と秘密情報:Cypress.env()廃止後のcy.envとCypress.expose
Cypress 16.0.0で Cypress.env() が削除されました。16.1.1で呼ぶと「Cypress.env() was removed in Cypress version 16.0.0」で始まる例外が出て、テストはその場で失敗します。代わりのAPIは2つあり、ブラウザに値を見せてよいかで使い分けます。
| API | ブラウザへの露出 | 入れる値 | 値の渡し方 |
|---|---|---|---|
cy.env() |
要求した値のみ取得 | パスワード・APIキー・トークン | CYPRESS_*・cypress.env.json・--env |
Cypress.expose() |
あり | 機能フラグ・環境名・APIの版 | 設定のexpose・--expose |
cy.env() はキーの配列を受け取り、値は .then で受け取る非同期のコマンドです。前章のコードは、CIで CYPRESS_password という環境変数を渡せばそのまま動きます。Cypress.expose() は同期的に値を返しますが、アプリ側のコードやブラウザ拡張からも見えるため、秘密情報には使いません。テスト単位の設定上書きに書いていた env キーも使えなくなっています。
cypress runとGitHub ActionsでのCI実行
cypress runの主なオプションと終了コード
npx cypress run --browser chrome --spec "cypress/e2e/login.cy.js"
cypress run は画面を出さずに全テストを実行します。--browser を省略すると同梱のElectronで動きますが、Electronはテスト用ブラウザとしてCypress 16で非推奨になり、将来のメジャー版で削除予定です。今から作るCIでは --browser chrome を明示しておくと、削除時に設定を直さずに済みます。既定の終了コードは失敗したテストの件数です。256件ちょうど失敗すると多くのシェルで0として扱われるため、CIでは --posix-exit-codes を付けて「失敗があれば1」に揃える方が安全です。
GitHub Actionsのワークフロー例
name: Cypress Tests
on: push
jobs:
cypress-run:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Cypress run
uses: cypress-io/github-action@v7
with:
build: npm run build
start: npm start
wait-on: 'http://localhost:3000'
browser: chrome
env:
CYPRESS_password: ${{ secrets.APP_PASSWORD }}
公式Actionは依存のインストールとキャッシュ、ビルド、サーバーの起動、wait-on に書いたURLの応答待ち、テスト実行までを1ステップで行います。2026年10月1日時点の最新タグはv7.4.6で、公式は @v7 のようにメジャー版で指定するよう勧めています。パスワードはリポジトリのSecretsに入れて CYPRESS_ 付きの環境変数で渡し、テストでは cy.env() で読みます。どのイベントで実行するかの設計はGitHub Actionsのトリガー(on)|イベント選定とbranches・pathsフィルタの設計を参照してください。
Cypress Cloudの料金と並列実行
Cypress Cloudは、実行結果の記録、失敗時の再生(Test Replay)、複数マシンへのテスト分散を担うクラウドサービスで、無料枠と有料プランがあります。npx cypress run --record --key <記録キー> --parallel で使います。料金ページ(2026年10月2日確認)では、無料のStarterが月500テスト結果まで、Teamは年払い799ドルから(月額表示67ドル)で、基本料金に年12万テスト結果を含み、追加分は別料金です。課金対象はCloudに記録したテスト結果で、スキップは数えず、同じテストのリトライはまとめて1件と数えます。100件を毎日5回、30日間記録すると月15,000件です。無料枠は個人の検証用と考えておくのが現実的です。
Cypress 15から16へ上げるときに壊れる箇所
16.0.0の破壊的変更のうち、E2Eテストで影響が出やすいものを抜き出しました。公式の移行ガイドには、AIアシスタントに読ませるための移行用プロンプトも載っています。
| 変更点 | 15系まで | 16系での対応 |
|---|---|---|
| Node.js | 20以上 | 22・24・26以上 |
| Cypress.env() | 使用可 | cy.env()かCypress.expose() |
| cy.exec() | 15.21.0で非推奨 | 削除。cy.task()へ |
| cy.end() | 使用可 | 削除。呼び出しを消すだけ |
| cy.typeの打鍵間隔 | 既定10ms | 既定0ms |
| 表示判定 | 従来方式 | modern方式が既定 |
| メモリ管理 | experimentalMemoryManagement | manageBrowserMemory(既定true) |
| Chrome系の通信 | Cypress経由 | ブラウザ本来の経路 |
打鍵間隔が0msになったことで、1文字ごとの入力イベントに頼る入力補完やバリデーションのテストが不安定になる場合があります。その場合は設定で keystrokeDelay: 10 に戻せます。16.0.0には、Chrome・Edgeでの実行が途中で出力を止めて固まる不具合などがあり、16.1.0(2026年9月15日)で修正されました。16系へ上げるなら16.1.0以降を使います。コンポーネントテストではVite 8未満、Angular 21未満、Next.js 14のサポートも終わっています。コンポーネントテスト側の変更はCypressとは|E2E・コンポーネントテストの仕組みとcy.mountの書き方で扱っています。
Cypressでよく出るエラーと対処
- Timed out retrying after 4000ms: Expected to find element:4秒以内に要素が見つかっていません。まずセレクタの綴りを確認し、描画が本当に遅いならそのコマンドだけ
timeoutを延ばします。 - 別オリジンへの遷移で失敗する:1つのテストは1つのオリジンが前提です。外部の認証画面などに移る部分は
cy.origin()で囲みます。injectDocumentDomainが無効(既定)のときは、同じドメインのサブドメインも別オリジンとして扱われます。 - Cypress.env() was removed / cy.exec() was removed:16系への更新で出ます。前者は
cy.env()かCypress.expose()、後者はcy.task()に書き換えます。プラグインが呼んでいる場合は、Cypress 16対応版へ更新します。 - Cypress binary is missing:バイナリが見つかりません。npm 12のpostinstallブロックのほか、ダウンロード失敗やキャッシュの欠落などが原因になります。
npx cypress installの後、npx cypress verifyで起動できるか確かめます。 - アプリ側の例外でテストが落ちる:アプリが投げた未処理の例外も、Cypressはテストの失敗として扱います。無視する場合は
Cypress.on('uncaught:exception', ...)で、エラーメッセージを条件にして対象を絞ります。
よくある質問
Cypressの読み方は?
「サイプレス」と読みます。英語で糸杉を意味する単語で、検索結果に樹木や香水の情報が混じるのはそのためです。テストツールを調べるときは「cypress e2e」「cypress testing」のように用途の語を足すと絞れます。
Cypressは無料で使えますか?
テストを書いて実行するCypress本体はMITライセンスで無料です。実行結果の記録や自動分散並列実行を担うCypress Cloudには無料枠と有料プランがあり、無料のStarterプランは月500テスト結果までです。
PythonやC#でCypressのテストを書けますか?
書けません。テストコードはJavaScriptかTypeScriptで書きます。ただし、テスト対象のアプリはブラウザで動けばよく、バックエンドがPython・C#・PHPでも検証できます。テストもPythonやC#で書きたい場合はPlaywrightかSeleniumを選びます。
cypress.jsonはどこへ行きましたか?
Cypress 10で廃止され、cypress.config.js または cypress.config.ts に置き換わりました。プラグインファイルの処理も、設定内の setupNodeEvents に移っています。
E2Eテストとは何ですか?
ユーザーと同じようにブラウザを操作して、画面からサーバー、データベースまでを通しで検証するテストです。単体テストより遅く壊れやすいため、件数は主要な業務フローに絞ります。結合テストとの違いや対象の選び方はE2E(エンドツーエンド)テストのベストプラクティス|結合テストとの違いとツール選定、テスト全体の配分はテストピラミッドとは?単体・結合・E2Eの配分と実装者向けテスト戦略設計で解説しています。