リンク切れは「静かなるプロダクトの死」。Playwrightで実現する堅牢なリンク検証戦略
フロントエンドエンジニアの皆さんが、日々丹精込めて作り上げたUI。しかし、どれほど美しいデザインや洗練されたアニメーションを実装しても、ユーザーがクリックした先のリンクが「404 Not Found」を返した瞬間、その体験は一気に地に落ちます。
「リンク切れなんて、定期的にクロールツールを回せばいいのでは?」と思っているあなた。それは甘い。実務の現場では、動的なルーティングや認証が必要な画面、あるいはJavaScriptで制御された遷移など、単純なクローラーでは検知できない「罠」が至る所に潜んでいます。
今回は、モダンなE2Eテストのデファクトスタンダードである「Playwright」を用い、単なるクリック確認に留まらない、プロダクトの信頼性を担保するためのリンク検証術を深掘りします。
—
ブラウザは「リンク」をどう見ているか
HTMLの `` タグは、単なるテキストの装飾ではありません。ブラウザはこれを解析する際、単に `href` 属性を読み込むだけでなく、その背後で以下のような複雑な処理を高速で行っています。
1. URLの解決: 相対パスであれば、現在のドキュメントのベースURLを元に絶対URLを算出。
2. ナビゲーションの準備: ユーザーがクリックした瞬間、既存のDOMを破棄し、新しいリクエストを投げる準備(`beforeunload`イベントの発火など)。
3. ステータスコードの処理: リクエストを送り、サーバーからのレスポンスを受信。200以外であればブラウザがデフォルトのエラーページを表示。
我々が書くべきテストは、この「ブラウザの挙動」をシミュレートするだけでなく、「意図したURLへ、正しい手順で遷移できているか」を機械的に検証し、CIパイプラインで自動化することにあります。
—
実践:Playwrightでリンクの「妥当性」と「遷移」を検証する
まずは、最も頻繁に遭遇する「リンクが正しく機能しているか」を検証するコードを見てみましょう。単にクリックするだけでなく、レスポンスコードまで確認するのがプロの流儀です。
import { test, expect } from ‘@playwright/test’;
test(‘重要なCTAリンクが正常に遷移し、200を返すこと’, async ({ page }) => {
// 1. ページへアクセス
await page.goto(‘/pricing’);
// 2. リンクを取得し、href属性が期待通りか確認
const ctaLink = page.getByRole(‘link’, { name: /プラン詳細を見る/i });
await expect(ctaLink).toHaveAttribute(‘href’, /\/plans\/.+/);
// 3. クリックと同時にレスポンスを待機する設計にする
// ネットワークの競合を防ぐため、Promise.allでクリックとレスポンスを紐付ける
const [response] = await Promise.all([
page.waitForResponse((res) => res.status() === 200),
ctaLink.click(),
]);
// 4. 遷移後のURLを検証
await expect(page).toHaveURL(/\/plans\//);
// 5. 念のため、遷移先で目的の要素が存在するかまで確認する
await expect(page.getByRole(‘heading’, { name: ‘料金プラン’ })).toBeVisible();
});
なぜ `Promise.all` を使うのか?
現場でよく見かけるミスが、`click()` だけを記述してテストを終了させてしまうケースです。これでは、クリック直後に発生したリクエストが完了する前にテストが終了してしまい、「クリックは成功したが、実はリンク先がエラーだった」という致命的なバグを見逃します。 上記のように `waitForResponse` を組み合わせるのが、堅牢なテストの鉄則です。
—
現場で役立つ「リンク検証」のTIPS
1. 外部サイトへのリンクは「生存確認」のみを行う
自サイト内のリンクは遷移まで確認すべきですが、外部サイト(Twitterや外部APIなど)へ遷移するリンクを毎回クリックすると、テスト時間が伸びる上に外部サイトの負荷になります。外部へのリンクは `href` の存在確認と、可能であれば `head` リクエストを飛ばしてステータスコードを確認するだけで十分です。
2. `target=”_blank”` の取り扱い
`target=”_blank”` が付与されたリンクは、ブラウザが新しいタブを開きます。Playwrightでこれを扱う際は、以下のテクニックを使いましょう。
test(‘新しいタブで開くリンクの検証’, async ({ page, context }) => {
// 新しいページ(タブ)が開くのを待機するハンドラーをセット
const [newPage] = await Promise.all([
context.waitForEvent(‘page’),
page.getByRole(‘link’, { name: ‘利用規約’ }).click(),
]);
await newPage.waitForLoadState();
await expect(newPage).toHaveURL(/terms/);
await newPage.close(); // 検証後は閉じるのが作法
});
—
結論:テストは「コードの品質」ではなく「体験の品質」を守るもの
フロントエンドのエンジニアがリンクのテストを書くとき、それは単なる「属性の検証」ではありません。「ユーザーが迷子にならず、目的のコンテンツへ辿り着けるか」というUXの導線を守る行為です。
今回紹介した手法をベースに、皆さんのプロジェクトのクリティカルなパスだけでも自動化してみてください。最初は面倒に感じるかもしれませんが、リリース直前に「リンク切れが見つかった!」と焦る冷や汗をかく回数は、確実にゼロに近づくはずです。
さあ、堅牢なテストコードを書いて、自信を持ってデプロイしましょう。それが、シニアエンジニアとしての第一歩です。

コメント