0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Nuxt3】loading=”lazy”を使用するとplaywrightのVRTテストに失敗する

Last updated at Posted at 2025-05-19

概要

Nuxt3 でウェブサイトのパフォーマンス改善のため、画像に loading="lazy" 属性を追加することは一般的です。しかし、これを行うと Playwright を使ったビジュアルリグレッションテスト (VRT) が失敗するという問題に直面することがあります。この記事では、その原因と具体的な解決策について解説します。

原因

この問題の原因は、Playwright がページのスクリーンショットを撮影するタイミングにあります。loading="lazy" が設定された画像は、ビューポートに入るまで読み込みが遅延されます。Playwright がスクリーンショットを撮る際に、これらの画像がまだ読み込まれていない(表示されていない)状態であるため、期待される画像が表示された状態のスクリーンショットと比較すると差分が生じ、テストが失敗します。

解決策

この問題を解決するためのアプローチは、テスト実行時に画像を強制的に読み込ませ、それが完了するまで待機することです。

解決策のポイントは以下です。

  • 遅延読み込みを無効化する (loading = 'eager')
  • すべての画像の読み込みが完了するまで待機する
    • この判定処理がないと一部のブラウザで失敗する場合がある
  • 画像の読み込み処理は並列処理で対応する

以下は具体的な Playwright テストコードです。

test('VRTテスト', async ({ page }) => {
  // ページ内の遅延読み込み画像を強制的に読み込み、完了を待つ
  await page.evaluate(async () => {
    // loading="lazy" 属性を持つ要素をすべて取得
    const elementsWithLazyLoad = document.querySelectorAll('[loading="lazy"]')
    // Promise を格納する配列
    const elementLoadPromises: Promise<void>[] = []

    // 画像の読み込み処理
    for (const element of Array.from(elementsWithLazyLoad)) {
      // 型チェック
      if (element instanceof HTMLImageElement) {
        // loading="lazy" を "eager" に変更して強制的に読み込みを開始
        element.loading = 'eager'

        // 画像の読み込み完了またはエラー発生を待つPromiseを作成
        elementLoadPromises.push(
          new Promise((resolve) => {
            // 読み込み完了またはエラー発生時のハンドラ
            const handleLoadOrError = () => {
              // イベントリスナーを解除
              element.removeEventListener('load', handleLoadOrError)
              element.removeEventListener('error', handleLoadOrError)

              // 待機Promiseを解決
              resolve()
            }

            // load/error イベントリスナーを追加
            element.addEventListener('load', handleLoadOrError)
            element.addEventListener('error', handleLoadOrError)

            // 既に読み込みが完了している場合は即座にハンドラを実行
            if (element.complete) {
              handleLoadOrError()
            }
          })
        )
      }
    }

    // すべての画像の読み込みPromiseが完了するまで待機
    if (elementLoadPromises.length > 0) {
      await Promise.all(elementLoadPromises)
    }
  })

  // 画像の読み込みが完了した状態でスクリーンショットを撮影し、VRTを実行
  await expect(page).toHaveScreenshot({
    fullPage: true
  })
})

その他の検討事項

Playwright issue (#19277) では、画像を読み込ませる方法として「画面をスクロールさせる」方法が議論されているようです。しかし、今回はその方法は採用しませんでした。その理由は、スクロールによって意図しない要素(例: CTAボタン)が表示されてしまい、想定しているスクリーンショットとの間に不要な差分が発生し、テスト結果が不安定になる可能性があるためです。前述の loading="eager" に変更して待機する方法の方が、テスト対象の要素に直接作用し、安定した結果を得やすいと考えられます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?