本記事では、PlaywrightのE2Eテストで globalSetup と storageState を使い、ログイン状態を一度だけ取得して各テストで再利用する仕組みを解説します。
この方式により、ログインが必要なページのテストでも毎回ログイン操作を繰り返さずに済み、テスト全体を高速かつ安定的に実行できます。
この記事で得られること
globalSetup+storageStateでログインを1回だけ実行し、全テストで再利用する実装httpOnlyCookieを含むログイン状態が保存・注入されるしくみ- 実際にログインするテストとログイン済み状態から開始するテストの使い分け
globalSetup内で成功・失敗それぞれのトレースを記録するデバッグ手法
背景と課題
これまで、ログインが必要な画面のリグレッション確認を、リリースのたびに手動で実施していました。
対象画面が十数件にのぼり、毎回30分以上の確認工数がかかっていたうえ、ログインを伴う操作フローは手動では見落としが起きやすく、リリース後に不具合に気づくケースもありました。
機能追加のペースが上がり、確認対象もさらに増えてきたことをきっかけに、これらの確認をE2Eテストで自動化し、リグレッションを継続的に担保することにしました。
E2Eテストを導入するうえで課題になるのが認証の扱いです。
テストごとにログイン操作を繰り返すと、テスト数に比例して実行時間が増え、さらにログイン処理自体の不安定さがテスト全体の信頼性を下げます。
そこで、ログインを一度だけ実行してセッション情報を保存し、各テストで再利用する仕組みを globalSetup と storageState で構築しました。本記事はその構成と運用方法をまとめたものです。
前提環境
本記事は以下の環境で動作を確認しています。お使いの環境に合わせて読み替えてください。
| ツール | バージョン |
|---|---|
| Node.js | 20.18.0 (LTS) |
| @playwright/test | 1.49.1 |
| Nuxt | 3.14.0 |
| TypeScript | 5.6.3 |
| 実行OS | macOS 14 / Ubuntu 22.04 (CI) |
ファイル構成
E2E専用の構成として、関連ファイルは次のように配置します。
| |
E2E専用の設定ファイルを分ける理由
E2Eテストは実APIを呼び出し、実際にログインしてセッションを取得します。スナップショット比較やユニットテストなど他の種類のテストとは、API・認証・実行タイミングの要件が異なります。
そのため設定を1ファイルに混在させると、「モック設定がE2Eにも適用されて実APIが呼ばれなくなる」「不要な globalSetup が走ってしまう」といった干渉が起きます。これを避けるため、E2E専用のPlaywright設定ファイルを用意し、設定・認証・実行環境を独立させて管理します。
設計の考え方
globalSetup と storageState の流れ
| |
storageState を使う理由
storageState を使わない場合、すべてのテストがログイン操作を繰り返すため遅くなります。globalSetup でログイン済みのCookieをファイルに保存し、各テストはそれを再利用します。これにより高速化できます。
Playwright 公式が推奨する project dependencies
Playwright公式ドキュメントでは、setup専用プロジェクトを依存関係として定義する project dependencies が推奨されています。本記事で扱う globalSetup オプション方式とは異なるアプローチです。project dependenciesでは、HTMLレポート上にセットアップが表示され、トレース記録やPlaywright fixturesもそのまま使えるメリットがあります。
本記事では学習コスト・既存リソースとの整合性から globalSetup 方式を採用しています。HTMLレポートに統合されない点は context.tracing.start/stop で補います。リッチな統合を重視する場合はproject dependenciesの採用を検討してください(公式: Global setup and teardown)。
セットアップ手順
ファイル構成と全体の流れが把握できたところで、実際に動かすための準備を進めます。
なお、以降のコマンドはすべてpackage.jsonのあるディレクトリ(本記事ではsrc/)で実行します。
1. パッケージをインストールする
| |
@playwright/test本体と、.env.e2eの読み込みで使うdotenvをインストールします。npx playwright install chromiumは、テストで使うブラウザ本体をインストールするコマンドです。
2. npm スクリプトを追加する
package.jsonのscriptsへ次のコマンドを追加します。--configでE2E専用の設定ファイルを指定しています。
| |
3. .env.e2e を作成する
.env.e2e(gitignoreされているため各自で作成):
| |
注: 本記事のドメイン(example.com)・Cookie名(sessionToken_dev)・認証情報はすべてダミー値です。実際の環境に合わせて読み替えてください。
4. playwright/.auth/ をgitignoreの対象にする
ログイン済みCookieの保存先playwright/.auth/は、テスト実行時に自動で作成されます。保存されるuser.jsonはセッショントークンを含むため、必ずgitignoreの対象にしてください。
| |
ここまでで実行に必要な準備が整いました。ここからは、実際に作成する次の3ファイルの中身を順に解説します。
- 設定ファイル(
playwright.e2e.config.ts) - グローバルセットアップ(
tests/e2e/global-setup.ts) - テストの書き方(
tests/e2e/auth.spec.ts)
設定ファイル(playwright.e2e.config.ts)
E2E専用の設定ファイルです。
| |
ポイント:
globalSetupを指定することで、テスト実行前に一度だけログイン処理を実行できるdotenvで.env.e2eから認証情報を読み込む(ESMプロジェクトのためimport.meta.urlから__dirnameを生成する)
trace の設定値
trace はPlaywrightがテストの実行内容を記録するかどうかを制御します。記録されたトレースは npx playwright show-trace で確認できます。
| 設定値 | 挙動 | 用途 |
|---|---|---|
"off" | 記録しない | Playwright 標準のデフォルト |
"on" | 全テストで常に記録する | 開発中・デバッグ時(※ 公式はパフォーマンス影響大として CI 非推奨) |
"retain-on-failure" | 全テストで記録するが、成功テストは破棄する | CI 環境(失敗時のみトレースを残せる) |
"on-first-retry" | 最初のリトライ時のみ記録する | retries を設定している場合(公式は CI でこちらを推奨) |
"on-all-retries" | すべてのリトライ時に記録する | リトライごとの差分を追いたい場合 |
本記事の設定(前述のコード)では ?? "on" により未指定時のフォールバックを全テスト記録にしています。そのため PLAYWRIGHT_TRACE 環境変数を指定しない限り全テストのトレースが保存されます。ローカル開発では便利ですが、CIでは "on-first-retry" または "retain-on-failure" への切り替えを推奨します。
| |
グローバルセットアップ(tests/e2e/global-setup.ts)
テスト実行前に一度だけ呼ばれ、実際にブラウザでログインしてセッション情報を保存します。
| |
ポイント:
- トレースと
storageStateはコンテキスト単位のAPIのため、browser.newContext()を明示的に呼び出して操作している context.tracing.start/stopをtry/catchで囲むことで、成功・失敗それぞれのトレースを保存できる
トレースファイルの保存先は次のとおりです。セットアップ失敗時の原因調査に役立ちます。
| 状況 | ファイル |
|---|---|
| セットアップ成功時 | test-results/setup-trace.zip |
| セットアップ失敗時 | test-results/failed-setup-trace.zip |
なお、トレースにはログイン操作のネットワーク記録(認証情報・セッションCookieを含む)が保存されます。test-results/ も .gitignore の対象にし、CIでアーティファクトとして保存する場合は公開範囲に注意してください。
ログイン情報の保存のしくみ
ここでは、Cookieベースのセッション認証を例に説明します。
ログイン情報を保存する流れ
globalSetup では、実際にブラウザを起動してログインフォームを操作します。ログインが成功するとサーバーがレスポンスヘッダーに Set-Cookie を返し、ブラウザのコンテキストにCookieが保存されます。
| |
context.storageState() はブラウザコンテキストが保持しているCookieをすべてJSONファイルに書き出します。Cookie認証のほか、origins キーにlocalStorageベースの認証状態も保存されます(sessionStorageは保存対象外です)。IndexedDBはv1.51以降で indexedDB: true を指定した場合のみ保存されます。
httpOnly でJavaScriptから読めないCookieも、ブラウザコンテキストが保持しているため漏れなく保存されます。
| |
実際に保存されるファイルの内容:
| |
この例では、APIサーバーが検証するセッショントークンと、フロントエンドがログイン状態を判定するためのフラグの2つが保存されています。保存されるCookieの種類や名前はアプリの認証実装によって異なります。
各テストで storageState を読み込む
test.use({ storageState: AUTH_FILE }) を指定すると、テスト開始時にブラウザコンテキストへCookieが注入されます。
| |
ブラウザが .example.com ドメインへリクエストを送る際に自動でCookieが付与されるため、サーバー側のセッション検証も通ります。
テストの書き方(tests/e2e/auth.spec.ts)
ログイン操作に関わるテストは、次の2パターンに分けて書きます。
| パターン | storageState | 用途 |
|---|---|---|
| 認証フロー | 使わない(空のCookieで開始) | ログイン機能そのものの検証 |
| ログイン済みフロー | globalSetupで保存したuser.jsonを使用 | ログイン後の画面・機能の検証 |
どちらのテストも同じファイルに書けます。冒頭の共通部分は次のとおりです。
| |
パターン1: 実際にログイン操作を行うテスト(認証フロー)
ログイン機能そのものを検証するテストです。実際にフォームを操作してログインし、成功後の状態を確認します。
| |
空のstorageStateを明示しておくと、設定やほかのテストの状態にかかわらず、常に未ログインの状態から開始できます。
パターン2: ログイン済み状態から開始するテスト(ログイン済みフロー)
ログイン後の画面・機能を検証するテストです。test.use({ storageState: AUTH_FILE })を指定するとglobalSetupの保存したCookieが注入され、ログイン操作なしでログイン済み状態から開始できます。
| |
ログインが必要な画面のテストは、基本的にこのパターン2で書きます。パターン1を使うのはログイン機能そのものを検証するときだけです。
ログイン再利用で削減できるコード
storageState を使わない場合、ログインが必要な各テストへ次のログイン操作を書く必要があります。
| |
storageState を再利用すると、このログイン操作5行がtest.use({ storageState: AUTH_FILE })の1行に置き換わります。
| |
テストが10件あれば、50行分のログイン操作と10回分のログイン実行を削減できます。実行時間に上乗せされるログイン処理はglobalSetupの1回だけになり、テスト数が増えても変わりません。後述の実測では、このログイン処理1回あたり約2.3秒を要していました。テストが増えるほど、この差は積み上がります。
削減できるのはコード量だけではありません。ログイン処理の失敗ポイントもglobalSetupの1箇所に集約されるため、テスト全体の安定性が向上します。
実測: ログイン方式別の実行時間
ここまでの「ログインを1回に集約する」効果を、実際に計測しました。
計測方法
同一スイート内に、本体処理(TOPページを開いて描画完了を待つだけの軽量処理)をそろえた2方式のテストを各50件ずつ用意しました。計測対象は、各テストの**「ログイン済みでページが表示できるまで」の所要時間**です。
| 方式 | 各テストの処理 |
|---|---|
| A. 毎テストログイン | テストごとにフォームから実ログイン → TOP表示 |
B. storageState 再利用 | globalSetupのuser.jsonを注入 → TOP表示 |
本体処理を同一にしているため、両者の差はそのままログイン操作のオーバーヘッドを表します。
- 実行:
@playwright/test(Desktop Chrome /workers: 1) - 試行: 各方式50件
- 環境: dev環境のAPIに対する実ログイン
結果
| 指標 | A. 毎テストログイン | B. storageState 再利用 |
|---|---|---|
| 1テストあたり平均 | 2,576 ms | 290 ms |
| 中央値 | 2,488 ms | 274 ms |
| 最小〜最大 | 1,916〜5,423 ms | 258〜466 ms |
| 50件の合計 | 128.8 s | 14.5 s |
1テストあたり平均で約2.3秒(約8.9倍)短縮でき、50件では合計で約114秒(約1分54秒)の差が出ました。Aは毎回API認証の往復が入るため平均2.58秒・ばらつきも大きい(最大5.4秒)一方、Bは注入済みCookieによる遷移コストのみで平均0.29秒と安定しています。
工数への効き方
冒頭で触れたとおり、対象画面は十数件あり、手動リグレッションでは毎回30分以上かかっていました。これをE2Eに置き換えると、storageState方式ではログインはglobalSetupの1回(≈数秒)に集約され、画面数が増えてもログイン回数は1回のままです。
仮に対象が15画面の場合、毎テストログイン方式ではログインだけで 2.3秒 × 15 ≈ 35秒 を消費します。storageState方式ではこの大半を削減できます。テスト本体(実際の検証)の時間は方式によらず一定です。そのため、画面数が増えるほどログイン集約の効果は積み上がります。
上記はログインのオーバーヘッドを切り出して計測した値です。実際のスイートの総時間はテスト本体の内容に依存します。
テストの実行
設定ファイル・グローバルセットアップ・テストの3ファイルがそろったら、テストを実行します。
| |
実行のたびにglobalSetupがログインし、playwright/.auth/user.jsonを更新します。テスト完了後はnpx playwright show-reportで結果を確認できます。

レポートには各テストの合否と実行時間、View Traceへのリンクが並びます。storageState再利用により、ログイン済み前提の検証だけがテスト本体として記録されます。
CIでの実行
CIでは認証情報を.env.e2eとしてコミットできないため、リポジトリのSecretsに登録し、実行時に環境変数として注入します。globalSetupはprocess.env.TEST_EMAIL/process.env.TEST_PASSWORDを参照するため、設定ファイルの変更は不要です。
GitHub Actionsでの例を示します。
| |
ポイント:
--with-depsでブラウザ実行に必要なOS依存パッケージも同時にインストールする(CIのクリーン環境で必要)PLAYWRIGHT_TRACE=retain-on-failureで、失敗したテストのトレースだけを残すif: ${{ !cancelled() }}により、テストが失敗してもレポートとトレースをアップロードする
注意: トレース(test-results/)にはログイン操作のネットワーク記録(認証情報・セッションCookie)が含まれます。アーティファクトの公開範囲を限定し、保持期間(retention-days)も必要最小限にしてください。
トラブルシューティング
globalSetup が失敗したとき
| |
Traceビューアでログイン操作の各ステップ・ネットワークリクエスト・スクリーンショットを確認できます。

上の例では、ログインフォーム送信後に「ログインに失敗しました」と表示され、/loginから遷移しなかったことが一目で分かります。各ステップのDOMスナップショット・ネットワーク・コンソールログがそろっているため、失敗の原因(認証情報の誤り、APIエラーなど)を素早く切り分けられます。
ログイン済みフローのテストが認証エラーで失敗するとき
playwright/.auth/user.json を開き、Cookieが保存されているかを確認します。中身が空に近い場合、globalSetup のログインは実際には成功していません。test-results/setup-trace.zip をTraceビューアで開き、ログイン操作の流れを確認してください。
なお、globalSetup は実行のたびにログインし直すため、古いuser.jsonが残り続けることはありません。
まとめ
PlaywrightのE2Eテスト導入として、次の構成を紹介しました。
- E2E専用の設定ファイルを用意し、他のテストから独立して管理する
globalSetupでログインを1回だけ実行し、storageStateでログイン状態を全テストへ再利用する- テストは「認証フロー」と「ログイン済みフロー」の2パターンに分けて書く
globalSetup内でトレースを記録し、セットアップ失敗時も原因を調査できるようにする
ログインの扱いをglobalSetupへ集約したことで、テストの実行時間とコード量を抑えながら、ログインが必要な画面のリグレッションを継続的に確認できるようになりました。
冒頭で触れたとおり、より統合度の高いセットアップを求める場合はproject dependenciesを検討してください(公式: Global setup and teardown)。
