私たちの立場を明言しておきます。私たちはGeonodeであり、プロキシを販売しています。プロキシ経由でのスクリーンショットは、当社の製品における比較的クリーンなユースケースの一つです。別の国からそのページが実際にどのように見えるかを確認することは、どのAPIでも教えてくれないことだからです。 正直なところ、懸念点はコストです。ブラウザは画像、フォント、スクリプト、動画をすべてプリロードするため、スクリーンショットの取得は、通信量制限のある環境において最も帯域幅を消費する作業となります。 これを削減する方法については以下のセクションで説明していますが、そこに紹介されている手法を用いれば、安価なプロバイダーを選ぶよりもさらにコストを削減できます。
スクリーンショットの3種類
ビューポート — 現在表示されている範囲。デフォルトの設定です:
await page.screenshot({ path: 'viewport.png' });
フルページ — ドキュメントでは、「まるで非常に背の高い画面があり、ページがそこに完全に収まるかのように、スクロール可能なページ全体をキャプチャしたもの」と説明されています:
await page.screenshot({ path: 'full.png', fullPage: true });
要素 — 「単一の要素のスクリーンショットを撮ることが役立つ場合もあります」:
await page.getByRole('article').screenshot({ path: 'element.png' });
どちらを選ぶかは、主に結果の用途によって決まります。ビューポートスクリーンショットは「ユーザーが最初に何を見るか」という問いに答えます。 全ページスクリーンショットは「このページには何が表示されているか」という問いに答えます。要素スクリーンショットは「このコンポーネントは正しく表示されているか」という問いに答えるものであり、質問の対象外となる要素をすべて除外しているため、比較を行う上で3つのうち最も安定した結果が得られます。
フルページのスクリーンショットとその問題点
fullPage: true は、多くの人が利用しているオプションですが、最も注意点の多いものです。
遅延読み込みされるコンテンツが表示されない場合があります。 Playwrightはスクロールしてキャプチャを行いますが、交差時に読み込まれる画像やコンポーネントは、キャプチャが完了するまでに読み込みが終わっていない可能性があります。 確実な対処法は、意図的にスクロールし、期待するコンテンツが表示されるまで待つことです:
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await expect(page.getByRole('img').last()).toBeVisible();
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({ path: 'full.png', fullPage: true });
スティッキーヘッダーが重複したり、不自然に浮いたりすることがあります。 position: fixed や sticky が設定された要素は、スティッチキャプチャでは予期せぬ動作をすることがあります。styleオプション(「キャプチャ中のスタイリングのためにページに挿入するCSS文字列」)を使用するのが、最も適切な解決策です:
await page.screenshot({
path: 'full.png',
fullPage: true,
style: '.sticky-header { position: absolute !important; }',
});
ページが非常に長いと、ファイルサイズも非常に大きくなります。 無限スクロールフィードには自然な下端がありません。代わりに、clip を使用して定義された領域のみをキャプチャすることを検討してください。このオプションは、「結果の画像のクリッピングを指定するオブジェクト」を受け取ります。
オーバーレイもキャプチャされます。 クッキーバナー、チャットウィジェット、モーダルは、ユーザーに表示されるのと同じ状態でスクリーンショットに写り込みます。これらをキャプチャしたくない場合は、まず閉じてください。閉じることはできない場合は、mask をご利用ください。
要素のスクリーンショットとバッファ
要素のスクリーンショットは、要素を画面内にスクロール表示し、そのバウンディングボックスのみをキャプチャします。そのため、コンポーネントレベルのチェックには最適なデフォルト設定となっています。
const card = page.getByTestId('product-card').first();
await card.screenshot({ path: 'card.png' });
ただし、次の2つのことは行いません。overflow: hidden
によって切り取られたコンテンツのキャプチャ、および視覚的には重なっていても要素のボックスの外側にあるもののキャプチャです。
ファイルの代わりにバッファを使用する。 ドキュメントには、「ファイルに書き込む代わりに、画像を含むバッファを取得して後処理を行ったり、サードパーティのピクセル差分解析ツールに渡したりすることができます」と記載されています。 path
を省略すると、バイトデータが返されます:
const buffer = await page.screenshot();
const base64 = buffer.toString('base64');
スクリーンショットをローカルディスク以外の場所(オブジェクトストア、API、レポート、差分比較サービスなど)に送信する場合は、この形式を使用します。また、ファイルシステムを完全に回避できるため、ディスクが一時的なコンテナ化されたランナーでは特に重要です。
スクリーンショットを再現可能にするオプション
スクリーンショット同士を比較する場合は、これらは必須です。単に目視で確認するだけなら、無視して構いません。
| オプション | 値 | 機能 |
|---|---|---|
animations |
| disabled
, allow
| 「'disabled' に設定すると、キャプチャ中に CSS アニメーションを停止します」 |
| caret
| hide
, initial
| 「'hide' に設定すると、スクリーンショット撮影中にテキストカーソルを非表示にします」 |
| mask
| Locator[]
| 「スクリーンショットを撮影する際にマスクすべき位置指定子を指定します」 |
| maskColor
| CSSカラー、デフォルト値 #F0F
| 「マスクされる領域に使用する色を指定します」 |
| scale
| css
, device
| 「ウェブページのレンダリング倍率」 |
| omitBackground
| ブール値、デフォルト値 false
| 「デフォルトの白い背景を非表示にし、透明なスクリーンショットを撮影できるようにします」 |
| type
| png
, jpeg
, デフォルト png
| 「スクリーンショットのファイル形式を指定」 |
| quality
| 0–100 | 「JPEG形式の画像の品質」 — JPEGのみ |
| style
| CSS文字列 | キャプチャ実行中にページに挿入される |
再現性の問題をほとんど解決する4つの設定:
**animations: 'disabled'
** は、同じページの2つのキャプチャ間に生じる差異の最大の原因を取り除きます。実行中のCSSトランジションは、実行するたびに異なるピクセル結果を生み出します。
**mask
** は、領域を単色で置き換えます。これにより、比較を完全に放棄することなく、タイムスタンプ、セッションID、パーソナライズされたレコメンデーション、広告など、本質的に変動するコンテンツを除外できます:
await page.screenshot({
path: 'page.png',
mask: [page.getByTestId('timestamp'), page.locator('.ad-slot')],
maskColor: '#000000',
});
**scale: 'css'
** は、デバイスのピクセル比ではなくCSSピクセル単位でキャプチャを行うため、高DPIのマシンとCIランナーでも同等のサイズの画像が生成されます。 これは、お使いのノートパソコンとビルドサーバーの間で最も違いが生じやすいオプションであるため、デフォルトに依存せず、明示的に設定してください。
**caret: 'hide'
** は、入力フィールドにフォーカスがあるページのキャプチャの約半数に表示される、点滅するテキストカーソルを非表示にします。
再現性を確保するためにさらに3つの点を確定する必要がありますが、いずれもスクリーンショットのオプションではありません。設定でビューポートサイズを固定し、ロケールとタイムゾーンを固定し、フォントを固定してください。開発者のマシンとコンテナでは利用可能なフォントが異なり、フォントが異なるとレイアウトも異なります。
テスト失敗時の自動スクリーンショット
Playwrightにおいて最も有用なスクリーンショット設定であり、必要なのはたった1行だけです:
export default defineConfig({
use: {
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
});
screenshot: 'only-on-failure'
は、テストが失敗した瞬間のページをキャプチャし、レポートに添付します。オプションとして、off
、on
、only-on-failure
があります。on
はすべてのテストごとにキャプチャを行うため、大量のアーティファクトが生成されます。
trace
と組み合わせることは特に重視すべき点です。なぜなら、トレースにはDOMのスナップショット、ネットワークアクティビティ、およびすべてのアクションとタイミングが含まれるからです。スクリーンショットはページが正しく表示されていなかったことを示しますが、トレースはその理由を教えてくれます。無人実行されるテストについては、両方を有効にしておくべきです。
この設定は、ページはレンダリングされたものの、期待したページとは異なるという、判断が難しい種類の障害を診断する最も迅速な方法でもあります。チャレンジページ、ログイン後のリダイレクト、地域別のバリエーションなどは、スクリーンショットを確認するまでは要素の問題のように見えるタイムアウトを引き起こします。
「toHaveScreenshot
」との視覚的比較 アドホックなキャプチャではなく、実際の視覚的回帰テストを行う場合:
await expect(page).toHaveScreenshot('homepage.png');
await expect(page.getByRole('navigation')).toHaveScreenshot('nav.png');
初回実行時にはベースラインが作成され、それ以降の実行では比較が行われ、差異がある場合に失敗となります。--update-snapshots
を使用して、意図的にベースラインを更新してください。
3つの実用上の注意点。
ベースラインはプラットフォーム固有です。 オペレーティングシステムによってフォントのレンダリングが異なるため、macOSで生成されたベースラインは、Linuxコンテナで生成されたものとは一致しません。テストを実行する環境と同じ環境でベースラインを生成してください。通常はCI環境であり、ローカルでも実行可能なコンテナを介して生成するのが一般的です。
許容誤差を設定してください。 ピクセル単位での厳密な一致を求めると、人間には気づかないようなアンチエイリアシングの違いでも失敗として判定されてしまいます。設定に maxDiffPixels` ` または maxDiffPixelRatio を指定することで、テストスイートを実用的なものにできます。
実行前に、変動する要素をすべてマスクしてください。 タイムスタンプが変更されるたびに失敗する視覚テストは、1週間も経たないうちに無効化されてしまいます。それは、テストがないよりも悪い結果となります。
プロキシ経由でのスクリーンショット
これはまさに私たちの専門分野であり、非常に有用なケースです。
Playwrightの設定でプロキシを構成してください:
const context = await browser.newContext({
proxy: { server: 'http://proxy.example.com:9000', username: 'u', password: 'p' },
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
});
プロキシ設定の横にある「locale
」と「timezoneId
」に注意してください。en-US
のロケールとロンドンのタイムゾーンを持つドイツのエグジットアドレスという組み合わせは、実際の訪問者が生成するものではありません。多くのサイトは、アドレスとは独立してロケールに基づいて表示内容を決定しています。これら3つをすべて一緒に設定しないと、意図した内容とは異なるものをテストすることになります。
これが真に明らかにすること: その国の実際の訪問者が何を見ているか。地域別の価格設定、通貨の表示、在庫状況、プロモーションバナー、広告が支払った通りに配信されているかどうか、そしてコンテンツの横に何が表示されているか。答えはレンダリングされたページそのものであるため、これを提供できるAPIは存在しません。
コストと削減方法 ブラウザはあらゆるデータを取得します。データ通信量制限のある家庭用回線(当社の場合は2026年9月時点で料金ページを確認したところ、0.79ドル/GBから)では、20の市場でスクリーンショットを取得するだけで、コストはすぐに膨れ上がります。 不要なリソースの種類をブロックすることが、最も効果的な対策です:
await page.route('**/*.{woff,woff2,mp4,webm}', route => route.abort());
このリストに含まれていないものに注目してください。スクリーンショットが成果物である場合、画像をブロックすることはできません。そうすれば本末転倒になってしまいます。 フォントとメディアをブロックし、画像は残しておき、視覚的な検証は本質的にコストのかかるプロキシ作業であることを受け入れてください。外観ではなくテキストコンテンツのみを確認すればよい場合は、画像もブロックし、スクリーンショットの取得を完全に省略してください。
そして、実際にその地理的位置に到達したかどうかを検証してください。 スクリーンショットを撮って確認してください。 ブラジルの出口経由でキャプチャされたページに、自社のデスクと同じ価格が表示されている場合、IP検索の結果がどうであれ、ターゲティングは機能していません。これはまさに、プロキシのテストが重要な理由で説明した「サイレント・フェイル」そのものです。スクリーンショットは、人間が一目で見抜くことができるため、この問題を検出するのに非常に有効です。
大規模なスクリーンショットの撮影
スクリーンショットを数枚以上撮影する場合、いくつかのコツを実践することで、作業が手に負えなくなるのを防げます。
コンテキストではなく、ブラウザを再利用しましょう。 ブラウザを起動するにはリソースを消費しますが、コンテキストの作成は軽い作業です。 多くのページや領域を横断して処理を行う場合は、ブラウザを一度起動し、作業単位ごとに新しいコンテキストを作成しましょう。そうすることで、起動コストを繰り返し支払うことなく、独立したクッキーやストレージを確保できます:
const browser = await chromium.launch();
for (const country of countries) {
const ctx = await browser.newContext({ proxy: { server: proxyFor(country) } });
const page = await ctx.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: `shots/${country}.png`, fullPage: true });
await ctx.close();
}
await browser.close();
待機条件として networkidle を使用しないでください。 アナリティクスビーコン、WebSocket、またはポーリングを含むページは決してアイドル状態にならず、待機時間がタイムアウトしてしまいます。 ページの準備が整ったことを知らせる要素を待機してください:
await page.goto(url);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await page.screenshot({ path: 'shot.png' });
並行処理数を意図的に制限してください。 各ブラウザコンテキストは実際のメモリを消費します。ページが読み込まれると、数百メガバイトのメモリ使用量は普通です。小型のランナーで30個を並行実行すると、タイムアウトのように見えるエラーが発生しますが、実際にはマシンのメモリが不足していることが原因です。 最初は4~5から始め、メモリ状況を監視しながら増やしてください。
ファイル名は見つけやすいように付けましょう。 「screenshot-1.png」から「screenshot-400.png」までのディレクトリは実用になりません。ファイル名には対象、地域、タイムスタンプを含め、画像と一緒にURLも保存してください。
アーカイブする前に圧縮してください。 PNGはロスレスですがファイルサイズが大きくなります。画像がピクセル比較ではなく人間の目による確認用である場合、quality: 80 の設定でJPEGに圧縮すれば、通常はファイルサイズが数分の1になり、視覚的にも区別がつきません。20の市場をスケジュール通りに処理する場合、その差がストレージ費用に直結します。
実行を停止せずにエラーに対処する。 1つのページが読み込めないからといって、残りの19ページを放棄すべきではありません。各キャプチャを個別に処理し、エラーを記録した上で続行します。そうすることで、ターゲット3でジョブ全体が失敗したことに後から気づくのではなく、どのターゲットで失敗したかを報告できるようになります。
スクリーンショットが不適切なツールとなる場合
データが必要なとき。 価格が必要な場合は、価格を抽出してください。数字のスクリーンショットは、その後画像から読み取らなければならない数字に過ぎません。スクリーンショットは外観を捉えるためのものであり、セレクタはコンテンツを扱うためのものです。
テストが失敗した理由を知りたいとき。 トレースの方がはるかに情報量が多く、そもそもスクリーンショットも含まれています。
ページが膨大すぎる場合。 無限スクロールページの全ページキャプチャは、誰も開こうとしないような巨大なファイルになります。重要な領域だけを切り取ってください。
テキストを確認する場合。 テキストに対してアサーションを行ってください。expect(locator).toHaveText()を使用すれば読みやすい失敗メッセージが得られますが、ピクセル差分では単なる画像が得られるだけです。
大規模なアーカイブが必要な場合。 スクリーンショットは容量が大きく、多くの市場で数千枚にも及ぶと、ストレージと帯域幅の両方で負荷がかかります。ハッシュ値や差分を保存し、変更があった場合のみ完全な画像を保持するようにしてください。
よくある質問
Playwrightでスクリーンショットを撮るにはどうすればよいですか? ビューポートの場合は
await page.screenshot({ path: 'shot.png' })、スクロール可能なページ全体の場合は { fullPage: true }、単一の要素の場合は locator.screenshot() を使用します。ファイルに書き込む代わりにバッファを取得するには、path を省略してください。
ページ全体のスクリーンショットを撮るにはどうすればよいですか?
fullPage: true を渡してください。遅延読み込みされたコンテンツがまだ読み込まれていない場合や、スティッキー要素や固定要素が合成された結果で予期せぬ動作をする可能性があることに注意してください。まず意図的にスクロールし、style オプションを使用して、キャプチャ中のスティッキー配置の影響を無効にしてください。
単一の要素のスクリーンショットを撮るにはどうすればよいですか?
ページではなく、ロケーターに対して screenshot() を呼び出します:await page.getByTestId('card').screenshot({ path: 'card.png' })。Playwright は要素を画面内にスクロール表示し、そのバウンディングボックスをキャプチャします。overflow: hidden によって切り取られたコンテンツは含まれません。
実行ごとにPlaywrightのスクリーンショットを統一するにはどうすればよいですか?
animations: 'disabled'とcaret: 'hide'を設定し、maskで可変領域をマスクし、scaleを明示的に設定します。さらに、ビューポートのサイズ、ロケール、タイムゾーン、利用可能なフォントを固定してください。これら4つはすべてレイアウトに影響しますが、スクリーンショットのオプションには含まれていないためです。
テストが失敗したときに自動的にスクリーンショットをキャプチャするにはどうすればよいですか?
Playwrightの設定ファイルのuseブロックで、screenshot: 'only-on-failure'を設定してください。これをtrace: 'retain-on-failure'と組み合わせて使用します。トレースにはDOMスナップショット、ネットワークアクティビティ、アクションのタイミングが含まれるため、単に失敗を表示するだけでなく、その原因を説明することができます。
ファイルではなくbase64形式でスクリーンショットを取得することはできますか?
はい。pathオプションを省略すると、screenshot()はバッファを返します。これをbuffer.toString('base64')で変換できます。ドキュメントでは、後処理やピクセル差分サービスへの渡しのためにこの方法が推奨されており、一時的なCIランナーではファイルシステムを介さずに済みます。
スクリーンショットから動的コンテンツを非表示にするにはどうすればよいですか?
maskオプションをロケーターの配列と共に使用すると、それらの領域が単色に置き換えられます。maskColorのデフォルト値は#F0Fですが、変更可能です。これにより、タイムスタンプ、セッションデータ、広告が含まれるページでも、視覚的な比較を有効に保つことができます。
プロキシ経由でスクリーンショットを撮影し、地域別のページを表示することはできますか?
はい、可能です。これはプロキシを活用する上で特に有効な用途の一つです。ブラウザの設定でプロキシを設定し、locale および timezoneId を対象の国に合わせて設定してください。多くのサイトでは、IPアドレスとは独立してロケールを使用しています。その後、生成された画像を確認して、IPアドレスの照合結果を鵜呑みにするのではなく、地域別のコンテンツが実際に異なっていることを確認してください。
まとめ
Playwrightでスクリーンショットを撮るには、たった1行のコードで済みます。しかし、意味のあるスクリーンショットを撮るには、もう少し手間がかかります。
スクリーンショットが、人間が一度だけ確認するためのものである場合――エラーの証拠、バグ報告、ブラジルから見たページの表示確認など――は、デフォルトの設定で十分であり、設定ファイルに screenshot: 'only-on-failure' を追加するだけで最大の効果を得られます。ただし、トレースも併せて記録するようにしましょう。スクリーンショットだけではわからない部分を、トレースが説明してくれるからです。
スクリーンショットを別のスクリーンショットと比較する場合は、すべてが異なります。 アニメーションを無効にし、カーソルを非表示にし、可変領域をマスクし、拡大率を固定し、ビューポート、ロケール、タイムゾーン、フォントを固定します。その後、テストが実行されるのと同じ環境でベースラインを生成してください。フォントのレンダリングはプラットフォームによって異なるため、自分のノートパソコンで作成したベースラインはコンテナ環境とは決して一致しないからです。
また、地理的な検証については――これはレンダリングされたページが構造化データに真に勝る点ですが――プロキシ、ロケール、タイムゾーンをまとめて設定し、画像を確認してターゲティングが機能しているかを確認してください。帯域幅にはコストがかかりますが、画像はブロックできない唯一のリソースタイプであり、それがまさに視覚的検証にかかるコストなのです。
