概要
Playwright で認証フロー全体を E2E テストする。ログインフォームへの入力・送信・保護ページへの redirect 確認・ログアウトまでの一連の操作を検証する。storageState を使ってセッションを保存・再利用することで、複数テストでのログイン操作の繰り返しを省略できる。
インストール
npm install --save-dev @playwright/test
npx playwright install chromium
実装
Playwright 設定
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
},
projects: [
{
name: "setup",
testMatch: /.*\.setup\.ts/,
},
{
name: "authenticated",
use: {
...devices["Desktop Chrome"],
storageState: "e2e/.auth/user.json",
},
dependencies: ["setup"],
},
{
name: "unauthenticated",
use: { ...devices["Desktop Chrome"] },
},
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
認証セットアップ(セッション保存)
// e2e/auth.setup.ts
import { test as setup, expect } from "@playwright/test";
import path from "path";
const authFile = path.join(__dirname, ".auth/user.json");
setup("ログインしてセッションを保存", async ({ page }) => {
await page.goto("/login");
await page.getByPlaceholder("メールアドレス").fill("user@example.com");
await page.getByPlaceholder("パスワード").fill("password");
await page.getByRole("button", { name: "ログイン" }).click();
// ダッシュボードへの redirect を確認
await expect(page).toHaveURL("/dashboard");
// Cookie を含むストレージ状態をファイルに保存
await page.context().storageState({ path: authFile });
});
認証済みユーザーのテスト
// e2e/dashboard.spec.ts
import { test, expect } from "@playwright/test";
// playwright.config.ts の "authenticated" プロジェクトが storageState を自動注入する
test("ダッシュボードが表示される", async ({ page }) => {
await page.goto("/dashboard");
// ログインページへ redirect されないことを確認
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading", { name: "ダッシュボード" })).toBeVisible();
});
test("ログイン済みでログインページにアクセスするとダッシュボードへ redirect される", async ({
page,
}) => {
await page.goto("/login");
await expect(page).toHaveURL("/dashboard");
});
test("ログアウト後は保護ページへアクセスできない", async ({ page }) => {
// ログアウト API を呼び出す
await page.request.post("/api/logout");
await page.goto("/dashboard");
// ログインページへ redirect されることを確認
await expect(page).toHaveURL("/login");
});
未認証ユーザーのテスト
// e2e/auth.spec.ts
import { test, expect } from "@playwright/test";
// "unauthenticated" プロジェクトで実行(storageState なし)
test("未認証で保護ページにアクセスするとログインページへ redirect される", async ({
page,
}) => {
await page.goto("/dashboard");
await expect(page).toHaveURL("/login");
});
test("ログインフォームに正しい認証情報を入力するとダッシュボードへ遷移する", async ({
page,
}) => {
await page.goto("/login");
await page.getByPlaceholder("メールアドレス").fill("user@example.com");
await page.getByPlaceholder("パスワード").fill("password");
await page.getByRole("button", { name: "ログイン" }).click();
await expect(page).toHaveURL("/dashboard");
});
test("誤った認証情報ではエラーメッセージが表示される", async ({ page }) => {
await page.goto("/login");
await page.getByPlaceholder("メールアドレス").fill("wrong@example.com");
await page.getByPlaceholder("パスワード").fill("wrongpassword");
await page.getByRole("button", { name: "ログイン" }).click();
await expect(
page.getByText("メールアドレスまたはパスワードが正しくありません"),
).toBeVisible();
await expect(page).toHaveURL("/login");
});
ポイント
storageStateを使うと Cookie を含むブラウザセッション全体をファイルに保存できる。auth.setup.tsで 1 回だけログインし、以降のテストはその状態を再利用するため、テストごとにログイン操作を繰り返す必要がないplaywright.config.tsでdependencies: ["setup"]を設定すると、setupプロジェクトが完了してからauthenticatedプロジェクトが実行される。セッションファイルが確実に存在する状態でテストが始まるpage.request.post("/api/logout")は Playwright の API リクエスト機能でサーバー側の Cookie を削除する。その後page.gotoで保護ページにアクセスして redirect を確認するgetByRole/getByPlaceholderなどのセマンティックロケーターを使うことで、UI の実装詳細(クラス名 / ID)の変更に影響されない堅牢なテストになるe2e/.auth/ディレクトリはセッション情報を含むため.gitignoreに追加し、リポジトリにコミットしない