チュートリアル
Shotomatic Team
読了まで約21分

Playwright で Web サイトのスクリーンショットを自動化する方法

Playwright と JavaScript で Web サイトのスクリーンショットを自動化します。単一ページ、ページ全体、モバイル表示、URL の一括撮影まで、再試行を含めて解説します。

Web サイトのスクリーンショット自動化スクリプトを表示したノートパソコン

この記事では、Playwright のスクリーンショットスクリプトを段階的に組み立てます。まず 1 画面分を撮影し、次にページ全体、再利用できる URL 処理、モバイルのビューポートへと進みます。

スクリーンショットを、それを生成するコードと一緒に管理したい場合、Playwright は使いやすい選択肢です。実際のブラウザによるレンダリング、分離されたブラウザコンテキスト、明示的なビューポート指定、デバイスのエミュレーションに加え、必要なページ状態まで待つための仕組みも利用できます。

例では Shotomatic の機能ページ https://www.shotomatic.com/features を使います。同じページについて、ビューポート、ページ全体、一括処理向け、モバイルの各撮影結果を比べられます。

要点: スクリーンショットの自動化をコードで管理するなら Playwright を使います。ブラウザコンテキストを作り、ビューポートまたはデバイスを設定し、必要なページ状態まで待って page.screenshot() を呼び出します。その後、ページ全体の撮影、一括処理、再試行、レポートを追加します。スクリプトの作成と保守を担当したくない場合は、Shotomatic の Website Captureのほうが簡単です。

情報開示:Shotomatic は私たちが開発しています。そのため、例では公開中の Shotomatic 機能ページを撮影対象にしています。Playwright では、撮影を許可されているほかの公開 Web サイトも撮影できます。URL リストをノーコードで撮影したい場合は、Shotomatic の Website Capture を利用できます。

Playwright が適している場面

ブラウザ上の処理に沿って撮影する必要がある場合は、Playwright が向いています。

たとえば、次の用途です。

  • CI の中でスクリーンショットを撮る
  • ログインしてから撮影する
  • 特定のセレクターやアプリの状態になるまで待つ
  • 同じ撮影ジョブを Chromium、Firefox、WebKit で実行する
  • 明示的なビューポート設定からレスポンシブ表示を撮影する
  • 社内ツールの中でスクリーンショットを生成する

一方、スクリーンショットを必要とする人と、スクリプトを保守する人が異なる場合は扱いにくくなります。作業をコードとして管理するなら、Playwright はよい出発点です。

撮影スクリプト用に Playwright をインストールする

通常の Node.js スクリプトでは、Playwright ライブラリと自動操作するブラウザをインストールします。

$ mkdir website-screenshots-playwright
$ cd website-screenshots-playwright
$ npm init -y
$ npm i -D playwright
$ npx playwright install chromium

Playwright のライブラリに関するドキュメントでも、パッケージとブラウザをインストールし、Playwright を読み込み、ブラウザを起動してページを操作するという同じ基本構成を使っています。後で Firefox や WebKit が必要になったら、それらのブラウザもインストールしてください。

スクリプトと出力用のフォルダを作ります。

$ mkdir scripts screenshots

Web サイトを 1 ページ撮影する

scripts/capture-one.js を作成します。

const { chromium } = require("playwright");
const fs = require("node:fs/promises");

const url = "https://www.shotomatic.com/features";
const outputPath = "screenshots/shotomatic-features.png";

(async () => {
  await fs.mkdir("screenshots", { recursive: true });

  const browser = await chromium.launch();
  const context = await browser.newContext({
    viewport: { width: 1440, height: 1000 },
    deviceScaleFactor: 1,
  });

  const page = await context.newPage();

  try {
    await page.goto(url, { waitUntil: "load", timeout: 45_000 });
    await page.screenshot({ path: outputPath });
    console.log(`Saved ${outputPath}`);
  } finally {
    await context.close();
    await browser.close();
  }
})();

実行します。

$ node scripts/capture-one.js

これで、表示中のビューポートが撮影されます。Playwright のスクリーンショット APIは、ページ全体、要素、バッファ、切り抜き、画質の指定にも対応しています。

このスクリプトを実行すると、screenshots/shotomatic-features.png が作成されます。

Playwright の capture-one の例で撮影した Shotomatic の機能ページ
上のスクリプトで 1440 × 1000 のビューポートを指定して撮影した機能ページ。

ページ全体のスクリーンショットを撮る

ページ全体のスクリーンショットを撮るには、fullPage: true を渡します。

await page.screenshot({
  path: "screenshots/shotomatic-features-full-page.png",
  fullPage: true,
});

上から下まで文書全体が必要ならページ全体を撮影します。特定の画面サイズで訪問者に何が見えるかを確認したい場合は、ビューポートのスクリーンショットを使います。

Playwright で撮影した Shotomatic 機能ページ全体のスクリーンショット
画面外のセクションが表示された後に、同じ `/features` ページ全体を撮影した結果。

ページ全体の撮影でも、結果はページの状態に左右されます。fullPage: true は撮影範囲を変えるだけで、遅延読み込みの画像、アニメーション、画面内に入ったときに表示されるセクションを強制的にレンダリングするわけではありません。後ほど、ページ状態が重要になる理由を遅延読み込みの項目で説明します。

対象 URL を一括処理スクリプトに移す

1 ページ用のスクリプトが動いたら、URL のリストを受け取り、分かりやすいファイル名で保存できるスクリプトへ移します。例では引き続き /features だけを使うため、出力を追いやすくなっています。後から同じ配列に URL を追加できます。

scripts/capture-batch.js を作成します。

const { chromium } = require("playwright");
const fs = require("node:fs/promises");

const urls = ["https://www.shotomatic.com/features"];

const outputDir = "screenshots";

function filenameForUrl(url, index) {
  const parsed = new URL(url);
  const rawName = `${parsed.hostname}${parsed.pathname}`;

  const slug = rawName
    .replace(/^www\./, "")
    .replace(/\/$/, "")
    .replace(/[^a-z0-9]+/gi, "-")
    .replace(/^-+|-+$/g, "")
    .toLowerCase();

  return `${String(index + 1).padStart(2, "0")}-${slug || "home"}.png`;
}

async function captureUrl(browser, url, index) {
  const context = await browser.newContext({
    viewport: { width: 1440, height: 1000 },
    deviceScaleFactor: 1,
  });

  const page = await context.newPage();
  const outputPath = `${outputDir}/${filenameForUrl(url, index)}`;

  try {
    await page.goto(url, { waitUntil: "load", timeout: 45_000 });
    await page.locator("body").waitFor({ state: "visible", timeout: 15_000 });
    await page.screenshot({ path: outputPath, fullPage: true });

    console.log(`Captured ${url} -> ${outputPath}`);
    return { url, ok: true, outputPath };
  } catch (error) {
    console.error(`Failed ${url}: ${error.message}`);
    return { url, ok: false, error: error.message };
  } finally {
    await context.close();
  }
}

(async () => {
  await fs.mkdir(outputDir, { recursive: true });

  const browser = await chromium.launch();

  try {
    const results = [];

    for (const [index, url] of urls.entries()) {
      results.push(await captureUrl(browser, url, index));
    }

    const failed = results.filter((result) => !result.ok);

    if (failed.length > 0) {
      process.exitCode = 1;
      console.error(`${failed.length} screenshot(s) failed.`);
    }
  } finally {
    await browser.close();
  }
})();

この版は、意図的に 1 件ずつ実行します。速度は遅いものの、デバッグははるかに簡単です。対象ページで安定して動くことを確認してから、同時実行を追加します。

件数が多いときは同時実行を加える

URL が 5 件なら単純なループで十分です。50 件、500 件と増える場合は、少数ずつ同時に処理するよう制限したほうがよいでしょう。

何百ものページを一度に開かないでください。ブラウザの自動操作は多くのメモリを使います。また、短時間に大量のアクセスを送ると、対象サイトのレート制限にかかったり、正しく動かなくなったりします。まず 2〜4 件の同時撮影から始め、安定した結果を確認してから増やします。

このガイドの Puppeteer 版でも使っている、次の補助関数を追加します。

async function runWithConcurrency(items, limit, worker) {
  const results = new Array(items.length);
  let nextIndex = 0;

  async function runNext() {
    while (nextIndex < items.length) {
      const currentIndex = nextIndex;
      nextIndex += 1;
      results[currentIndex] = await worker(items[currentIndex], currentIndex);
    }
  }

  const workers = Array.from(
    { length: Math.min(limit, items.length) },
    () => runNext(),
  );

  await Promise.all(workers);
  return results;
}

続いて、1 件ずつ実行するループを置き換えます。

const results = await runWithConcurrency(urls, 3, (url, index) =>
  captureUrl(browser, url, index),
);

この構成では 1 つのブラウザを開いたまま、撮影ごとに分離したブラウザコンテキストを作り、同時に動くページ数を制限します。Playwright のライブラリに関するドキュメントでも、Node スクリプトの流れでブラウザコンテキストを明示的に扱うため、撮影ごとにライフサイクルを分けられます。

モバイルやタブレットの画面を撮影する

Playwright には、一般的なスマートフォンやタブレット向けのデバイスエミュレーション設定が用意されています。たとえば、次のように使います。

const { chromium, devices } = require("playwright");

(async () => {
  const browser = await chromium.launch();
  const iPhone = devices["iPhone 13"];

  const context = await browser.newContext({
    ...iPhone,
  });

  const page = await context.newPage();
  await page.goto("https://www.shotomatic.com/features", {
    waitUntil: "load",
  });
  await page.screenshot({
    path: "screenshots/shotomatic-features-iphone-13.png",
  });

  await context.close();
  await browser.close();
})();

iPhone 13 の設定で同じ機能ページを撮影すると、次のようになります。

Playwright で Shotomatic 機能ページをモバイルのビューポートで撮影した結果
Playwright の iPhone 13 デバイス設定を使ったモバイル表示の撮影結果。

ユーザーエージェント、タッチ操作、ビューポート、デバイスピクセル比が重要なら、デバイス設定を使います。特定の寸法だけが必要なら、通常のビューポートを指定します。

const context = await browser.newContext({
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 2,
  isMobile: true,
});

遅延読み込みのコンテンツに対応する

多くのページでは、画像が画面内に入ってから読み込まれたり、セクションが表示されたりします。ページ全体を早く撮りすぎると、画面外のコンテンツがまだ表示されず、空白のまま残ることがあります。

遅延読み込みのセクションが表示される前に撮影し、大きな空白が残った Playwright のページ全体スクリーンショット
すぐに撮影した結果。ページの高さは正しいものの、一部のコンテンツはまだ表示されていません。
遅延読み込みのセクションまでスクロールした後に撮影した Playwright のページ全体スクリーンショット
スクロール後に撮影した結果。画面外にあったセクションも表示されています。

ページ全体を撮影する前に文書を下までスクロールし、遅延読み込みの画像や、画面内に入ったときに表示されるセクションを動かします。

async function triggerLazyLoading(page) {
  await page.evaluate(async () => {
    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    const viewportHeight = window.innerHeight;
    const scrollHeight = document.body.scrollHeight;

    for (let y = 0; y < scrollHeight; y += viewportHeight) {
      window.scrollTo(0, y);
      await delay(600);
    }

    window.scrollTo(0, 0);
    await delay(300);
  });
}

page.screenshot() の前に実行します。

await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await triggerLazyLoading(page);
await page
  .waitForFunction(
    () =>
      Array.from(document.images).every(
        (image) => image.complete && image.naturalWidth > 0,
      ),
    null,
    { timeout: 15_000 },
  )
  .catch(() => {});
await page.screenshot({ path: outputPath, fullPage: true });

これだけでは足りないページもあります。独自のスクロール領域、アニメーション、遅れて実行される API 呼び出し、仮想化されたコンテンツでは、ページ固有のセレクターや状態を待つ必要があります。

適切な待機方法を選ぶ

スクリーンショットの安定性に関する問題は、多くがタイミングに行き着きます。

Playwright のナビゲーションに関するドキュメントによると、page.goto() は既定で load イベントを待ちます。ただし、現代の Web ページでは、その後もデータ取得やレンダリングが続くことがあります。撮影時は、通常、次のいずれかを使います。

  • 一般的な公開ページでは、まず waitUntil: "load" を使う
  • 必要なコンテンツが表示されたことを示す特定のロケーターを待つ
  • アプリの状態によって準備完了を判断する場合は page.waitForFunction() を使う
  • 画像が遅延読み込みされる場合は撮影前にスクロールする
  • 固定時間の待機を主な待機条件にしない

例は次のとおりです。

await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await page
  .getByRole("heading", { name: "Shotomatic Features" })
  .waitFor({ state: "visible", timeout: 15_000 });
await page.screenshot({ path: outputPath, fullPage: true });

すべてのページに同じ待機条件が通用するとは考えないでください。対象サイトを自分で管理しているなら、一般的なネットワーク状態を待つより、ロケーターやアプリ状態を待つほうが安定します。

再試行を追加する

ネットワーク要求は失敗します。ページはタイムアウトし、外部のウィジェットが応答しないこともあります。実際の一括処理スクリプトでは、実行全体を失敗させる前に、失敗した URL を再試行できるようにします。

async function withRetries(task, retries = 2) {
  let lastError;

  for (let attempt = 0; attempt <= retries; attempt += 1) {
    try {
      return await task();
    } catch (error) {
      lastError = error;
      console.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
    }
  }

  throw lastError;
}

ページ操作をこの関数で囲みます。

await withRetries(async () => {
  await page.goto(url, { waitUntil: "load", timeout: 45_000 });
  await page.screenshot({ path: outputPath, fullPage: true });
});

本番運用では、URL、出力先、状態、エラーメッセージ、時刻を含む JSON レポートも保存します。後から「どのページが、なぜ失敗したか」を確認するときに役立ちます。

実用的なフォルダ構成

繰り返し実行する撮影ジョブでは、入力、出力、スクリプトを分けておきます。

website-screenshots-playwright/
  scripts/
    capture-batch.js
  urls/
    launch-pages.txt
    competitor-pages.txt
  screenshots/
    2026-06-23-launch/
    2026-06-23-competitors/
  reports/
    2026-06-23-launch.json

この構成なら再実行しやすく、スクリーンショット一式と、その元になった URL リストの関係も保てます。

よくある間違い

  • 撮影が早すぎる:load に到達しても、必要なコンテンツがまだ表示されていないことがあります。セレクターを待つか、ページ固有の確認を追加します。
  • 一度に開くページが多すぎる:同時実行数が多いと、ブラウザのクラッシュ、メモリ不足、レート制限の原因になります。少数から始めます。
  • すべてのサイトに同じファイル名の規則を使う:URL にはクエリ文字列、末尾のスラッシュ、重複、特殊文字が含まれます。必ずファイル名を安全な形に変換し、連番を含めます。
  • ブラウザを閉じ忘れる:ページ、コンテキスト、ブラウザを閉じます。長時間動くジョブでは、閉じられていないコンテキストが深刻な問題になります。
  • ページ全体のスクリーンショットをビジュアルテストとして扱う:スクリーンショットファイルは記録です。ビジュアルリグレッションテストには、基準画像、比較のしきい値、確認工程も必要です。

Playwright スクリプト以外の選択肢

Web サイトのスクリーンショットを自動化する方法は Playwright だけではありません。最初のスクリプトを作った後、誰がその作業を担当するかで選択が変わります。

目的を絞ったブラウザ自動化スクリプトだけが必要なら、Puppeteerで十分な場合があります。スクリーンショットをより大きなテストスイートの近くに置く場合、Chromium、Firefox、WebKit での確認が必要な場合、すでに Playwright Test のランナー、トレース、レポートを使っている場合は、Playwright のほうが適しています。

スクリーンショット撮影を製品に組み込むなら、自分でブラウザを保守するよりスクリーンショット APIを使うほうが手間を減らせる場合があります。

スクリプトの作成や保守に時間をかけたくない場合は、コードを書かずに Web サイトのスクリーンショットを自動化する方法をご覧ください。URL リストからの撮影、ページごとの設定、結果の確認、書き出しを Playwright コードなしで行うなら、Shotomatic の Website Captureを使えます。

よくある質問

Playwright で Web サイトのスクリーンショットを自動化できますか?

はい。Playwright ではブラウザを起動し、URL を開き、ビューポートやデバイスの設定を指定して、page.screenshot() でページのスクリーンショットを保存できます。

Playwright でページ全体のスクリーンショットを撮れますか?

はい。page.screenshot()fullPage: true を渡すと、表示中のビューポートだけでなく、スクロール可能なページ全体を撮影できます。

Playwright で複数の URL を撮影できますか?

はい。URL を配列に入れて順に処理し、撮影ごとに新しいブラウザコンテキストを作成します。件数が多い場合は、同時実行数の制限とエラー処理も追加してください。

Web サイトの撮影には Playwright と Puppeteer のどちらを使うべきですか?

複数ブラウザへの対応、デバイスのエミュレーション、スクリーンショットを含む大きなテスト工程が必要なら Playwright が向いています。Chrome 中心の撮影スクリプトには Puppeteer も実用的です。

Playwright よりノーコードの撮影手順が適しているのはどんな場合ですか?

スクリプト、ブラウザ依存関係、CI ジョブを管理せずに、開発者以外の人が URL リストの撮影、確認、書き出しを行う必要がある場合は、ノーコードの手順が適しています。

参考資料

上の例は、現在の公式ドキュメントと照合しています。

コードを書かずにWebサイトのスクリーンショットを自動化

Website Captureなら、URLリストをページごとの設定で撮影し、結果を確認して書き出せます。スクリプトの管理は不要です。

Playwright で Web サイトのスクリーンショットを自動化する方法 | ブログ | Shotomatic