l

o

a

d

i

n

g

.

.

.

E2EのフロントテストでPlaywrightを使うことを決めた話 #10. プロジェクト固有の Page Object モデルを導入する

2026/02/02

テクノロジー 学習

t f B! P L
eyecatch プロジェクト固有の Page Object Model(POM)を導入する方法について学習します。 POMというのは、1つのページ or 画面機能を クラスとして表現し、その中に「操作メソッド」と「要素取得」をまとめる設計手法です。 テストコードから UI の構造を隠蔽し、「ページをどう操作すべきか」を明確にすることができます。

なぜPlaywrightでPOMが重要なのか?

もはや言わずもがなですが、POMを使うことにより、次のような特徴が生まれます。 ・変更に強いテストになる ・テストコードが激減する ・プロジェクト固有のUIルールを共通化できる

変更に強いテストになる

画面の CSS / DOM が変わっても修正はそのページクラスだけで完結することができるようになります。 要するに、テスト全体を直す必要がなくなるんですね。

テストコードが激減する

ページ操作の「お決まりパターン」をメソッド化することで、テスト側は意味だけを書くコードになります。

プロジェクト固有のUIルールを共通化できる

作ったサービス特有の構造(独自モーダル、Flow UIなど)がある場合、その操作方法を POM側に閉じ込めることができます。 例えば、次のようなケースですね。 「このサービスのカードは id をクリックすると詳細ページに飛ぶ」 「このサービスは全ページで共通の検索バーがある」 POMを使うことで、プロジェクト特有の操作ルールを標準化するできるんです。

サンプルコード

基本設計

tests/ ├ pages/ │ ├ LoginPage.ts │ └ DashboardPage.ts ├ smoke/ └ detail/

pages/LoginPage.ts

import { Page, Locator } from '@playwright/test'; export class LoginPage { readonly page: Page; readonly email: Locator; readonly password: Locator; readonly submit: Locator; constructor(page: Page) { this.page = page; this.email = page.getByLabel('メールアドレス'); this.password = page.getByLabel('パスワード'); this.submit = page.getByRole('button', { name: 'ログイン' }); } async goto() { await this.page.goto('/login'); } async login(email: string, password: string) { await this.email.fill(email); await this.password.fill(password); await this.submit.click(); } }

テスト実行側

import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; test('ログインが成功する', async ({ page }) => { const login = new LoginPage(page); await login.goto(); await login.login('test@example.com', 'password'); await expect(page).toHaveURL('/dashboard'); }); 実行時に、UIの細かい操作が一切出てこないし、意味のある“ビジネスロジック”だけ残る状態になります。

プロジェクト固有POMの導入ポイント

1. 共通コンポーネントは“page”ではなく“component”として切り出す

・独自の Modal ・Drawer ・Sidebar ・Tab UI ・カードリスト
ex) Modal export class Modal { constructor(private page: Page) {} get root() { return this.page.locator('.modal'); } async close() { await this.root.getByRole('button', { name: '閉じる' }).click(); } }

2. プロジェクト特有の “操作パターン” をメソッド化する

作ったプロジェクトに次のようなUIの暗黙のルールが存在するような場合。
・カードを押すと編集ページへ飛ぶ ・検索バーにキーワードを入れるとテーブル更新 ・削除時は必ず確認ダイアログが開く
これを POM に書いてしまえば、テスト側が UI の内部構造を一切意識せずに済みます。

拡張:BasePage を作るとさらに管理しやすい

export class BasePage { constructor(public page: Page) {} async waitLoaded() { await this.page.waitForLoadState('networkidle'); } } 各ページはこれを継承サンプル export class DashboardPage extends BasePage { async goto() { await this.page.goto('/dashboard'); await this.waitLoaded(); } } これにより、全ページの安定性が段違いにあがります。

あとがき

コードの内容というより、ファイル分けしたオブジェクト思考的な構造体がPOMの正体ですね。 テストコードは、とにかく陳腐化しやすいものです。 Webページのリニューアルみたいなことをやると、すぐにテストとして使えないゴミコードになってしまいます。 そんな時に、UI部分と、構造の基礎部分を分離しておくことで、更新箇所を極限まで少なくすることができるでしょう。 あまり推奨されてはいませんが、要素をまとめたモデルを実ソースに構築しておき、それと連動するような形にするという手もなくもないですが、依存関係が複雑化しそうなので、よほどテストも含めて構造管理ができる場合でない限りはオススメできないんですよね。 とにかく、安定したテストというのは、できる限り手戻りの少ないテストと言っても過言ではないでしょう。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ