How to Take Scheduled Screenshots of WooCommerce Product Pages
Capture WooCommerce product pages on a recurring schedule with Playwright and cron, or use a managed screenshot API. Learn how to handle WP-Cron, storage, and failures.
To take scheduled screenshots of WooCommerce product pages, run a browser capture script on a recurring schedule, or use a managed screenshot service that supports recurring captures. For a self-hosted setup, Playwright can open each product URL, wait for the page to render, and save a screenshot; a server cron job can run that script daily or on another cadence. Store the files somewhere durable, record run times, and make failures visible.
This guide uses a server-side cron job to schedule a Node.js Playwright script. It is independent of WooCommerce’s WP-Cron queue, which normally runs when a request reaches the WordPress site and can run late on low-traffic stores. If your screenshot job is scheduled by WordPress instead, check WP-Cron and scheduled actions as described below.
1. Choose what to capture and how often
Start with a short list of public product-page URLs. Include a product variant only when it has a distinct public URL and you need a separate visual record. Choose one viewport and browser configuration and keep them consistent between runs so differences are easier to interpret.
| Decision | Guidance |
|---|---|
| Viewport or full page | Use a viewport capture for the initial visible layout. Use full-page capture when the record must include content below the fold, such as product details or reviews. |
| Cadence | Choose a cadence that matches the purpose. A daily archive may suit routine review; a launch may call for more frequent captures. There is no universally correct interval. |
| Destination and retention | Choose a folder or object store, decide how long to keep images, and make sure each capture has a timestamp in its filename or metadata. |
| Change review | Pixel comparisons can flag rotating promotions, ads, stock counters, or other dynamic content. Hide or stabilize elements that are irrelevant to the change you want to detect. |
| Failure handling | Log the URL, capture time, and error. Arrange an alert or inspect logs regularly; a scheduled command that fails silently is not a reliable archive. |
2. Self-hosted method: Playwright plus server cron
This setup runs a Node.js script on a machine that can reach your store. The example uses Playwright with Chromium, captures a full page after the main document loads, and writes timestamped PNG files. It records errors and sets a nonzero exit status if any URL fails.
Install Playwright
mkdir woo-product-captures
cd woo-product-captures
npm init -y
npm install playwright
npx playwright install chromium
mkdir -p screenshots logs
Create capture-products.mjs in that directory:
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const urls = [
'https://store.example/products/first-product/',
'https://store.example/products/second-product/'
];
const outputDir = new URL('./screenshots/', import.meta.url);
await mkdir(outputDir, { recursive: true });
const timestamp = new Date().toISOString().replaceAll(':', '-');
const browser = await chromium.launch({ headless: true });
let failures = 0;
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1
});
for (const [index, url] of urls.entries()) {
try {
const response = await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 60000
});
if (!response || !response.ok()) {
throw new Error(`Navigation returned ${response?.status() ?? 'no response'}`);
}
// Wait for the product content that matters to your store.
await page.locator('main').waitFor({ state: 'visible', timeout: 30000 });
// Allow images and late layout changes a brief opportunity to settle.
await page.waitForTimeout(1500);
const filename = `product-${index + 1}-${timestamp}.png`;
await page.screenshot({
path: new URL(filename, outputDir).pathname,
fullPage: true,
animations: 'disabled'
});
console.log(`Captured ${url} -> ${filename}`);
} catch (error) {
failures += 1;
console.error(`Failed ${url}: ${error.message}`);
}
}
} finally {
await browser.close();
}
if (failures > 0) process.exitCode = 1;
Replace the example URLs and, if your theme does not use a visible main element, change the selector to a stable product-content selector. If you need only the visible viewport, set fullPage: false. The script uses a fixed viewport and disables animations to make captures more consistent; it does not guarantee that every page element is static.
Schedule it with cron
First run it manually from its project directory and confirm that the expected images appear:
cd /absolute/path/to/woo-product-captures
node capture-products.mjs
Then edit the crontab for the user that owns the project and add a daily run at 02:15 server time:
crontab -e
15 2 * * * cd /absolute/path/to/woo-product-captures && /usr/bin/node capture-products.mjs >> logs/capture.log 2>&1
Use the actual absolute path to Node.js on your host; check it with command -v node. Cron uses the server’s timezone unless configured otherwise. To change the cadence, edit the five time fields in the schedule expression and verify the host timezone. Keep logs rotated or otherwise bounded, and make sure the screenshots directory has a retention policy.
Useful Playwright capture choices
fullPage: truecaptures the whole scrollable page;falsecaptures the current viewport.viewportcontrols the browser viewport dimensions. Keep them fixed for visual comparisons.deviceScaleFactorchanges pixel density. Use a consistent value when comparing runs.waitUntilcontrols the navigation milestone.domcontentloadedis often less fragile than waiting for every network connection to stop, especially on pages with analytics or chat. Wait separately for the content selector you need.timeoutbounds navigation or selector waits. Increase it only when the store genuinely needs more time; longer limits also make a run take longer.- For a page that lazy-loads below the fold, a full-page screenshot may not cause every image to load. If missing images matter, scroll through the page before capture or use a service that explicitly supports lazy-image loading.
3. Where WP-Cron and Action Scheduler fit
WooCommerce uses scheduled actions for background work. WP-Cron is normally triggered by page requests, so a quiet store may not run due tasks on time. WooCommerce documents using a server-side cron trigger when visitor traffic is too low and recommends checking scheduled action status and failures in the admin. See the WooCommerce scheduled-events guide and its system status report documentation.
Your screenshot schedule does not have to run inside WordPress. The external cron example above launches the browser directly, so it does not depend on a store visit to wake WP-Cron. If custom WordPress code or a plugin schedules the screenshot job, verify both the WP-Cron trigger and the Action Scheduler queue. In WooCommerce, inspect WooCommerce > Status > Scheduled Actions for pending, completed, and failed actions. Do not assume a displayed WP-Cron-enabled indicator proves that scheduled work is completing.
For stores that need WordPress background tasks to run independently of visitors, ask the host or administrator to configure a server-level trigger appropriate to the installation. WooCommerce’s documentation describes this as a way to avoid relying on visitor traffic. Coordinate the configuration with the host: the appropriate command and whether to trigger WP-Cron or process Action Scheduler depend on the site setup.
4. Managed capture versus self-hosting
A managed service can take responsibility for recurring runs, capture history, and delivery. Self-hosting gives you control over the browser and script, but you operate the scheduler, storage, retries, and alerts. Compare the options on timing reliability, wait behavior, full-page support, retention, delivery, and how you will handle false visual changes.
For recurring monitoring, Allscreenshots documents schedules with configurable URL, screen size, image format, optional full-page capture, cadence, timezone, and email or webhook delivery. Its documentation also describes run history, manual triggers, and pixel-based change alerts. Pixel comparison can flag normal changes such as rotating banners or ads, so consider hiding dynamic elements or adjusting sensitivity. Check the provider’s current plan details before choosing, because limits and pricing can change.
WooCommerce’s woocommerce-snap repository is another self-hosted reference: it documents a local Docker, Node.js, and Playwright workflow, configurable shopper URLs, and a full-page screenshot setting. Its documented capture command is for running captures; it does not by itself provide a recurring production schedule, persistent archive, or alerting system. Use the current repository instructions and treat scheduling, storage, retention, and failure notifications as separate operating tasks.
5. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A one-time API call can capture a WooCommerce product URL; schedule that call from your existing server cron or job runner for recurring captures. See the ScreenshotNeo API documentation for request options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://store.example/products/first-product/ \
-o product.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://store.example/products/first-product/",
},
timeout=90,
)
r.raise_for_status()
with open("product.webp", "wb") as image:
image.write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://store.example/products/first-product/'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('product.webp', image));
To make this recurring, put the command or script in your scheduler, use a stable output filename or timestamped archive as needed, and preserve the API key in an environment variable or secret manager rather than committing it to source control. Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.
6. Reliability, performance, and cost
- Runtime: A browser must start and render each page, so a run takes longer than a plain HTTP download. Keep the URL list and timeout sensible, and avoid overlapping scheduled runs if a prior run can still be active.
- Resource use: Full-page images and multiple browser contexts use more memory and storage than viewport captures. Capture only the pages and dimensions you need, and remove or archive old files.
- Retries: A transient network issue can fail one page. Log individual failures and decide whether your scheduler or wrapper should retry. Avoid infinite retries; they can create duplicate work or hide a persistent page problem.
- Consistency: Product inventory, prices, promotions, personalization, consent choices, and third-party widgets may change between runs. Keep the same viewport and browser settings, and stabilize irrelevant dynamic regions before comparing images.
- Access: Public product URLs are simplest. If a page is behind authentication or a bot challenge, a normal public capture may not represent a shopper’s view. Do not put customer credentials or private data into logs or public image links.
- Cost: Self-hosting has no per-capture API charge, but uses compute, storage, and maintenance time. Managed plans may charge by capture quota or features; check current pricing and limits. ScreenshotNeo’s stated tiers are Free: 1,000 per month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.
7. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No image appears after a scheduled run | Cron used the wrong working directory, Node path, or file permissions. | Run the exact command manually as the cron user. Use absolute paths and inspect the cron log and destination directory permissions. |
| The job runs late or not at all in WordPress | WP-Cron depends on requests, or the scheduled action queue is stalled. | Check WooCommerce > Status > Scheduled Actions, overdue and failed entries, and the WP-Cron configuration. Consider a host-configured server cron trigger. |
| Navigation times out | The page is slow, a third-party request never finishes, or the timeout is too short. | Use a realistic navigation milestone such as domcontentloaded, wait for the product selector separately, and inspect the page manually. Raise the timeout only if the page needs it. |
| Screenshot is blank or shows the wrong page | The URL redirects, the response failed, or content had not rendered before capture. | Check the final URL and HTTP response, wait for a stable content selector, and confirm the page is publicly reachable from the capture host. |
| Images below the fold are missing | Lazy-loaded images may not load until scrolled into view. | Scroll through the page before capturing, verify the image elements loaded, or select a capture tool with lazy-image loading support. |
| Every run looks different | Ads, rotating offers, chat widgets, stock details, or animations are changing. | Disable animations, hide irrelevant selectors, wait for page content to settle, or filter comparison regions. Review whether each detected difference matters. |
| One bad URL prevents all captures | The script exits early on an exception. | Catch errors per URL, as in the example, and set a failure exit code after processing the remaining URLs so the log still identifies the failed page. |
| Files consume too much disk | Timestamped archives accumulate without a retention policy. | Delete or move files older than the period you need, or store them in a destination with lifecycle retention rules. |
8. Operating checklist
- Confirm each product URL loads publicly and represents the product or variant you intend to track.
- Choose viewport or full-page capture, fixed dimensions, cadence, timezone, output destination, and retention period.
- Run one manual capture and inspect the target page, image dimensions, below-the-fold content, and output timestamp.
- Enable logging and ensure failures are surfaced through an alert or regular log review.
- Check the first scheduled run. For WordPress-triggered jobs, also inspect overdue or failed scheduled actions.
- Review captures periodically and adjust waits or hide selectors if dynamic content creates noise.
FAQ
Does WooCommerce include a built-in product-page screenshot schedule?
The sources here document WooCommerce background scheduling and the local woocommerce-snap testing utility, but do not establish a built-in production screenshot archive and recurring capture feature. Use a scheduler with a capture script or a managed service.
Can I use this for a staging store?
Yes, if the capture machine can reach it. Password protection or network restrictions may block the browser or a WordPress cron request; provide access through an approved route and avoid exposing private captures.
Should I schedule screenshots through WP-Cron?
Use WP-Cron if its timing characteristics meet your need and it is being triggered reliably. For low-traffic stores or tighter timing requirements, use a server-side scheduler or a managed schedule and monitor its run history.
Can a screenshot prove what a particular customer saw?
No. It records one browser session, viewport, time, and page state. Personalization, consent, inventory, and regional settings can make another visitor’s page differ.


