T-CREATOR

Playwright × TypeScript 超入門チュートリアル:型安全 E2E を最短構築

Playwright × TypeScript 超入門チュートリアル:型安全 E2E を最短構築

Web アプリケーション開発において、E2E テストは品質保証の要となります。しかし、テストコードの保守性や型安全性が課題となることも少なくありません。

本記事では、Playwright と TypeScript を組み合わせて、型安全な E2E テスト環境を最短で構築する方法をご紹介します。初めて触れる方でも、手順通り進めれば確実に動作する環境が手に入りますよ。

背景

E2E テストの重要性

E2E(End-to-End)テストは、ユーザーが実際に操作する流れをシミュレートし、アプリケーション全体の動作を検証するテスト手法です。単体テストや統合テストでは検出できない、画面遷移やユーザーインタラクションに関する問題を発見できます。

近年の Web アプリケーションは複雑化しており、手動テストだけでは品質を担保するのが困難になってきました。そこで自動化された E2E テストが必要不可欠となっています。

Playwright が選ばれる理由

Playwright は Microsoft が開発する、モダンな Web アプリケーション向けの E2E テストフレームワークです。以下のような特徴があります。

#項目詳細
1クロスブラウザ対応Chromium、Firefox、WebKit を標準サポート
2高速実行並列実行やヘッドレスモードに対応
3強力な API自動待機機能やネットワーク制御など充実
4TypeScript サポート型定義が標準で提供され、型安全な開発が可能
5デバッグ機能Inspector やトレース機能で問題解析が容易

下記の図は、Playwright を使った E2E テストの基本的なフローを示しています。

mermaidflowchart LR
  dev["開発者"] -->|テストコード作成| pw["Playwright"]
  pw -->|ブラウザ起動| chromium["Chromium"]
  pw -->|ブラウザ起動| firefox["Firefox"]
  pw -->|ブラウザ起動| webkit["WebKit"]
  chromium -->|操作・検証| app["Web アプリ"]
  firefox -->|操作・検証| app
  webkit -->|操作・検証| app
  app -->|結果| pw
  pw -->|レポート| dev

このように、Playwright は複数のブラウザに対して同一のテストコードを実行し、結果を統合してレポートできます。

TypeScript との組み合わせのメリット

TypeScript を使用することで、以下のメリットが得られます。

テストコード作成時に、Playwright の API を型補完によって素早く記述できるでしょう。また、リファクタリング時の変更漏れを防ぎ、セレクタやデータの型を明示することで可読性が向上します。

開発チーム全体で型安全なコードベースを維持できれば、長期的なメンテナンスコストも削減できますね。

課題

E2E テスト導入時の障壁

E2E テストを導入する際、以下のような課題に直面することがあります。

環境構築の複雑さがあります。複数のツールやライブラリの組み合わせが必要で、初期設定に時間がかかるケースが多いです。

テストコードの保守性も問題となります。型がないと、セレクタの変更やリファクタリング時にエラーが発生しやすくなります。

実行速度の遅さにより、開発フローに組み込みにくい場合もあるでしょう。

ブラウザ互換性のテストを行うには、複数のブラウザドライバを管理する必要があります。

下記の図は、従来の E2E テスト環境における課題を図解したものです。

mermaidflowchart TD
  start["E2E テスト導入"] --> setup["環境構築"]
  setup --> config["複雑な設定ファイル"]
  setup --> driver["ブラウザドライバ管理"]
  config --> error1["設定ミス"]
  driver --> error2["バージョン不整合"]

  start --> code["テストコード作成"]
  code --> noType["型定義なし"]
  noType --> error3["実行時エラー"]
  noType --> error4["保守性低下"]

  error1 --> fail["導入断念"]
  error2 --> fail
  error3 --> fail
  error4 --> fail

これらの課題を解決するには、シンプルで型安全な環境構築が重要となります。

型安全性の欠如による問題

JavaScript で E2E テストを書く場合、以下のような問題が発生しがちです。

javascript// セレクタの typo に気づかない
await page.click('.sbumit-button'); // submit の typo

// 存在しないメソッドを呼び出してしまう
await page.clck('.submit-button'); // click の typo

// 引数の型が間違っていても気づかない
await page.fill('.email-input', 123); // 文字列が必要なのに数値を渡している

このようなエラーは実行時にしか検出できず、テスト実行時間の無駄につながります。TypeScript を使えば、これらのエラーをコーディング時点で検出できるのです。

解決策

Playwright × TypeScript 環境の構築

Playwright と TypeScript を組み合わせることで、上記の課題を一気に解決できます。以下の手順で環境を構築しましょう。

型安全性の確保により、コーディング時点でエラーを検出できます。シンプルな設定で、最小限の手順で実行環境を整えられます。高速な実行も可能で、並列実行やヘッドレスモードを活用できるでしょう。

下記の図は、Playwright × TypeScript 環境における開発フローを示しています。

mermaidflowchart LR
  dev["開発者"] -->|TypeScript でコード作成| ts["型チェック"]
  ts -->|エラー検出| fix["コード修正"]
  fix --> ts
  ts -->|型安全確認| build["ビルド"]
  build -->|JavaScript 生成| pw["Playwright 実行"]
  pw -->|並列実行| test["E2E テスト"]
  test -->|成功| report["レポート生成"]
  test -->|失敗| debug["デバッグ"]
  debug --> fix
  report --> dev

このフローにより、型安全性を保ちながら効率的にテストを実行できますね。

環境構築の全体像

環境構築は以下のステップで進めます。

#ステップ内容
1Node.js 確認Node.js がインストールされているか確認
2プロジェクト初期化package.json の作成
3Playwright インストール必要なパッケージの導入
4TypeScript 設定tsconfig.json の作成
5テストコード作成初回テストの作成
6テスト実行動作確認

それでは、順番に見ていきましょう。

具体例

前提条件の確認

まず、Node.js がインストールされているか確認します。ターミナルで以下のコマンドを実行してください。

bashnode -v

バージョンが表示されれば OK です。Node.js 18 以上を推奨します。インストールされていない場合は、公式サイトからダウンロードしましょう。

プロジェクトの初期化

新しいディレクトリを作成し、プロジェクトを初期化します。

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

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

bashyarn init -y

これにより、package.json が自動生成されます。

Playwright のインストール

Playwright を TypeScript サポート付きでインストールします。以下のコマンドを実行してください。

bashyarn add -D @playwright/test
yarn add -D typescript

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

続いて、Playwright の初期設定を行います。

bashyarn playwright install

このコマンドにより、Chromium、Firefox、WebKit の各ブラウザバイナリがダウンロードされます。初回は時間がかかる場合がありますが、待ちましょう。

TypeScript 設定ファイルの作成

TypeScript の設定ファイル tsconfig.json をプロジェクトルートに作成します。

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"]
}

各設定項目の意味を説明します。

#設定項目説明
1targetコンパイル後の JavaScript バージョン
2moduleモジュールシステムの指定
3strict厳格な型チェックを有効化
4esModuleInteropCommonJS との互換性向上
5outDirコンパイル後のファイル出力先
6rootDirソースコードのルートディレクトリ

strict: true により、TypeScript の型チェックが厳格になり、型安全性が高まります。

Playwright 設定ファイルの作成

プロジェクトルートに playwright.config.ts を作成します。この設定ファイルで、テストの実行方法やブラウザの設定を行います。

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

export 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',

上記は基本設定の部分です。testDir でテストファイルの場所を指定し、timeout でテスト全体のタイムアウトを設定しています。

続いて、ブラウザの設定を追加しましょう。

typescript  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

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

projects セクションで、テストを実行するブラウザを指定します。複数のブラウザを並列実行することで、クロスブラウザテストが簡単に実現できますね。

各設定項目の役割をまとめます。

#設定項目役割
1baseURLテスト対象の基本 URL
2trace失敗時のトレース記録
3screenshotスクリーンショット撮影タイミング
4projects実行するブラウザの種類
5fullyParallelテストの並列実行を有効化

テストディレクトリの作成

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

bashmkdir tests

これで、テストコードを配置する準備が整いました。

初めてのテストコードを書く

tests​/​example.spec.ts ファイルを作成し、基本的なテストを書いてみましょう。まず、必要なモジュールをインポートします。

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

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

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

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

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

page オブジェクトは Playwright が提供するブラウザページのインスタンスです。goto メソッドで指定 URL にアクセスし、expect でタイトルを検証しています。

続いて、もう少し複雑な操作を含むテストを追加しましょう。

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

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

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

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

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

このテストでは、要素の検索(locator)、テキスト入力(fill)、キー操作(press)、URL 検証、要素の可視性確認など、基本的な操作を網羅しています。

TypeScript の型チェックにより、メソッド名や引数の型を間違えた場合は、コーディング時点でエラーが表示されますよ。

型安全なセレクタの活用

Playwright では、さまざまなセレクタ方法が用意されています。TypeScript と組み合わせることで、型安全にセレクタを扱えます。

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

  // 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');

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

各セレクタメソッドは TypeScript の型定義により、適切な引数の型が保証されています。間違った型を渡すとコンパイルエラーになるため、安心ですね。

Page Object Model パターンの実装

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

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

bashmkdir tests/pages

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

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

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

  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');
  }

ここでは、コンストラクタで各要素のセレクタを定義しています。readonly を使うことで、誤って値を変更できないようにしていますね。

続いて、操作メソッドを追加しましょう。

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

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

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

各メソッドは async で定義し、非同期操作を扱います。TypeScript の型定義により、query の型が string であることが保証されます。

テストコードから POM を使う例を見てみましょう。

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

test('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 クラスを修正するだけで済むため、保守性も高いですね。

テストデータの型定義

テストで使用するデータも TypeScript で型定義しましょう。tests​/​types​/​user.ts を作成します。

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

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

これらの型を使ってテストデータを定義します。tests​/​fixtures​/​users.ts を作成しましょう。

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

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

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

型定義により、必須フィールドの漏れや型の不一致をコンパイル時に検出できます。テストコードで使用する際も、自動補完が効いて便利ですよ。

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

test('ユーザー登録テスト', 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.age は number 型なので、文字列に変換
  await page.fill('#age', testUser.age.toString());

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

テストの実行

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

bashyarn playwright test

すべてのブラウザ(Chromium、Firefox、WebKit)でテストが並列実行されます。初回実行時は少し時間がかかるかもしれません。

特定のブラウザだけで実行したい場合は、以下のようにプロジェクト名を指定します。

bashyarn playwright test --project=chromium

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

bashyarn playwright test --headed

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

bashyarn playwright test tests/example.spec.ts

テスト結果の確認

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

bashRunning 6 tests using 3 workers

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

  6 passed (10s)

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

bashyarn playwright show-report

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

デバッグ機能の活用

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

bashyarn playwright test --debug

Inspector が起動し、ステップ実行やブレークポイントの設定が可能になります。各ステップでブラウザの状態を確認しながら、問題箇所を特定できるでしょう。

トレース機能を使う場合は、以下のコマンドでトレースを記録します。

bashyarn playwright test --trace on

トレースファイルは test-results ディレクトリに保存されます。トレースビューアで開くには、以下を実行しましょう。

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

タイムライン上でテストの各ステップを確認でき、DOM スナップショットやネットワーク通信も確認できます。

CI/CD への組み込み

GitHub Actions を使って、Playwright テストを CI に組み込む例を見てみましょう。.github​/​workflows​/​playwright.yml を作成します。

yamlname: Playwright Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18

まず、基本的なセットアップを行います。Node.js 18 をインストールし、リポジトリをチェックアウトしています。

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

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
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 30

--with-deps オプションにより、必要なシステム依存関係も自動的にインストールされます。テスト結果は成功・失敗に関わらずアーティファクトとしてアップロードされるため、後から確認できますね。

環境変数の活用

テスト実行時に環境変数を使う方法も見ておきましょう。.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 で環境変数を読み込みましょう。

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

dotenv.config();

export default defineConfig({
  use: {
    baseURL:
      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 に対して「この値は必ず存在する」ことを示しています。

カスタムフィクスチャの作成

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

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

type MyFixtures = {
  searchPage: SearchPage;
};

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

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

このフィクスチャを使うと、各テストで 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);
});

フィクスチャにより、テストコードがさらに簡潔になりますね。

ビジュアルリグレッションテスト

Playwright は、スクリーンショット比較によるビジュアルリグレッションテストもサポートしています。

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

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

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

初回実行時にベースラインとなるスクリーンショットが保存され、2 回目以降の実行で差分が検出されます。UI の意図しない変更を防げるため、リファクタリング時に便利ですよ。

モバイルデバイスのエミュレーション

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

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

export default defineConfig({
  projects: [
    // デスクトップブラウザ
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    // モバイルデバイス
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
});

これで、モバイルデバイスの画面サイズや User-Agent でテストが実行されます。レスポンシブデザインの動作確認に最適ですね。

まとめ

本記事では、Playwright と TypeScript を組み合わせた型安全な E2E テスト環境の構築方法をご紹介しました。

環境構築の簡単さが魅力です。最小限のコマンドで、すぐにテストを開始できます。型安全性の恩恵により、コーディング時点でエラーを検出し、保守性の高いテストコードを書けるでしょう。

豊富な機能も見逃せません。クロスブラウザテスト、ビジュアルリグレッションテスト、モバイルエミュレーションなど、多様なテストシナリオに対応できます。CI/CD への組み込みも容易で、開発フローに自然に統合できますね。

Page Object Model パターンやカスタムフィクスチャを活用することで、テストコードの再利用性が高まり、長期的なメンテナンスコストを削減できます。

ぜひ、本記事の手順を参考に、あなたのプロジェクトにも Playwright × TypeScript による E2E テストを導入してみてください。品質の高い Web アプリケーション開発に、大きく貢献することでしょう。

関連リンク