How to Schedule Website Screenshots in Azure DevOps Pipelines
Schedule Playwright screenshots with Azure Pipelines YAML, publish them as artifacts, and troubleshoot UTC, branch, and CI issues.
Use an Azure Pipelines YAML cron schedule to start a recurring job, run Playwright to capture the site, and publish the output directory as a pipeline artifact. For captures that must run even when the repository has not changed, set always: true. Cron times use UTC, so convert your intended local capture time deliberately.
This guide covers a scheduled capture pipeline, full-page screenshots, artifact retention, common reasons schedules do not run, and the factors that affect repeatability. Microsoft and Playwright document the scheduling, CI, capture, and artifact steps; the YAML below combines them into a screenshot workflow.
1. Add a scheduled trigger to the main pipeline YAML
Put schedules: in the pipeline’s main YAML file, not in a template. Each cron expression has five fields: minute, hour, day of month, month, and day of week. Azure evaluates the time in UTC. Add branch filters for the branches that should receive the schedule.
trigger: none
pr: none
schedules:
- cron: '0 6 * * *' # Daily at 06:00 UTC
displayName: Daily website screenshots
branches:
include:
- main
always: true
Here, trigger: none and pr: none disable CI and PR starts; the schedule remains. Remove either setting if you also want those trigger types. Change main to the branch containing this YAML and the code to run. A schedule is associated with the YAML in each branch, so branch filters and branch contents both matter.
always: true makes the scheduled run happen even when source code or pipeline settings have not changed since the last successful scheduled run. Without it, the default behavior can skip a run when nothing changed. The optional batch setting controls what happens when an earlier scheduled run is still in progress; Microsoft documents this option for Azure DevOps Server 2022.1 and later.
Choose the cron time carefully
For example, 0 6 * * 1-5 means 06:00 UTC Monday through Friday. If you want a consistent local wall-clock time in a region that observes daylight saving, you may need to adjust the UTC hour when the clocks change. Preview upcoming runs in the pipeline’s Scheduled runs view; it shows only the next seven days.
Microsoft currently documents limits of around 1,000 scheduled runs per pipeline per week and 10 runs per pipeline per 15 minutes. A daily or hourly capture usually avoids the need to approach those limits.
2. Create a Playwright capture script
Use Playwright’s Page API to open a page and save a screenshot. The following standalone Node.js script captures the viewport by default and supports an optional full-page capture. It reads the target from a pipeline variable so the URL does not need to be hard-coded.
// scripts/capture.mjs
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const targetUrl = process.env.TARGET_URL;
const outputDir = process.env.SCREENSHOT_DIR ?? 'screenshots';
const fullPage = process.env.FULL_PAGE === 'true';
if (!targetUrl) {
throw new Error('Set TARGET_URL to the website to capture.');
}
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 60000 });
await page.screenshot({
path: `${outputDir}/website.png`,
fullPage,
animations: 'disabled'
});
} finally {
await browser.close();
}
For a simple screenshot, page.screenshot({ path: 'screenshot.png' }) captures the visible viewport. Set fullPage: true to capture the entire scrollable page. The example uses networkidle to wait for network activity to settle, but sites with long polling or persistent connections may never become idle; in that case, wait for a meaningful selector or use a bounded delay instead.
Install the project dependencies
Commit a package-lock.json alongside package.json so the pipeline can use npm ci. For the script above, a minimal manifest is:
{
"private": true,
"type": "module",
"scripts": {
"capture": "node scripts/capture.mjs"
},
"dependencies": {
"playwright": "YOUR_PINNED_PLAYWRIGHT_VERSION"
}
}
Replace the placeholder with the Playwright version your project has chosen and generate and commit the lockfile locally. Pinning the dependency and installing browsers from that version helps keep recurring runs consistent.
3. Run the capture and publish the screenshots
This pipeline uses a Linux Microsoft-hosted agent, installs Node dependencies and the matching browser dependencies, runs the capture, then publishes the generated directory. The artifact step uses succeededOrFailed() so files are still published if a later step fails after output was created.
pool:
vmImage: ubuntu-latest
variables:
TARGET_URL: 'https://example.com'
SCREENSHOT_DIR: 'screenshots'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: Use Node.js
- script: npm ci
displayName: Install dependencies
- script: npx playwright install --with-deps chromium
displayName: Install Playwright browser and Linux dependencies
- script: npm run capture
displayName: Capture website
env:
TARGET_URL: $(TARGET_URL)
SCREENSHOT_DIR: $(SCREENSHOT_DIR)
FULL_PAGE: 'true'
- task: PublishPipelineArtifact@1
condition: succeededOrFailed()
inputs:
targetPath: 'screenshots'
artifact: 'website-screenshots'
publishLocation: 'pipeline'
displayName: Publish screenshots
Set TARGET_URL to the page you are authorized to capture. The targetPath must match the directory where the script writes its images. After a run, download the website-screenshots pipeline artifact from that run to retrieve the files; files on a hosted agent are otherwise temporary.
The command above installs Chromium only. If your capture uses a different Playwright browser, install that browser instead. On Linux, Playwright needs browser system dependencies; --with-deps installs them. The official Playwright CI guidance says Windows and macOS agents need no additional configuration beyond installing Playwright and running it. Linux users can also use Playwright’s supported container.
4. Configure capture behavior for the target page
- Viewport or full page: Leave
fullPagefalse to capture the viewport; set it true for the scrollable page. Very long pages create large images and can take longer to render and publish. - Wait condition:
networkidlecan work for mostly static pages. For dynamic sites, wait for a content selector withpage.waitForSelector()or wait for a short, bounded delay after navigation. Avoid unbounded waits. - Viewport: Fix viewport width and height to make captures comparable from run to run. If responsive layouts matter, run separate captures at the viewport sizes you care about.
- Animations: Playwright’s screenshot option
animations: 'disabled'reduces variation from CSS animations and transitions. Dynamic timestamps, rotating content, ads, and personalized pages can still change pixels. - Authentication: If the page requires login, use an appropriate Playwright storage state or authenticate in the script. Store credentials and state as protected pipeline secrets or secure files; do not commit live credentials.
- Failure behavior: Let navigation or screenshot errors fail the script so the run is visibly unsuccessful. Publishing with
succeededOrFailed()can preserve partial output and diagnostics.
5. Keep scheduled captures repeatable
A screenshot can change even when the page code does not. Operating system, browser version, hardware, power source, headless mode, and other rendering conditions can affect pixels. Keep the agent image, Playwright version, browser, viewport, and capture settings stable when you compare runs. Playwright recommends using one worker in CI unless the system is powerful and self-hosted; for a single capture script, avoid adding parallel captures unless needed.
A recurring capture that archives images is different from a visual regression test. For regression checks, Playwright Test’s toHaveScreenshot() creates a baseline on first use and compares later captures against it. Review and manage those baselines deliberately: a successful scheduled artifact upload alone does not establish that a visual change is acceptable.
6. Troubleshoot missed runs and missing images
| Symptom | Likely cause | Fix |
|---|---|---|
| No scheduled run appears | Cron was interpreted as local time, has invalid fields, or falls outside the preview window. | Validate the five cron fields in UTC, check the next seven days in Scheduled runs, and convert local time including daylight-saving changes. |
| YAML schedule is ignored | A schedule is configured in the pipeline settings UI; UI-defined schedules take precedence over YAML. | Review pipeline settings and remove the UI schedule if YAML should control scheduling. Microsoft says a push is needed after removing UI schedules before YAML schedules are reevaluated. |
| Only some branches run | The branch filter excludes them, or that branch’s YAML does not define the schedule. | Check branches.include and branches.exclude, and verify the main pipeline YAML in each intended branch. |
| Run did not happen after an unchanged day | The default scheduled behavior skips runs when source or pipeline settings have not changed since the last successful scheduled run. | Set always: true for recurring captures that must run regardless of changes. |
| Browser launch fails on Linux | Browser binaries or required operating-system libraries are missing. | Run npx playwright install --with-deps chromium after npm ci, or use a supported Playwright container. |
| Navigation times out | The site is slow, unreachable from the agent, blocks automation, or never reaches the selected wait condition. | Confirm agent network access and site permissions. Increase the timeout only when appropriate; for persistent network activity, wait for a content selector or use a bounded delay rather than networkidle. |
| Artifact is missing or empty | The script wrote to a different directory, failed before creating files, or the publish step targeted the wrong path. | Match SCREENSHOT_DIR and targetPath, inspect preceding logs, and use succeededOrFailed() to publish partial output after failures. |
| Images differ between runs | Rendering environment or page content changed. | Stabilize OS image, browser and Playwright versions, viewport, and headless settings; account for timestamps, personalization, animations, and other dynamic content. |
7. Performance, reliability, and cost
Capture frequency and page weight drive pipeline usage. Each run must start an agent, install or access a browser, load the target, render the page, and upload the artifact. Use a sensible cadence, keep the browser install aligned with the pinned Playwright version, and capture only the pages and viewport sizes needed. Full-page images of long pages require more memory and artifact storage than viewport images.
Scheduled-run limits are Azure Pipelines limits, not browser throughput guarantees: Microsoft documents around 1,000 scheduled runs per pipeline per week and 10 runs per pipeline per 15 minutes. Reliability also depends on agent availability, network access, target-site behavior, and stable browser rendering. The research sources provide no general cost figure; pipeline cost depends on your Azure DevOps plan, agent type, duration, and artifact retention settings. Check your organization’s current billing and retention configuration.
Or skip the browser setup
If you only need the screenshot file and do not need a custom Playwright workflow, ScreenshotNeo provides a website screenshot API and MCP server. Its API accepts a URL and returns an image or PDF; see the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Cookie and consent banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. An 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 shots. ScreenshotNeo also supports full-page capture, element capture, device and viewport settings, PDF options, custom CSS and JavaScript, wait conditions, request blocking, caching, signed links, async jobs, bulk capture, and more; every feature is on every plan.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
FAQ
How do I download screenshots from a run?
Publish the directory with PublishPipelineArtifact@1, then download the named artifact from that pipeline run. Without publishing, files on a hosted agent are temporary.
Can I use the schedule with a Playwright test instead of a script?
Yes. The schedule starts the pipeline; invoke your Playwright Test command in the job and publish the directory where the test or setup writes screenshots.
Can I schedule more than one capture time?
Define additional cron entries under schedules:, with clear display names and branch filters. Check the upcoming run preview and the documented per-pipeline schedule limits.


