コンポーネントの分離
コンポーネントの分離
複数のページで使われる共通の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つ以上のページで使われる部品はコンポーネント化
- 独立して動作できる単位で分離
- コンポーネント内で完結するメソッドを作る