リンクの「到達性」を極める:Playwrightで実装する堅牢なナビゲーションテスト
フロントエンドのアーキテクチャがどれほど洗練されていようとも、ユーザーがクリックするたった一つの「リンク」が死んでいれば、すべては無に帰す。これは単なるDOMの整合性の問題ではなく、アプリケーションの信頼性を左右するクリティカルな境界条件だ。
今回は、単なる「クリックしてページが変わるか確認する」という初歩的なテストを超え、上級エンジニアが向き合うべき「リンクの到達性検証」の深淵に踏み込んでいく。
—
1. なぜ「リンクの検証」は泥沼化するのか
多くの開発者は `await page.click(‘a’)` で満足する。しかし、現実のWebアプリケーションは非同期の嵐だ。
- ハイドレーションの競合: ReactやNext.jsにおけるハイドレーション完了前にリンクをクリックし、ルーティングがフックされる前にブラウザがデフォルトのナビゲーションを行ってしまうケース。
- メモリとレンダリング負荷: SPAにおけるルーティングの切り替え時、適切なクリーンアップが行われないままコンポーネントがメモリリークし、リンククリック後のリフローでブラウザがスタックする。
- 不正なリダイレクト: 開発環境では正しくても、本番環境のCDNやセキュリティレイヤーで意図しない302/301が挟まるリスク。
これらを防ぐには、Playwrightを用いた「多層的な検証」が不可欠となる。
—
2. 実装:堅牢なリンク検証アーキテクチャ
単に `href` を確認するのではなく、ネットワークレベルでのステータスコード検証と、レンダリング後の状態(DOMの状態)の整合性を両立させる。
import { test, expect, Page } from ‘@playwright/test’;
/
- リンクの到達性を検証するユーティリティ
- 単なる遷移確認だけでなく、ネットワークの健全性とDOMの安定性を担保する
/
async function verifyLinkNavigation(page: Page, selector: string, expectedUrlPath: string) {
const locator = page.locator(selector);
// 1. 存在確認と可視性(インタラクティブであることを保証)
await expect(locator).toBeVisible();
// 2. hrefの妥当性をTypeScriptで厳格にチェック
const href = await locator.getAttribute(‘href’);
if (!href || href.startsWith(‘#’)) {
throw new Error(`Invalid or anchor-only link detected: ${selector}`);
}
// 3. 非同期の競合を避けるためのPromise待機
// clickをトリガーにする前に、ナビゲーションの完了を補足する
const navigationPromise = page.waitForURL(`${expectedUrlPath}`);
await locator.click();
// 4. ナビゲーションの完了を待ちつつ、ステータスコードを検証
await navigationPromise;
expect(page.url()).toContain(expectedUrlPath);
// 5. レンダリングの安定性チェック(リフロー完了を待つ)
// ページ内のメインコンテンツ要素が安定するまで待機する手法
await page.waitForLoadState(‘networkidle’);
await expect(page.locator(‘main’)).toBeAttached();
}
—
3. 上級者のための「エッジケース」戦略
競合状態の排除
`page.click()` はクリックイベントを発火させるが、SPAのルーターがそれを乗っ取る前にブラウザが遷移を開始してしまうことがある。これを防ぐには、`page.waitForEvent(‘request’)` を用いて、リンククリックによって発火した特定のリクエストが返ってくることを確認するか、あるいは `page.waitForURL` を厳密に宣言するのが定石だ。
パフォーマンスと負荷の可視化
大規模なページでリンクを全走査する場合、すべてのリンクに対して単独でページ遷移を繰り返すと、テスト時間が指数関数的に増大する。
- 戦略: `head` タグや `fetch` API を使い、リンク先のHTTPステータスコード(200 OK)だけを先行して検証する「ヘッドレス検証」をまず行う。その上で、重要なパスのみを本番のUIテストとして組み込むのが、効率と精度のバランスを取る「賢い」設計だ。
—
4. なぜ `time` や `code` タグの検証が重要なのか
本稿のテーマである `a`, `span`, `strong` などのHTMLセマンティクスは、単なる見た目の装飾ではない。
- `time` 要素: `datetime` 属性の妥当性を検証しているだろうか? SEOや機械可読性の観点から、`time` 要素のフォーマットが崩れていると、検索エンジンへのシグナル送信に失敗する。
- `code` 要素: コンテンツ内に `code` タグが含まれる場合、CSSの `white-space: pre-wrap` 等の指定により、レイアウトが崩れやすい。リンクと並列で検証することで、タイポグラフィの崩れを早期に検知できる。
これらは `page.textContent()` を検証するだけでなく、`page.locator(‘time’).getAttribute(‘datetime’)` のように、属性値まで含めた「データとしての正当性」を検証することが、フロントエンド・エンジニアとしての矜持である。
—
結論
フロントエンドのテストは、単なる「動くことの確認」ではない。ブラウザエンジンがどのようにDOMを構築し、メモリを消費し、リフローを引き起こすのかを想像しながら、「どこが壊れやすいか」を推測し、それをコードで先回りして叩くことだ。
Playwrightは強力な武器だが、それを使う側の設計思想が甘ければ、テストは単なるノイズになる。今回提示したような、ネットワーク、URL、DOMの三位一体の検証を標準化することで、あなたのアプリケーションはより堅牢で、予測可能なものへと進化するはずだ。
さあ、コードを書いて、壊れる前に壊そう。それが上級エンジニアの流儀だ。

コメント