Playwright ページオブジェクトモデル(POM)

この章では、ページオブジェクトモデル(Page Object Model)の設計思想と実装方法を紹介し、大規模なテストスイートを整理するのに役立ちます。


POMとは

ページオブジェクトモデルは、テスト設計パターンであり、ページの要素と操作を独立したクラスにカプセル化します。

各ページオブジェクトは、Webアプリケーション内の1つのページまたはコンポーネントを表し、そのページのロケーターと操作メソッドを含みます。


POMの利点

利点説明
テスト作成の簡素化テストコードは高レベルAPI(例:loginPage.login('user', 'pass'))を使用し、生のLocator操作は使用しません
保守コストの削減ページ構造の変更はPage Objectクラスを修正するだけでよく、各テストを修正する必要はありません
可読性の向上テストコードがビジネス言語に近くなり、非技術者にも理解しやすくなります
重複の回避同じロケーターと操作は一度だけ定義すればよい

Page Objectクラスの作成

以下ではサンプルサイトを例に、ログインページのPage Objectを作成します。

例

// ファイルパス: pages/LoginPage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  // すべてのロケーターを読み取り専用プロパティとして宣言
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    // コンストラクタ内で全てのロケーターを初期化
    this.usernameInput = page.getByLabel('ユーザー名');
    this.passwordInput = page.getByLabel('パスワード');
    this.loginButton = page.getByRole('button', { name: 'ログイン' });
    this.errorMessage = page.getByTestId('login-error');
  }

  // ログインページに移動
  async goto() {
    await this.page.goto('/login');
  }

  // ログイン操作を実行
  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }

  // ログイン失敗時のエラーメッセージを検証
  async expectLoginError(message: string) {
    await expect(this.errorMessage).toBeVisible();
    await expect(this.errorMessage).toContainText(message);
  }
}

テストでPage Objectを使用する

例

// ファイルパス: tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('ログイン機能', () => {
  test('正しいユーザー名とパスワードでログイン成功', async ({ page }) => {
    const loginPage = new LoginPage(page);

    // Page Objectのメソッドを使用
    await loginPage.goto();
    await loginPage.login('サンプル_user', 'correct_password');

    // ログイン成功後にリダイレクトを検証
    await expect(page).toHaveURL('/dashboard');
  });

  test('誤ったパスワードでログイン失敗', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await loginPage.goto();
    await loginPage.login('サンプル_user', 'wrong_password');

    // Page Objectのアサーションメソッドを使用
    await loginPage.expectLoginError('ユーザー名またはパスワードが正しくありません');
  });
});

複数層のページオブジェクト

実際のプロジェクトでは、複数のPage Objectを作成でき、それらは相互に関連付けることができます。

例

// ファイルパス: pages/DashboardPage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class DashboardPage {
  readonly page: Page;
  readonly welcomeText: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.welcomeText = page.getByText('おかえりなさい');
    this.logoutButton = page.getByRole('button', { name: 'ログアウト' });
  }

  async expectWelcomeMessage(username: string) {
    await expect(this.welcomeText).toContainText(username);
  }

  async logout() {
    await this.logoutButton.click();
  }
}

ページオブジェクトの組み合わせ使用:

例

test('ログインとログアウトの完全なフロー', async ({ page }) => {
  const loginPage = new LoginPage(page);
  const dashboardPage = new DashboardPage(page);

  // ログイン
  await loginPage.goto();
  await loginPage.login('サンプル_user', 'password');

  // ダッシュボードを検証
  await dashboardPage.expectWelcomeMessage('サンプル_user');

  // ログアウト
  await dashboardPage.logout();
  await expect(page).toHaveURL('/login');
});

POMのベストプラクティス

実践説明
Locatorをコンストラクタに集中すべてのロケーターはコンストラクタで定義し、メソッド内で動的に作成しない
メソッドはPage Objectを返す操作メソッドは新しいPage Objectを返すことができます(例:ログイン後にDashboardPageを返す)
アサーションをカプセル化するが、乱用しないよく使われるアサーションの組み合わせはメソッドにカプセル化し、単純なアサーションはテストに直接書きます
継承ではなくコンポジションを使用深い継承を避け、コンポジションパターンで複数のPage Objectを関連付けます
1セットのオブジェクトで1つのページ/コンポーネントをカバー粒度は適度にし、小さな要素ごとに個別のクラスを作成しないでください

POMは必須ではありません。

テストが数個しかない小規模プロジェクトでは、テストに直接Locatorを書く方が明確です。テストファイルが5つ以上になったら、POMの導入を検討してください。

その他の拡張