How to Take Recurring Screenshots of a Website Using Playwright and Node.js
Build a recurring website screenshot job with Playwright and Node.js, choose an in-process timer or external scheduler, and handle failures, output, and visual consistency.
To take recurring screenshots with Playwright and Node.js, put the browser capture in an async function, then call it on a schedule. Use a Node.js timer for a simple worker that stays running; use an external scheduler or CI runner when jobs need to run independently of that process. A timer is approximate and stops when its process stops.
The example below is a standalone Node.js script. It creates an output directory, captures the full page, uses a timestamp so captures do not overwrite one another, and closes the browser even if navigation or capture fails.
const { chromium } = require('playwright');
const fs = require('node:fs/promises');
const path = require('node:path');
const targetUrl = 'https://example.com';
const outputDir = path.join(__dirname, 'screenshots');
async function capture() {
await fs.mkdir(outputDir, { recursive: true });
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(targetUrl, { waitUntil: 'load', timeout: 30_000 });
const filename = `capture-${new Date().toISOString().replaceAll(':', '-')}.png`;
const outputPath = path.join(outputDir, filename);
await page.screenshot({ path: outputPath, fullPage: true });
console.log(`Saved ${outputPath}`);
} finally {
await browser.close();
}
}
capture().catch((error) => {
console.error('Screenshot capture failed:', error);
process.exitCode = 1;
});
Save it as capture.js, install Playwright, and install its Chromium browser:
npm init -y
npm install playwright
npx playwright install chromium
node capture.js
Playwright’s Page API provides navigation and screenshot methods; see also its screenshots guide. The waitUntil: 'load' setting waits for the load event. A modern page may continue loading application data or images afterward, so replace or supplement that milestone with a meaningful readiness condition for the site you capture.
1. Choose how the job repeats
Use a timer in a long-running Node.js process
This example awaits each capture before starting the next one. It catches a failure per run, so one failed page does not stop future attempts. The interval is measured between timer callbacks, not as a guarantee that each screenshot begins at an exact clock time.
const intervalMs = 60 * 60 * 1000; // approximately once per hour
async function runOnInterval() {
try {
await capture();
} catch (error) {
console.error('Scheduled capture failed:', error);
}
}
// Capture once at startup, then schedule later runs.
void runOnInterval();
const timer = setInterval(() => {
void runOnInterval();
}, intervalMs);
// If your application needs to stop the schedule, call:
// clearInterval(timer);
Node.js describes setInterval(callback, delay) as scheduling repeated callback execution, but callback timing depends on event-loop work. Because the callback here awaits its work internally while the interval itself does not, a capture that takes longer than the interval could overlap with a later run. If that is possible, use a guarded scheduler:
let running = false;
async function runWithoutOverlap() {
if (running) {
console.warn('Skipping scheduled run because the previous capture is still running');
return;
}
running = true;
try {
await capture();
} catch (error) {
console.error('Scheduled capture failed:', error);
} finally {
running = false;
}
}
void runWithoutOverlap();
const timer = setInterval(() => {
void runWithoutOverlap();
}, 60 * 60 * 1000);
The process must remain alive for its timer to fire. A restart, crash, deployment, or machine shutdown interrupts the schedule. For an approximate cadence in a continuously running worker, a process timer is often sufficient. For calendar times, durable independent runs, or execution when the application process is down, use an external scheduler or CI scheduler appropriate to your environment. Configure its retries and output storage deliberately; the right settings depend on that system.
Node.js documents timer behavior in its Timers API. Playwright Test retries are a separate feature: they repeat failed tests during a test run and do not schedule new runs at a cadence. Retries are off by default unless configured (Playwright retries).
2. Pick the screenshot output
| Goal | Playwright option | Example |
|---|---|---|
| Visible viewport | Omit fullPage or set it to false |
page.screenshot({ path: 'viewport.png' }) |
| Entire scrollable page | fullPage: true |
page.screenshot({ path: 'full.png', fullPage: true }) |
| One element | Use a locator screenshot | page.locator('main article').screenshot({ path: 'article.png' }) |
| Process the image in memory | Omit path and use the returned buffer |
const image = await page.screenshot() |
For a selected element, navigate and wait for it before capturing:
await page.goto(targetUrl, { waitUntil: 'load', timeout: 30_000 });
const article = page.locator('main article');
await article.waitFor({ state: 'visible', timeout: 10_000 });
await article.screenshot({ path: path.join(outputDir, 'article.png') });
For buffer output, return it to an upload or image-processing step rather than writing a file:
const imageBuffer = await page.screenshot({ fullPage: true, type: 'png' });
// Pass imageBuffer to your storage client or image-processing function.
A full-page capture can be much taller and larger than a viewport image. If the page uses lazy-loaded images or content that appears only while scrolling, determine whether the capture needs those sections loaded and wait for the site’s content to become ready. A locator screenshot is useful when only one region matters and avoids archiving irrelevant page content.
3. Make repeated captures useful
- Choose a readiness condition. Prefer a locator or application state that means the content you need is ready. A fixed delay can help with a known animation or late update, but it adds time without proving that the page is ready.
- Keep the capture environment consistent. Set a known viewport and keep the browser version, operating system, settings, and headless mode stable when comparing images. Playwright notes that rendering can vary with host OS, browser version, settings, hardware, power source, and headless mode (visual comparisons).
- Name and retain files deliberately. Timestamped names preserve history; a stable name overwrites the last capture. Set a retention policy or move older files to storage so recurring output does not consume unlimited disk space.
- Record useful run context. Log the capture time, target, output path, and error. Avoid writing secrets such as authorization headers or cookies into logs.
- Prevent competing jobs. A single-process guard prevents overlap in that process. If an external scheduler can start another instance before the previous one ends, use that scheduler’s concurrency controls or a shared lock.
- Respect the target. Check that recurring access is permitted and choose a frequency suitable for the site and your use case.
For an image archive, save screenshots and manage their retention. For visual regression, use Playwright Test’s expect(page).toHaveScreenshot() and maintain reviewed baseline images; that assertion works with the Playwright test runner (PageAssertions API). A scheduled new capture is not itself a baseline comparison.
4. Decide between a process timer and an external schedule
| Consideration | In-process timer | External scheduler or CI runner |
|---|---|---|
| Must a process stay up? | Yes. The timer belongs to the running process. | Usually the scheduler starts a job for each run; behavior depends on the chosen system. |
| Timing | Approximate intervals; event-loop work can delay callbacks. | Better fit for calendar-based scheduling, subject to the scheduler’s documented behavior. |
| Failure handling | Implement logging, per-run error handling, and any retry policy in the worker. | Use the scheduler’s or runner’s failure reporting and retry settings where available. |
| Output persistence | Files stay wherever the worker writes them, subject to that machine’s storage lifecycle. | Job files may need explicit upload to persistent storage after each run. |
| Overlap control | Guard the callback or choose a cadence longer than the expected capture time. | Configure concurrency or locking if another run can start before a prior one finishes. |
| Consistent rendering | Keep the worker’s OS and browser installation stable. | Pin the runtime image and browser version where the platform allows it. |
This is an engineering choice, not a guarantee about any particular scheduler. Check the documentation for the system you deploy on, especially its missed-run behavior, concurrency, retries, logs, and artifact retention.
5. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable is missing | The Playwright package is installed but its browser binary is not. | Run npx playwright install chromium in the environment that runs the job. |
| Navigation times out | The site is slow, unreachable, or waiting for a navigation condition that never occurs. | Check the URL and network access; choose a suitable timeout and navigation milestone. Wait for a specific ready element when the page continues loading after navigation. |
| Screenshot is blank or incomplete | The capture happened before the meaningful content rendered, or the application failed to load. | Wait for a visible page element or application readiness signal, then inspect navigation and console errors if the issue persists. |
| Element locator times out | The selector does not match, the element is hidden, or it appears later. | Confirm the selector against the page, wait for the intended state, and check whether the content is inside a frame. |
| Every scheduled run writes over the last | The output path is constant. | Include a timestamp or another unique run identifier in the filename. |
| Runs overlap or output conflicts | A capture lasts longer than the interval, or an external runner starts concurrent jobs. | Use a running guard, increase the interval, or configure concurrency/locking in the scheduler. |
| Images differ across runs without a site change | Rendering environment, fonts, animation, dynamic data, or page content changed. | Keep the browser and OS stable; control viewport and readiness; where appropriate, disable motion or mask dynamic regions for comparisons. |
| Timer stops firing | The process exited, crashed, or was paused by its environment. | Keep the worker supervised or use an external scheduler for independently launched runs. |
| Disk fills over time | Recurring files accumulate without retention. | Delete or archive captures older than a chosen period, or store them in a managed object store. |
6. Performance, reliability, and cost
Capture duration depends on the target site, readiness condition, page size, network, and browser environment; no general duration or throughput applies to every site. Full-page images can require more memory and storage than viewport images. Reuse a browser process in a long-lived worker if startup overhead matters, but isolate pages and ensure the browser closes during orderly shutdown. The simpler example launches and closes per capture, which limits resource leakage across runs at the cost of repeated browser startup.
Playwright itself is an open-source browser automation library; operational costs come from the machine or runner, browser execution, network transfer, and image storage. Estimate cost from the actual capture cadence, runtime, and retention needs in your deployment. There is no universal benchmark in the documentation reviewed for this guide.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. Its consent handling accepts cookie banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before the screenshot; each of those steps can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. AI agents can use its MCP server tools take_screenshot, get_page_info, and capture_pdf.
Here is a one-call capture. See the ScreenshotNeo API documentation for the request options, including caching TTL and other capture controls.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
The API also supports full-page captures, element selectors, dark mode, device presets and custom viewports, retina scale, custom CSS and JavaScript, click-before-capture, hidden selectors, wait conditions, request and resource blocking, custom headers and cookies, user agent and authorization, timezone and geolocation, transparent backgrounds, image resizing, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI spec. It accepts parameter names used by other screenshot APIs to make switching easier.
There are 1,000 screenshots per month on the free plan with no card. Paid plans start at $5 for 3,000 screenshots; higher listed plans are Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Sign up for 1,000 free screenshots a month with no card.
Frequently asked questions
Does Playwright need its test runner to save screenshots?
No. A standalone Node.js script can use Playwright’s browser and Page APIs to navigate and write an image. The test runner is useful for test assertions and baselines, not required for a basic screenshot file.
Does a timer run if the Node.js process is stopped?
No. setInterval is local to the process. Use a scheduler that can start the job independently if the worker may stop.
Should I use a fixed delay before each screenshot?
Only when a known delay is part of the page behavior. A locator or application readiness condition is usually a better signal that the content you need has rendered.
How do I keep visual comparisons stable?
Use the same operating system, browser version, viewport, and capture settings, and account for dynamic content. Playwright documents that rendering can vary across environments and modes.


