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.
| Value | Returns when |
|---|---|
load | The load event fires. This is the default. |
domcontentloaded | The HTML is parsed, before images finish. |
networkidle | No 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
- Forgetting
await. The script exits before the file is written. - Reusing one page after a crash. Open a new page when
gotothrows a target-closed error. - Relying on
networkidleeverywhere. Some pages never reach it and every row times out. - Capturing cookie banners. Click the consent button with a locator, or hide it with
page.addStyleTag. - 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
- How to Capture Website Screenshots in Bulk on MacCapture website screenshots in bulk on Mac with Temposhot. Paste a URL list, choose desktop/tablet/mobile presets, run parallel capture, and export for audits or reports.
- How to Automate Website Screenshots with PuppeteerUse Puppeteer to automate website screenshots with JavaScript. Capture one page, full-page screenshots, URL batches, and mobile viewports with a reusable script.
- How to Automate Website Screenshots Without CodeChoose a no-code way to automate website screenshots: URL-list capture, an automation platform with a screenshot API, monitoring tools, or browser helpers.
- Best Xnapper Alternatives for Mac in 2026Compare Xnapper alternatives for Mac: repeated capture, scrolling capture and recording, free backgrounds, cross-platform guides, and the built-in app.