Skip to content

How to Automate Website Screenshots with Playwright

Use Playwright to automate website screenshots with JavaScript. Capture one page, full pages, mobile views, and URL batches with retries.

4 min readWebsite captureBy Temposhot Team

Playwright is a browser automation library from Microsoft. It drives Chromium, Firefox and WebKit from Node.js, Python, Java or .NET. This post uses the Node.js package to build a screenshot script, starting with one page and ending with a URL batch that retries failures.

When Playwright is the right tool

A script is worth the setup when screenshots are part of a larger technical process:

  • captures run in CI after every deploy
  • the URL list comes from a sitemap, a database or an API
  • the files must land in a fixed folder structure or a storage bucket
  • the screenshot step sits next to tests, scraping or PDF generation

If the person who needs the images does not write code, or the list changes every week, a no-code tool is usually faster. That trade-off is covered at the end.

Install Playwright for a screenshot script

mkdir site-shots && cd site-shots
npm init -y
npm install -D playwright
npx playwright install chromium

The last command downloads a Chromium build that matches the installed Playwright version. The scripts below use the .mjs extension so top-level await works without a bundler or extra config.

Capture one website screenshot

// one.mjs
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });

await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });

await browser.close();

Run it with node one.mjs. The browser starts headless, which means it renders the page without opening a window. The headless browser entry explains that model.

Without other options, the file is a viewport screenshot of exactly 1920×1080 pixels.

Capture a full-page screenshot

Add fullPage: true to capture the whole scrollable document.

await page.screenshot({ path: 'example-full.png', fullPage: true });

The width stays at the viewport width, and the height grows to fit the page. Very long pages produce very tall images, which some image viewers scale down heavily. Pages with fixed headers can show the header once at the top, since Playwright captures the document in one pass rather than stitching scrolled views.

To capture one element instead, call screenshot on a locator: page.locator('main').screenshot({ path: 'main.png' }).

Put the target URL in a batch script

Keep the URLs in a text file so nobody needs to edit code to change the list.

// batch.mjs
import { readFile, mkdir } from 'node:fs/promises';
import { chromium } from 'playwright';

const urls = (await readFile('urls.txt', 'utf8'))
  .split('\n').map((l) => l.trim()).filter(Boolean);

await mkdir('shots', { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });

for (const [i, url] of urls.entries()) {
  const name = `shots/${String(i + 1).padStart(3, '0')}.png`;
  await page.goto(url, { waitUntil: 'load', timeout: 30000 });
  await page.screenshot({ path: name, fullPage: true });
  console.log('saved', url, name);
}

await browser.close();

Zero-padded names such as 001.png keep files in list order in Finder.

Add concurrency for bigger batches

One page at a time is slow for hundreds of URLs. Run a few pages in parallel, each in its own page object, and cap the number so memory stays under control.

const LIMIT = 4;
const queue = [...urls.entries()];

async function worker() {
  const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
  while (queue.length) {
    const [i, url] = queue.shift();
    await page.goto(url);
    await page.screenshot({ path: `shots/${i + 1}.png`, fullPage: true });
  }
  await page.close();
}

await Promise.all(Array.from({ length: LIMIT }, worker));

Four workers is a sensible start on a laptop. Raise the limit only after checking memory use, and keep it low for sites you do not own so the request rate stays polite.

Capture mobile or tablet screenshots

Playwright ships device descriptors that set viewport, user agent, touch support and pixel ratio together.

import { chromium, devices } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 13'] });
const page = await context.newPage();

Use one context per device so cookies and storage stay separate. For a tablet, pick an iPad entry from the same devices list, or set a plain viewport such as 768×1024.

Handle lazy-loaded content

Many pages load images only when they scroll into view. A full-page capture taken right after load can show empty boxes further down. Scroll once through the page before the screenshot.

await page.evaluate(async () => {
  for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
    window.scrollTo(0, y);
    await new Promise((r) => setTimeout(r, 200));
  }
  window.scrollTo(0, 0);
});

The lazy loading entry explains why this happens.

Choose the right wait strategy

The waitUntil option decides when goto returns.

ValueReturns when
loadThe load event fires. This is the default.
domcontentloadedThe HTML is parsed, before images finish.
networkidleNo network requests for at least 500 ms.

Playwright's documentation discourages networkidle for tests, because pages with polling or analytics may never go quiet. A more reliable pattern is to wait for an element that marks the page as ready, for example await page.locator('main img').first().waitFor().

Add retries

Networks fail and some pages time out once, then load fine. Wrap the capture in a small retry helper.

async function capture(page, url, path, tries = 3) {
  for (let attempt = 1; attempt <= tries; attempt++) {
    try {
      await page.goto(url, { timeout: 30000 });
      await page.screenshot({ path, fullPage: true });
      return true;
    } catch (error) {
      console.warn(`attempt ${attempt} failed for ${url}: ${error.message}`);
    }
  }
  return false;
}

Collect the URLs that return false and write them to failed.txt so they can be rerun on their own.

A practical folder structure

site-shots/
  urls.txt
  batch.mjs
  shots/
    2026-03-24/
      desktop/001.png
      mobile/001.png
  failed.txt

A dated folder per run keeps older captures intact. A device subfolder makes side-by-side review simpler.

Common mistakes

  1. Forgetting await. The script exits before the file is written.
  2. Reusing one page after a crash. Open a new page when goto throws a target-closed error.
  3. Relying on networkidle everywhere. Some pages never reach it and every row times out.
  4. Capturing cookie banners. Click the consent button with a locator, or hide it with page.addStyleTag.
  5. Ignoring site terms. Check terms and robots rules for sites you do not own, and keep the rate low.

Alternatives to a Playwright screenshot script

  • Puppeteer offers a similar API for Chrome and Firefox. See How to automate website screenshots with Puppeteer.
  • Playwright Test compares screenshots against baselines. See the Playwright screenshot testing guide.
  • Hosted screenshot APIs run the browser for you and return images over HTTP.
  • Temposhot Website Capture runs a URL list locally on a Mac without code: Desktop 1920×1080, Tablet 768×1024 or Mobile 390×844, Viewport or Full page, up to 10 browsers at once, and per-row Retry.

FAQ

Does Playwright need a visible browser window?

No. Browsers launch headless by default. Pass headless: false to chromium.launch when you want to watch the page while debugging.

Can Playwright save JPEG instead of PNG?

Yes. Set type: 'jpeg' and a quality value from 0 to 100 in the screenshot options, or use a .jpg file name.

Why is my full-page screenshot cut off?

Some pages scroll inside an inner element instead of the document. Capture that element with a locator screenshot, or change its CSS height before capturing.

References

Need no-code website screenshot automation?

Temposhot Website Capture takes a pasted URL list and captures every page on your Mac with no script to maintain. See website screenshot automation or start with the Website Capture quick start.

playwrightwebsite-captureautomationjavascript

Terms used here: full page screenshot, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot

Turn repeatable screenshots into finished work

Temposhot captures on a timer or on every click and exports the result as a PDF, images, ZIP or MP4.

Related posts