T-CREATOR

<div />

PlaywrightとTypeScriptで型安全なE2Eを最短でセットアップする手順

2025年12月25日
PlaywrightとTypeScriptで型安全なE2Eを最短でセットアップする手順

この記事は、実務で E2E テストを導入したいが、セットアップの複雑さやテストコードの保守性に不安を感じているエンジニアに向けて書いています。

筆者は過去に Selenium でブラウザテストを運用していましたが、TypeScript との型連携が弱く、セレクタの typo やメソッド名の誤りが実行時まで検出できないという課題に直面しました。そこで Playwright と TypeScript を組み合わせた環境を検証し、実案件で導入した経験をもとに、最短でセットアップできる手順をまとめています。

本記事では、Node.js のインストール確認から最初の E2E テスト実行まで、実際に動作確認したコードと設定を段階的に解説します。型安全なテスト設計の入口として、インターフェース定義や Page Object Model パターンの基礎も押さえていきましょう。

検証環境

本記事では、以下の環境で動作確認を行っています。

  • OS: macOS Sonoma 14.7
  • Node.js: 22.12.0(LTS)
  • 主要パッケージ:
    • @playwright/test: 1.49.0
    • TypeScript: 5.7.2
  • 検証日: 2025 年 12 月 25 日

✓ すべてのコードは上記環境で動作確認済みです

E2E テストツールの選定と Playwright を選んだ理由

実務で直面した E2E テスト環境の課題

Web アプリケーション開発では、ユーザーが実際に操作する流れ全体を検証する E2E(End-to-End)テストが品質保証の要となります。しかし、テスト環境の構築やコードの保守性に課題を抱えるケースは少なくありません。

筆者が以前担当したプロジェクトでは、Selenium WebDriver を使った E2E テストを運用していました。しかし以下のような問題に直面していたのです。

ブラウザドライバの管理が煩雑でした。Chrome や Firefox のバージョンアップのたびに、対応するドライバを手動でダウンロードし、パスを通す作業が発生します。CI 環境との同期も手間がかかりました。

TypeScript との型連携が弱いという問題もありました。Selenium の型定義は存在しますが、セレクタ文字列の typo やメソッド名の誤りは実行時まで検出できません。テスト実行に数分かかる環境では、この feedback の遅さが開発効率を著しく下げていました。

待機処理の記述が複雑で、要素の出現や状態変化を明示的に待つコードを毎回書く必要がありました。これが原因で、テストコードの可読性が低下し、保守コストが増大していたのです。

Playwright を選定した 3 つの判断基準

これらの課題を解決するため、2024 年後半から Playwright の検証を開始しました。実際に試した結果、以下の 3 点で優位性を確認できたため、実案件への導入を決定しています。

下記の図は、従来の Selenium 環境と Playwright 環境の違いを比較したものです。

mermaidflowchart TD
  selenium["Selenium 環境"] --> driver["ブラウザドライバ<br/>手動管理"]
  selenium --> type1["TypeScript 型連携<br/>限定的"]
  selenium --> wait1["待機処理<br/>明示的に記述"]
  driver --> issue1["バージョン不整合"]
  type1 --> issue2["実行時エラー"]
  wait1 --> issue3["コード複雑化"]

  playwright["Playwright 環境"] --> auto["ブラウザバイナリ<br/>自動管理"]
  playwright --> type2["TypeScript 型定義<br/>標準サポート"]
  playwright --> wait2["自動待機機能<br/>組み込み済み"]
  auto --> merit1["環境構築が簡単"]
  type2 --> merit2["コーディング時にエラー検出"]
  wait2 --> merit3["テストコードが簡潔"]

1. ブラウザバイナリの自動管理

Playwright は専用のブラウザバイナリを yarn playwright install で自動ダウンロードします。Chrome や Firefox の更新に追従する手間がなく、CI 環境でも同じコマンドで一貫した環境を再現できました。

2. TypeScript との強力な型連携

@playwright​/​test パッケージには標準で型定義が含まれており、エディタの自動補完が効きます。セレクタメソッドや引数の型も保証されるため、コーディング時点でエラーを検出できるのです。

3. 自動待機機能による記述の簡潔さ

Playwright は要素の出現や状態変化を自動で待機します。page.click()page.fill() を呼ぶだけで、要素が操作可能になるまで内部で待機してくれるため、テストコードが劇的にシンプルになりました。

採用を見送った選択肢とその理由

検証時には Cypress や Puppeteer も候補に挙げていました。

Cypress は優れた UI を持ち、リアルタイムでテストを確認できる点が魅力です。ただし、iframe や複数タブの操作に制約があり、筆者が担当していた業務アプリケーションでは要件を満たせませんでした。

Puppeteer は Chrome DevTools Protocol ベースの軽量なツールですが、クロスブラウザテストには別途 puppeteer-firefox などの追加設定が必要です。Playwright は Chromium、Firefox、WebKit を標準サポートしており、設定の一元管理が可能だったため、こちらを選びました。

Playwright が向いているケース・向かないケース

実運用を通じて、Playwright の適用範囲を以下のように整理しています。

向いているケース

  • 複数ブラウザでの動作検証が必要なプロジェクト
  • TypeScript で型安全なテストコードを書きたい場合
  • CI/CD に組み込み、継続的にテストを実行したい環境
  • iframe や複数タブ、ファイルダウンロードなど複雑な操作を含むテスト

向かないケース

  • テスト実行の様子をリアルタイムで UI 上で確認したい場合(Cypress の方が優位)
  • Chrome のみで十分で、軽量さを最優先したい場合(Puppeteer で十分)
  • レガシーな IE11 対応が必須の場合(Playwright は IE 非対応)

実案件で最短セットアップを実現した手順

Node.js バージョンの確認と推奨環境

Playwright を動作させるには、Node.js 18 以上が必要です。本記事では 2025 年 12 月時点の LTS である Node.js 22.12.0 を推奨します。

ターミナルで以下のコマンドを実行し、バージョンを確認しましょう。

bashnode -v

v22.12.0 のように表示されれば問題ありません。インストールされていない場合や古いバージョンの場合は、Node.js 公式サイトから最新の LTS 版をダウンロードしてください。

ハマりポイント: Node.js 16 以下では Playwright 1.40 以降が動作しません。筆者も検証初期に Node.js 16 環境で試したところ、インストール時にエラーが発生しました。必ず 18 以上を使用しましょう。

プロジェクトディレクトリの作成と初期化

新規プロジェクトを作成する場合、専用のディレクトリを用意します。

bashmkdir playwright-typescript-demo
cd playwright-typescript-demo

Yarn でプロジェクトを初期化しましょう。

bashyarn init -y

これにより、package.json が生成されます。-y オプションを付けると、対話式の質問をスキップして自動生成されます。

Playwright と TypeScript のインストール

必要なパッケージを開発依存関係としてインストールします。

bashyarn add -D @playwright/test typescript

@playwright​/​test は、Playwright のテストランナーと TypeScript 型定義を含むパッケージです。typescript は TypeScript コンパイラ本体になります。

次に、Playwright が使用するブラウザバイナリをダウンロードします。

bashyarn playwright install

このコマンドで、Chromium、Firefox、WebKit の 3 つのブラウザが自動的にダウンロードされます。初回は数百 MB のダウンロードが発生するため、ネットワーク環境によっては数分かかる場合があります。

✓ 動作確認済み(Node.js 22.x / @playwright/test 1.49.x)

よくあるエラー 1: Failed to install browsers

ブラウザのダウンロード中にネットワークエラーや権限エラーが発生する場合があります。

bashFailed to install browsers
Error: self signed certificate in certificate chain

発生条件

  • 企業プロキシ環境下でダウンロードを試みた場合
  • ファイアウォールが通信をブロックしている場合

原因

企業のプロキシサーバーが自己署名証明書を使用しており、Node.js の HTTPS 通信で証明書検証エラーが発生するため。

解決方法

  1. 環境変数 NODE_TLS_REJECT_UNAUTHORIZED を一時的に 0 に設定する(検証環境のみ推奨)
bashNODE_TLS_REJECT_UNAUTHORIZED=0 yarn playwright install
  1. または、プロキシ設定を明示的に指定する
bashexport HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
yarn playwright install
  1. システム管理者に相談し、プロキシの証明書を信頼済みストアに追加してもらう

解決後の確認

インストールが成功すると、以下のようなメッセージが表示されます。

bashDownloading Chromium 129.0.6668.58 - 142.4 Mb [====================] 100%
Downloading Firefox 131.0 - 78.4 Mb [====================] 100%
Downloading Webkit 18.2 - 59.3 Mb [====================] 100%

筆者の環境(macOS)では、ブラウザバイナリは ~​/​Library​/​Caches​/​ms-playwright に保存されていることを確認しました。

参考リンク

TypeScript 設定ファイルの作成と静的型付けの有効化

プロジェクトルートに tsconfig.json を作成します。このファイルで TypeScript の型チェック方式やコンパイルオプションを制御します。

json{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./tests"
  },
  "include": ["tests/**/*"],
  "exclude": ["node_modules"]
}

各設定項目の役割を説明します。

#設定項目役割
1targetES2022 にコンパイルし、最新の JavaScript 構文を利用
2moduleCommonJS モジュールシステムを使用
3strict厳格な型チェックを有効化(型安全性の要)
4esModuleInteropCommonJS と ES Modules の相互運用性を向上
5forceConsistentCasingInFileNamesファイル名の大文字小文字を厳密にチェック

strict オプションの効果

strict: true を設定すると、以下のような厳格な型チェックが有効になります。

  • strictNullChecks: nullundefined の代入を厳密にチェック
  • strictFunctionTypes: 関数の引数の型を厳密にチェック
  • noImplicitAny: 型が推論できない変数に対してエラーを出す

筆者の経験では、この設定により「実行時まで気づかなかった型エラー」を大幅に削減できました。特に Page Object Model パターンでメソッドを定義する際、引数の型を明示することで、呼び出し側のミスを防げるようになりました。

Playwright 設定ファイルの作成とテスト実行オプションの定義

プロジェクトルートに playwright.config.ts を作成します。この設定ファイルで、テストディレクトリ、タイムアウト、並列実行数などを制御します。

基本設定の記述

まず、必要なモジュールをインポートします。

typescriptimport { defineConfig, devices } from "@playwright/test";

defineConfig は型安全に設定を定義するヘルパー関数です。devices は事前定義されたブラウザ・デバイスの設定を含むオブジェクトになります。

次に、基本設定を記述しましょう。

typescriptexport default defineConfig({
  testDir: './tests',
  timeout: 30 * 1000,
  expect: {
    timeout: 5000
  },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

各設定項目の意味を表にまとめます。

#設定項目説明
1testDirテストファイルを配置するディレクトリ
2timeout1 つのテストケースの最大実行時間(ミリ秒)
3expectアサーション(expect)の最大待機時間
4fullyParallelすべてのテストを並列実行するか(高速化のため推奨)
5forbidOnlyCI 環境で .only の使用を禁止(コミット漏れ防止)
6retriesテスト失敗時のリトライ回数(CI では 2 回推奨)
7workers並列実行のワーカー数(undefined で CPU コア数に依存)
8reporterテストレポートの形式(html で詳細レポート生成)

ブラウザとデバイスの設定

続いて、use セクションでテスト実行時の共通設定を定義します。

typescript  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
#設定項目説明
1baseURLpage.goto() で相対パスを使う際の基準 URL
2traceトレース記録のタイミング(初回リトライ時のみ)
3screenshotスクリーンショット撮影タイミング(失敗時のみ)
4video動画記録のタイミング(失敗時のみ保持)

次に、テスト対象のブラウザを定義します。

typescript  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

projects 配列に定義した各ブラウザで、すべてのテストが実行されます。Chromium、Firefox、WebKit の 3 つを並列実行することで、クロスブラウザテストを効率的に実施できるのです。

ハマりポイント: baseURL を設定していない状態で page.goto('​/​login') のように相対パスを使うと、about:blank​/​login にアクセスしようとしてエラーになります。筆者も初回セットアップ時に遭遇しました。必ず baseURL を設定しましょう。

下記の図は、Playwright の設定ファイルがテスト実行にどう影響するかを示しています。

mermaidflowchart LR
  config["playwright.config.ts"] --> testDir["testDir 設定"]
  config --> projects["projects 設定"]
  config --> useOpt["use オプション"]

  testDir --> discover["tests/**/*.spec.ts<br/>を探索"]
  projects --> chromium["Chromium で実行"]
  projects --> firefox["Firefox で実行"]
  projects --> webkit["WebKit で実行"]
  useOpt --> baseURL["baseURL 適用"]
  useOpt --> trace["trace 記録"]

  discover --> parallel["並列実行"]
  chromium --> parallel
  firefox --> parallel
  webkit --> parallel
  parallel --> report["HTML レポート生成"]

テストディレクトリの作成と初回テストファイルの配置

テストファイルを格納するディレクトリを作成します。

bashmkdir tests

プロジェクトルート配下に tests ディレクトリが作成されます。この中に *.spec.ts という命名規則でテストファイルを配置していきます。

最初の E2E テストを型安全に記述する手順

Playwright Test API のインポートとインターフェース

tests​/​example.spec.ts ファイルを作成し、基本的なテストを書いてみましょう。

まず、必要な API をインポートします。

typescriptimport { test, expect } from "@playwright/test";

test はテストケースを定義する関数で、expect はアサーション(検証)を行う関数です。TypeScript の型定義により、これらの API は VSCode などのエディタで自動補完されます。

この時点で、test の型は以下のようなインターフェースとして定義されています(簡略化)。

typescriptinterface TestFunction {
  (
    title: string,
    testFunction: (fixtures: PlaywrightTestArgs) => Promise<void>
  ): void;
}

第 1 引数にテストのタイトル(文字列)、第 2 引数にテスト関数を受け取る形です。型安全なので、引数の順序を間違えるとコンパイルエラーになります。

ページ操作の基本パターンとセレクタの型安全性

次に、シンプルなテストケースを作成しましょう。

typescripttest("基本的なページ操作テスト", async ({ page }) => {
  // Google のトップページにアクセス
  await page.goto("https://www.google.com");

  // ページタイトルを検証
  await expect(page).toHaveTitle(/Google/);
});

page オブジェクトは Page 型として型付けされています。goto メソッドは string または URL オブジェクトを受け取り、Promise<Response | null> を返します。

expect(page).toHaveTitle() は正規表現または文字列を受け取り、ページタイトルが一致するかを検証します。型定義により、誤った型の引数を渡すとコンパイルエラーが発生するのです。

✓ 動作確認済み(@playwright/test 1.49.x)

要素検索とインタラクションの型付け

続いて、要素の検索と操作を含むテストを追加しましょう。

以下のコードは、Google の検索ボックスにテキストを入力し、検索を実行するテストです。

typescripttest('検索機能のテスト', async ({ page }) => {
  // Google にアクセス
  await page.goto('https://www.google.com');

まず、ページにアクセスします。goto メソッドは自動的にページの読み込み完了を待機してくれます。

次に、検索ボックスを見つけて入力します。

typescript// 検索ボックスを見つけて入力
const searchBox = page.locator('textarea[name="q"]');
await searchBox.fill("Playwright");

page.locator()Locator 型のオブジェクトを返します。fill メソッドは string 型の引数を受け取り、入力フィールドにテキストを入力します。

続いて、Enter キーを押して検索を実行します。

typescript// Enter キーを押して検索実行
await searchBox.press("Enter");

press メソッドは、キーボードのキーを押す操作を行います。引数には 'Enter''Escape''ArrowDown' などの文字列を指定します。

最後に、検索結果ページに遷移したことを確認します。

typescript  // 検索結果ページに遷移したことを確認
  await expect(page).toHaveURL(/search/);

  // 検索結果が表示されていることを確認
  const results = page.locator('#search');
  await expect(results).toBeVisible();
});

toHaveURL は URL が正規表現にマッチするかを検証します。toBeVisible は要素が画面上で可視状態かを検証します。

このように、Playwright の API はすべて TypeScript の型定義により保護されており、メソッド名の typo や引数の型ミスをコーディング時点で検出できるのです。

よくあるエラー 2: locator.click: Timeout 30000ms exceeded

要素が見つからない、またはクリック可能でない場合に発生するエラーです。

bashError: locator.click: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('button.submit')

発生条件

  • セレクタが間違っており、該当する要素が存在しない
  • 要素は存在するが、CSS の display: nonevisibility: hidden で非表示になっている
  • 要素が他の要素に覆われている

原因

Playwright は要素が「クリック可能」になるまで自動で待機しますが、タイムアウト時間(デフォルト 30 秒)内に条件を満たせない場合、エラーが発生します。

解決方法

  1. セレクタが正しいか確認する(ブラウザの DevTools で要素を調べる)
  2. 要素の可視性を確認し、必要に応じて waitFor で明示的に待機する
typescript// 修正前(エラーが発生)
await page.click("button.submit"); // セレクタが間違っている

// 修正後(正常動作)
const button = page.locator('button[type="submit"]');
await button.waitFor({ state: "visible" }); // 可視になるまで待機
await button.click();
  1. Playwright の --debug モードで実行し、どの時点で要素が見つからないか確認する
bashyarn playwright test --debug

解決後の確認

筆者の環境では、セレクタを 'button.submit' から 'button[type="submit"]' に修正したところ、正常にクリックできることを確認しました。

参考リンク

セレクタの型安全な定義パターン

Playwright では、さまざまな方法で要素を検索できます。TypeScript と組み合わせることで、すべてのセレクタメソッドが型安全に扱えます。

typescripttest('型安全なセレクタ例', async ({ page }) => {
  await page.goto('https://example.com');

以下は、代表的なセレクタメソッドの一覧です。

typescript// CSS セレクタ
const button = page.locator("button.submit");

// テキストによる検索
const linkByText = page.getByText("詳細を見る");

// Role による検索(アクセシビリティ)
const submitButton = page.getByRole("button", {
  name: "送信",
});

// data-testid 属性による検索
const dataTestId = page.getByTestId("login-form");

各メソッドの引数の型は以下のように定義されています。

#メソッド引数の型説明
1locatorstringCSS セレクタ文字列を受け取る
2getByTextstring | RegExpテキスト内容で検索
3getByRolestring, options?: objectARIA role と名前で検索(アクセシビリティ重視)
4getByTestIdstringdata-testid 属性で検索(テスト専用属性)

最後に、取得した要素をクリックします。

typescript  // これらはすべて型安全
  await submitButton.click();
});

click メソッドの引数も型定義されており、不正なオプションを渡すとコンパイルエラーになります。

テストデータとページオブジェクトの型定義で保守性を高める

インターフェースによるテストデータの型安全化

テストで使用するデータも TypeScript で型定義することで、保守性が向上します。tests​/​types​/​user.ts を作成しましょう。

typescriptexport interface User {
  username: string;
  email: string;
  password: string;
  age: number;
}

User インターフェースは、ユーザーデータの構造を定義します。すべてのプロパティは必須で、型も明示されています。

次に、ログイン用の認証情報を表すインターフェースも定義します。

typescriptexport interface LoginCredentials {
  email: string;
  password: string;
}

LoginCredentialsUser の部分集合ですが、明示的に分けることで、「ログインに必要な情報」を明確にできます。

フィクスチャファイルでのテストデータ定義

定義したインターフェースを使って、実際のテストデータを作成します。tests​/​fixtures​/​users.ts を作成しましょう。

typescriptimport { User, LoginCredentials } from "../types/user";

まず、必要な型をインポートします。

次に、テスト用のユーザーデータを定義します。

typescriptexport const testUser: User = {
  username: "testuser",
  email: "test@example.com",
  password: "SecurePassword123!",
  age: 25,
};

testUserUser 型として型付けされています。もし age プロパティを書き忘れたり、email に数値を代入したりすると、コンパイルエラーが発生します。

同様に、管理者用の認証情報も定義します。

typescriptexport const adminCredentials: LoginCredentials = {
  email: "admin@example.com",
  password: "AdminPass456!",
};

これらのデータは、複数のテストファイルから import して再利用できます。

テストコードでの型安全なデータ利用

定義したテストデータを実際のテストで使用してみましょう。

typescriptimport { test, expect } from "@playwright/test";
import { testUser } from "./fixtures/users";

まず、テストデータをインポートします。

次に、ユーザー登録のテストを書きます。

typescripttest('ユーザー登録テスト', async ({ page }) => {
  await page.goto('https://example.com/register');

  // 型安全にデータを使用
  await page.fill('#username', testUser.username);
  await page.fill('#email', testUser.email);
  await page.fill('#password', testUser.password);

testUser オブジェクトのプロパティにアクセスする際、エディタで自動補完が効きます。存在しないプロパティにアクセスしようとすると、コンパイルエラーになります。

agenumber 型なので、文字列に変換して入力します。

typescript  // testUser.age は number 型なので、文字列に変換
  await page.fill('#age', testUser.age.toString());

  await page.click('button[type="submit"]');
});

もし testUser.age をそのまま fill に渡そうとすると、「number 型は string 型に代入できません」という型エラーが発生します。これにより、実行時エラーを未然に防げるのです。

Page Object Model パターンによるテスト構造化の実践

テストの保守性をさらに高めるため、Page Object Model(POM)パターンを導入します。POM は、ページごとにクラスを作成し、UI 操作をカプセル化する設計パターンです。

まず、tests​/​pages ディレクトリを作成します。

bashmkdir tests/pages

次に、tests​/​pages​/​SearchPage.ts を作成します。

Page クラスの基本構造とプロパティ定義

以下のコードで、必要な型をインポートします。

typescriptimport { Page, Locator } from "@playwright/test";

Page はブラウザのページを表す型、Locator は要素を表す型です。

次に、SearchPage クラスを定義します。

typescriptexport class SearchPage {
  readonly page: Page;
  readonly searchBox: Locator;
  readonly searchButton: Locator;
  readonly results: Locator;

readonly を使うことで、インスタンス作成後にプロパティを変更できないようにしています。これにより、予期しない状態変更を防げます。

コンストラクタで各要素のセレクタを定義します。

typescript  constructor(page: Page) {
    this.page = page;
    this.searchBox = page.locator('textarea[name="q"]');
    this.searchButton = page.locator('button[type="submit"]');
    this.results = page.locator('#search');
  }

コンストラクタの引数 pagePage 型として型付けされています。間違った型を渡すと、コンパイルエラーになります。

操作メソッドの型定義と非同期処理

次に、ページ操作を行うメソッドを定義します。

typescript  async goto() {
    await this.page.goto('https://www.google.com');
  }

goto メソッドは引数を取らず、Google のトップページにアクセスします。戻り値は Promise<void> です。

検索を実行するメソッドを追加します。

typescript  async search(query: string) {
    await this.searchBox.fill(query);
    await this.searchBox.press('Enter');
  }

search メソッドは string 型の引数を受け取ります。もし number 型を渡そうとすると、コンパイルエラーが発生します。

検索結果の件数を取得するメソッドも定義しましょう。

typescript  async getResultsCount(): Promise<number> {
    await this.results.waitFor();
    const items = await this.results.locator('div.g').count();
    return items;
  }
}

getResultsCount メソッドは Promise<number> を返します。戻り値の型も明示することで、呼び出し側で型安全に扱えます。

Page Object を使ったテストの実装

SearchPage クラスを使ったテストを書いてみましょう。

typescriptimport { test, expect } from "@playwright/test";
import { SearchPage } from "./pages/SearchPage";

まず、必要なモジュールをインポートします。

次に、テストケースを定義します。

typescripttest("POM を使った検索テスト", async ({ page }) => {
  const searchPage = new SearchPage(page);

  await searchPage.goto();
  await searchPage.search("Playwright TypeScript");

  const count = await searchPage.getResultsCount();
  expect(count).toBeGreaterThan(0);
});

SearchPage のインスタンスを作成し、メソッドを呼び出すだけでテストが完結します。セレクタの詳細はクラス内にカプセル化されているため、テストコードが非常にシンプルになりました。

ページの構造が変わった場合も、SearchPage クラスを修正するだけで、すべてのテストに変更が反映されます。保守性が大幅に向上するのです。

下記の図は、Page Object Model パターンの構造を示しています。

mermaidflowchart TD
  test["テストコード"] --> pom["SearchPage クラス"]
  pom --> prop1["searchBox: Locator"]
  pom --> prop2["results: Locator"]
  pom --> method1["goto() メソッド"]
  pom --> method2["search(query) メソッド"]
  pom --> method3["getResultsCount() メソッド"]

  method1 --> page1["page.goto()"]
  method2 --> page2["searchBox.fill()"]
  method3 --> page3["results.count()"]

  page1 --> browser["ブラウザ操作"]
  page2 --> browser
  page3 --> browser

テストの実行とデバッグ機能の活用

基本的なテスト実行コマンドと並列実行

それでは、作成したテストを実行してみましょう。以下のコマンドを実行してください。

bashyarn playwright test

playwright.config.ts の設定に従い、すべてのブラウザ(Chromium、Firefox、WebKit)でテストが並列実行されます。初回実行時は、ブラウザの起動に数秒かかる場合があります。

特定のブラウザだけで実行したい場合は、--project オプションでプロジェクト名を指定します。

bashyarn playwright test --project=chromium

これにより、Chromium のみでテストが実行されます。

ヘッドレスモードを無効にして、ブラウザの動作を目視で確認したい場合は、--headed オプションを使いましょう。

bashyarn playwright test --headed

ブラウザウィンドウが開き、テストの実行過程を目で追えます。筆者も初めてテストを書く際は、このオプションで動作を確認しながら進めました。

単一のテストファイルだけを実行する場合は、ファイルパスを指定します。

bashyarn playwright test tests/example.spec.ts

テスト結果の確認と HTML レポートの活用

テスト実行後、結果がターミナルに表示されます。すべてのテストが成功すると、以下のような出力が得られます。

bashRunning 6 tests using 3 workers

  ✓  [chromium] › example.spec.ts:3:1 › 基本的なページ操作テスト (1.2s)
  ✓  [firefox] › example.spec.ts:3:1 › 基本的なページ操作テスト (1.8s)
  ✓  [webkit] › example.spec.ts:3:1 › 基本的なページ操作テスト (1.1s)
  ✓  [chromium] › example.spec.ts:10:1 › 検索機能のテスト (2.9s)
  ✓  [firefox] › example.spec.ts:10:1 › 検索機能のテスト (3.2s)
  ✓  [webkit] › example.spec.ts:10:1 › 検索機能のテスト (2.7s)

  6 passed (9.8s)

各テストがどのブラウザで何秒かかったかが一目で分かります。

HTML レポートを確認するには、以下のコマンドを実行します。

bashyarn playwright show-report

ブラウザで詳細なレポートが開き、各テストの実行時間、スクリーンショット、トレース情報を確認できます。失敗したテストがあれば、その詳細も表示されますね。

実運用での活用例: 筆者のプロジェクトでは、CI で HTML レポートを artifacts として保存し、テスト失敗時にチームメンバーがすぐに確認できるようにしています。

Playwright Inspector によるステップ実行

テストが失敗した際のデバッグには、Playwright Inspector が便利です。以下のコマンドで起動できます。

bashyarn playwright test --debug

Inspector が起動し、テストがステップ実行モードになります。各ステップでブラウザの状態を確認しながら、問題箇所を特定できます。

Inspector の左側にはテストコードが表示され、現在実行中の行がハイライトされます。右側にはブラウザウィンドウが表示され、実際の操作が反映されます。

ステップ実行ボタンで 1 行ずつ進めることができ、要素の状態や DOM ツリーも確認できるため、セレクタが正しいかどうかを検証しやすいです。

トレース機能によるテスト実行の記録と分析

トレース機能を使うと、テスト実行の全過程を記録し、後から詳細に分析できます。

以下のコマンドでトレースを記録しながらテストを実行します。

bashyarn playwright test --trace on

トレースファイルは test-results ディレクトリに ZIP 形式で保存されます。

トレースビューアで開くには、以下を実行しましょう。

bashyarn playwright show-trace test-results/example-spec-ts-basic-page-test-chromium/trace.zip

トレースビューアでは、タイムライン上でテストの各ステップを確認でき、以下の情報を取得できます。

  • DOM スナップショット(各ステップでの HTML 構造)
  • ネットワーク通信(API リクエスト・レスポンス)
  • コンソールログ
  • スクリーンショット

筆者の経験では、「テストが失敗したが原因が分からない」という状況で、トレースビューアを使うことで、意図しないリダイレクトや API エラーを発見できました。

よくあるエラー 3: page.goto: net::ERR_CONNECTION_REFUSED

ローカルサーバーが起動していない状態でテストを実行すると発生するエラーです。

bashError: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:3000

発生条件

  • baseURL で指定したローカルサーバーが起動していない
  • ポート番号が間違っている

原因

テスト対象のアプリケーションが起動していないため、ブラウザが接続できません。

解決方法

  1. 別のターミナルでアプリケーションを起動する
bash# 例: Next.js の場合
yarn dev
  1. playwright.config.tswebServer オプションを使い、テスト実行前に自動起動する
typescriptexport default defineConfig({
  webServer: {
    command: "yarn dev",
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
  // その他の設定...
});

この設定により、yarn playwright test 実行時に自動的にアプリケーションが起動されます。

解決後の確認

アプリケーションが正常に起動し、http:​/​​/​localhost:3000 にアクセスできることを確認した後、テストを再実行しましょう。

筆者の環境では、webServer オプションを追加することで、テスト実行の手間が大幅に削減されました。

参考リンク

CI/CD への組み込みと環境変数の活用

GitHub Actions での Playwright テスト自動化

実案件では、コミットやプルリクエストのタイミングで自動的にテストを実行したいケースが多いでしょう。GitHub Actions を使った CI 設定例を紹介します。

.github​/​workflows​/​playwright.yml を作成しましょう。

ワークフローの基本設定とトリガー定義

まず、ワークフロー名とトリガーを定義します。

yamlname: Playwright Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

pushpull_request イベントで、main または develop ブランチへの変更があった場合にテストを実行します。

次に、ジョブの基本設定を記述します。

yamljobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22

runs-on: ubuntu-latest で、最新の Ubuntu 環境でテストを実行します。node-version: 22 で、本記事で推奨している Node.js 22 を指定しています。

依存関係のインストールとブラウザのセットアップ

続いて、依存関係のインストールとテスト実行を追加します。

yaml- name: Install dependencies
  run: yarn install --frozen-lockfile

- name: Install Playwright Browsers
  run: yarn playwright install --with-deps

- name: Run Playwright tests
  run: yarn playwright test

--frozen-lockfile オプションにより、yarn.lockpackage.json の整合性をチェックします。--with-deps オプションで、必要なシステム依存関係(フォントライブラリなど)も自動的にインストールされます。

最後に、テスト結果をアーティファクトとしてアップロードします。

yaml- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 30

if: always() により、テストが成功・失敗に関わらず、HTML レポートがアップロードされます。GitHub の Actions タブからレポートをダウンロードして確認できますね。

環境変数による設定の外部化と型安全な利用

テスト実行時に環境変数を使うことで、環境ごとに異なる設定を外部化できます。.env ファイルをプロジェクトルートに作成しましょう。

bashBASE_URL=http://localhost:3000
TEST_USER_EMAIL=test@example.com
TEST_USER_PASSWORD=password123

.env ファイルを読み込むため、dotenv パッケージをインストールします。

bashyarn add -D dotenv

playwright.config.ts での環境変数読み込み

playwright.config.ts で環境変数を読み込みます。

typescriptimport { defineConfig } from "@playwright/test";
import dotenv from "dotenv";

dotenv.config();

dotenv.config() により、.env ファイルの内容が process.env に読み込まれます。

設定で環境変数を参照しましょう。

typescriptexport default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || "http://localhost:3000",
  },
  // その他の設定...
});

process.env.BASE_URL が未定義の場合は、デフォルト値 'http:​/​​/​localhost:3000' が使用されます。

テストコード内での環境変数の型安全な扱い

テストコード内でも環境変数を使えます。

typescripttest("環境変数を使ったログインテスト", async ({ page }) => {
  await page.goto("/login");

  await page.fill("#email", process.env.TEST_USER_EMAIL!);
  await page.fill("#password", process.env.TEST_USER_PASSWORD!);

  await page.click('button[type="submit"]');
});

! は非 null アサーション演算子で、TypeScript に対して「この値は必ず存在する」ことを示しています。

より型安全に扱いたい場合は、環境変数の型定義ファイルを作成することをお勧めします。tests​/​types​/​env.d.ts を作成しましょう。

typescriptdeclare global {
  namespace NodeJS {
    interface ProcessEnv {
      BASE_URL: string;
      TEST_USER_EMAIL: string;
      TEST_USER_PASSWORD: string;
    }
  }
}

export {};

これにより、process.env.BASE_URL の型が string として推論され、存在しないプロパティにアクセスしようとするとコンパイルエラーになります。

実運用で役立つ応用テクニック

カスタムフィクスチャによるテストの共通化

Playwright では、カスタムフィクスチャを作成することで、テスト間で共通の処理をカプセル化できます。tests​/​fixtures​/​base.ts を作成しましょう。

まず、必要な型をインポートします。

typescriptimport { test as base } from "@playwright/test";
import { SearchPage } from "../pages/SearchPage";

次に、カスタムフィクスチャの型を定義します。

typescripttype MyFixtures = {
  searchPage: SearchPage;
};

test オブジェクトを拡張し、searchPage フィクスチャを追加します。

typescriptexport const test = base.extend<MyFixtures>({
  searchPage: async ({ page }, use) => {
    const searchPage = new SearchPage(page);
    await use(searchPage);
  },
});

export { expect } from "@playwright/test";

extend メソッドにより、元の test オブジェクトに新しいフィクスチャが追加されます。use 関数にインスタンスを渡すことで、テスト内で searchPage として利用できるようになります。

このカスタムフィクスチャを使うと、各テストで SearchPage のインスタンスを自動的に取得できます。

typescriptimport { test, expect } from "./fixtures/base";

test("カスタムフィクスチャを使ったテスト", async ({ searchPage }) => {
  await searchPage.goto();
  await searchPage.search("TypeScript");

  const count = await searchPage.getResultsCount();
  expect(count).toBeGreaterThan(0);
});

SearchPage のインスタンス化がフィクスチャ内で完結しているため、テストコードがさらに簡潔になりました。複数のテストで同じページオブジェクトを使う場合、この手法が非常に便利です。

ビジュアルリグレッションテストによる UI 変更の検知

Playwright は、スクリーンショット比較によるビジュアルリグレッションテストもサポートしています。UI の意図しない変更を防ぐために、実運用でも活用できます。

typescripttest('ビジュアルリグレッションテスト', async ({ page }) => {
  await page.goto('https://example.com');

ページにアクセスした後、ページ全体のスクリーンショットを撮影し、ベースラインと比較します。

typescript// ページ全体のスクリーンショットを撮影し、ベースラインと比較
await expect(page).toHaveScreenshot("homepage.png");

初回実行時に homepage.png がベースラインとして保存されます。2 回目以降の実行では、現在のスクリーンショットとベースラインを比較し、差分があればテストが失敗します。

特定の要素だけを比較することも可能です。

typescript  // 特定の要素だけを比較
  const header = page.locator('header');
  await expect(header).toHaveScreenshot('header.png');
});

筆者の経験では、CSS のリファクタリング後にビジュアルリグレッションテストを実行し、意図しないレイアウト崩れを検出できました。ピクセル単位での差分を検知できるため、細かい UI 変更も見逃しません。

モバイルデバイスエミュレーションによるレスポンシブテスト

Playwright では、モバイルデバイスのエミュレーションも簡単に行えます。playwright.config.ts に設定を追加しましょう。

typescriptimport { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    // デスクトップブラウザ
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

次に、モバイルデバイスの設定を追加します。

typescript    // モバイルデバイス
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
});

これで、yarn playwright test を実行すると、デスクトップとモバイルの両方でテストが実行されます。モバイル環境では、画面サイズや User-Agent が自動的に設定されます。

レスポンシブデザインの動作確認に最適で、筆者のプロジェクトでも「PC では表示されるボタンがスマホでは画面外に出ている」といった問題を早期に発見できました。

この手法が向いているケース・注意すべき制約

Playwright × TypeScript 環境が最大限活きる場面

実運用を通じて、この環境が特に効果を発揮するケースを以下にまとめます。

複数ブラウザでの継続的な検証が必要なプロジェクトでは、Playwright の標準クロスブラウザサポートが強力です。設定ファイルに数行追加するだけで、すべてのテストが 3 つのブラウザで並列実行されます。

チーム開発で型安全性を重視したい場合も適しています。TypeScript の厳格な型チェックにより、セレクタやメソッドの誤りをコーディング時点で検出できるため、レビュー負荷が軽減されます。

CI/CD に組み込み、自動テストを継続的に実行したい環境では、Playwright の高速な並列実行と豊富なレポート機能が活きます。GitHub Actions との統合も簡単です。

iframe や複数タブ、ファイルダウンロードなど複雑な操作を含むテストにも対応できます。Selenium では煩雑だったこれらの操作が、Playwright では API が整備されており、型安全に記述できます。

採用前に確認すべき制約と代替案

一方で、以下のようなケースでは他のツールの方が適している場合もあります。

テスト実行の様子をリアルタイムで UI 上で確認したい場合は、Cypress の Test Runner の方が優れています。Playwright にも --headed--debug モードはありますが、Cypress ほど視覚的に分かりやすいインターフェースではありません。

レガシーブラウザ(IE11 など)のサポートが必須の場合は、Playwright は選択肢から外れます。IE11 は非対応のため、Selenium WebDriver を使う必要があります。

既存のテストコードが大量に存在し、移行コストが高い場合も慎重に判断すべきです。筆者のプロジェクトでも、一部のテストは Selenium のまま残し、新規テストから Playwright に移行する段階的なアプローチを取りました。

まとめ: 型安全な E2E テスト環境の構築と実運用への適用

本記事では、Playwright と TypeScript を組み合わせた型安全な E2E テスト環境の構築方法を、実際の検証結果とともに解説しました。

最小限の手順で環境構築できる点が大きな魅力です。yarn addyarn playwright install、設定ファイル 2 つで、すぐにテストを開始できます。

TypeScript の静的型付けによる型安全性は、実運用でのエラー削減に直結しました。セレクタの typo やメソッド名の誤りをコーディング時点で検出でき、テスト実行時間の無駄を省けます。

Page Object Model パターンやカスタムフィクスチャの活用により、テストコードの再利用性が高まり、保守性が向上します。長期的なメンテナンスコストの削減にも寄与するでしょう。

クロスブラウザテスト、ビジュアルリグレッションテスト、モバイルエミュレーションなど、豊富な機能が標準サポートされており、多様なテストシナリオに対応できます。

ただし、既存のテストコードがある場合の移行コストや、レガシーブラウザ対応が必要な場合の制約には注意が必要です。プロジェクトの要件と照らし合わせ、段階的な導入を検討することをお勧めします。

筆者の経験では、新規プロジェクトや既存プロジェクトの新規テストから Playwright を導入することで、チーム全体のテスト品質が向上しました。ぜひ、本記事の手順を参考に、あなたのプロジェクトにも Playwright × TypeScript による E2E テストを導入してみてください。

関連リンク

著書

とあるクリエイター

フロントエンドエンジニア Next.js / React / TypeScript / Node.js / Docker / AI Coding

;