自動テストチュートリアル

Playwright、Seleniumなどの自動テストツールを学ぼう

コンポーネントの分離

コンポーネントの分離

複数のページで使われる共通のUI部品をコンポーネントとして分離する方法を学びます。

コンポーネントとは?

ヘッダー、フッター、サイドバー、モーダルなど、複数のページで使われるUI部品です。

コンポーネントクラスの作成

// pages/components/Header.ts
import { Page, Locator } from '@playwright/test';

export class Header {
  readonly page: Page;
  readonly logo: Locator;
  readonly searchInput: Locator;
  readonly userMenu: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.logo = page.locator('header .logo');
    this.searchInput = page.locator('header input[type="search"]');
    this.userMenu = page.locator('header .user-menu');
    this.logoutButton = page.locator('header button:has-text("ログアウト")');
  }

  async clickLogo() {
    await this.logo.click();
  }

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

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

Page Objectでコンポーネントを使う

// pages/DashboardPage.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './BasePage';
import { Header } from './components/Header';

export class DashboardPage extends BasePage {
  readonly header: Header;  // コンポーネントをプロパティとして持つ
  readonly welcomeMessage: Locator;

  constructor(page: Page) {
    super(page);
    this.header = new Header(page);
    this.welcomeMessage = page.locator('.welcome-message');
  }

  async goto() {
    await this.navigate('/dashboard');
  }
}

テストでの使用

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

test('ヘッダーからログアウトできる', async ({ page }) => {
  const dashboardPage = new DashboardPage(page);
  await dashboardPage.goto();

  // コンポーネントのメソッドを呼ぶ
  await dashboardPage.header.logout();

  await expect(page).toHaveURL('/login');
});

test('ヘッダーで検索できる', async ({ page }) => {
  const dashboardPage = new DashboardPage(page);
  await dashboardPage.goto();

  await dashboardPage.header.search('テスト');

  await expect(page).toHaveURL(/search/);
});

ディレクトリ構成

pages/
├── components/
│   ├── Header.ts
│   ├── Footer.ts
│   ├── Sidebar.ts
│   ├── Modal.ts
│   └── index.ts
├── BasePage.ts
├── LoginPage.ts
├── DashboardPage.ts
└── index.ts

モーダルコンポーネントの例

// pages/components/ConfirmModal.ts
import { Page, Locator } from '@playwright/test';

export class ConfirmModal {
  readonly page: Page;
  readonly modal: Locator;
  readonly title: Locator;
  readonly message: Locator;
  readonly confirmButton: Locator;
  readonly cancelButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.modal = page.locator('[role="dialog"]');
    this.title = this.modal.locator('.modal-title');
    this.message = this.modal.locator('.modal-message');
    this.confirmButton = this.modal.getByRole('button', { name: '確認' });
    this.cancelButton = this.modal.getByRole('button', { name: 'キャンセル' });
  }

  async confirm() {
    await this.confirmButton.click();
    await this.modal.waitFor({ state: 'hidden' });
  }

  async cancel() {
    await this.cancelButton.click();
    await this.modal.waitFor({ state: 'hidden' });
  }

  async isVisible(): Promise<boolean> {
    return await this.modal.isVisible();
  }
}
💡 コンポーネント化のポイント
  • 2つ以上のページで使われる部品はコンポーネント化
  • 独立して動作できる単位で分離
  • コンポーネント内で完結するメソッドを作る