How to Schedule Screenshots of a Web App After Selecting a Date Range
Use Playwright to select and verify a date range, capture the right part of a web app, and schedule the script with GitHub Actions.
To schedule a screenshot of a web app after selecting a date range, write a browser automation script that opens the app, uses its actual date controls, verifies the selected range and updated results, and captures the page or a specific element. Then configure a workflow runner, such as GitHub Actions, to start that script on a recurring schedule. The scheduler starts the automation; it does not select dates or take the screenshot.
The example below uses Playwright with Node.js and GitHub Actions. The date selectors and authentication steps are placeholders: each app has its own controls, date format, timezone, and login requirements.
1. Inspect the app’s date-range control
Before automating, identify how the app expects a range. It might have two labeled text fields, a calendar widget, preset buttons such as “Last 30 days,” or a URL that accepts start and end dates. Use the app’s visible labels and accessible roles where possible. If those are unavailable, use stable attributes such as a documented test ID. Avoid selectors based on fragile layout details like an element’s position in a long list.
Find out whether the app applies a range immediately or requires an Apply button. Check which date format it accepts, whether the end date is inclusive, and which timezone defines the boundaries. A range that looks right in the inputs can still produce unexpected results if the app interprets dates in a different timezone.
2. Create a Playwright capture script
This runnable example assumes the app has inputs labeled “Start date” and “End date,” an Apply button, and a visible element that reports the applied range. Replace those names, the verification text, and the capture selector with values from your app. The example reads credentials from environment variables instead of storing them in source code.
import { chromium } from 'playwright';
const appUrl = process.env.APP_URL;
const username = process.env.APP_USERNAME;
const password = process.env.APP_PASSWORD;
const startDate = process.env.START_DATE;
const endDate = process.env.END_DATE;
for (const [name, value] of Object.entries({ appUrl, username, password, startDate, endDate })) {
if (!value) throw new Error(`Missing required environment variable: ${name}`);
}
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
try {
await page.goto(appUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 });
// Replace this login flow with the authentication method your app supports.
await page.getByLabel('Email').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('heading', { name: 'Reports' }).waitFor({ state: 'visible', timeout: 30_000 });
// These labels and the required date format are app-specific.
await page.getByLabel('Start date').fill(startDate);
await page.getByLabel('End date').fill(endDate);
await page.getByRole('button', { name: 'Apply' }).click();
// Verify that the app applied the requested period before capturing it.
const appliedRange = page.getByTestId('applied-date-range');
await appliedRange.waitFor({ state: 'visible', timeout: 30_000 });
await page.getByText(`${startDate} – ${endDate}`, { exact: true }).waitFor({ timeout: 30_000 });
// Wait for the actual report to finish loading; replace with an app-specific signal.
await page.getByTestId('report-results').waitFor({ state: 'visible', timeout: 30_000 });
await page.screenshot({ path: 'artifacts/report.png', fullPage: true });
} finally {
await browser.close();
}
Install the project dependencies and browser once in the runner environment:
npm init -y
npm install playwright
npx playwright install chromium
Run the script with required environment variables set. For example, a local shell can supply the range and app URL while credentials remain in your shell environment:
APP_URL='https://app.example.com/reports' \
APP_USERNAME='your-user' \
APP_PASSWORD='your-password' \
START_DATE='2026-09-01' \
END_DATE='2026-09-30' \
node capture.mjs
Save the file as capture.mjs. The example assumes the app accepts ISO-style dates. Change the format if its controls expect another format. For a calendar widget that does not allow direct text entry, interact with its month navigation and day buttons using the roles and labels exposed by that widget.
3. Verify the selected range and page readiness
Do not take the screenshot immediately after clicking Apply. Wait for an app-specific indication that the new range is active, then wait for the relevant results to load. Useful signals include a displayed range label, a results panel changing from loading to ready, or a request completing that the app exposes through its UI. A fixed delay can help with a known animation, but it is not a substitute for checking the page state.
Playwright’s screenshot assertion feature waits for two consecutive screenshots to match before comparing against an expected snapshot. That stabilization behavior applies to screenshot assertions; a plain page.screenshot() call does not automatically know when your app’s data is ready. See the Playwright Page screenshot API and visual comparisons documentation.
When the date range comes from a URL parameter, prefer setting that parameter directly if the app supports it, then verify that the app reflects the range. This can avoid brittle calendar interactions, but only use URL parameters the app actually supports.
4. Choose what to capture
| Capture scope | Playwright option | Use it when | Trade-off |
|---|---|---|---|
| Visible viewport | page.screenshot({ path: 'artifacts/view.png' }) |
The important content fits on screen. | Content below the fold is omitted. |
| Entire page | page.screenshot({ path: 'artifacts/full.png', fullPage: true }) |
You need below-the-fold content too. | Tall pages create larger files and may include more changing content. |
| One element | page.getByTestId('report-results').screenshot({ path: 'artifacts/panel.png' }) |
You want a report panel or chart without surrounding navigation. | The selector must identify the intended element reliably. |
Playwright also supports PNG, JPEG, and scale options for page screenshots. Use PNG when crisp text and visual comparison matter; JPEG can reduce file size when some compression is acceptable. See the API options for the current option list.
5. Schedule the script with GitHub Actions
Create .github/workflows/capture.yml on the repository’s default branch. This example runs at 09:00 UTC on weekdays, installs Chromium, passes credentials from repository secrets, and uploads the generated screenshot as a workflow artifact.
name: Scheduled report screenshot
on:
schedule:
- cron: '0 9 * * 1-5'
workflow_dispatch:
jobs:
capture:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Capture report
run: node capture.mjs
env:
APP_URL: ${{ secrets.APP_URL }}
APP_USERNAME: ${{ secrets.APP_USERNAME }}
APP_PASSWORD: ${{ secrets.APP_PASSWORD }}
START_DATE: '2026-09-01'
END_DATE: '2026-09-30'
- uses: actions/upload-artifact@v4
with:
name: report-screenshot
path: artifacts/report.png
retention-days: 14
Ensure the project has a lockfile if you use npm ci. The retention value here is an example; choose a period that matches your reporting and storage needs. For changing ranges, calculate the dates in the script using the app’s timezone rules, or set them as workflow inputs. Don’t assume the runner’s local timezone matches the app’s timezone.
GitHub Actions scheduled workflows use POSIX cron, default to UTC, and support an IANA timezone setting. The documented minimum interval is once every five minutes. Scheduled runs use the latest commit on the default branch, and GitHub notes that scheduled workflows in public repositories are disabled after 60 days without repository activity. Read GitHub’s schedule event documentation before relying on a schedule for an important capture.
6. Make recurring captures comparable and retrievable
- Keep the rendering environment steady. Browser version, operating system, viewport, device scale factor, fonts, headless mode, and page settings can affect pixels. Playwright warns that rendering can vary across host environments. Run comparisons in a consistent environment when visual diffs matter.
- Use meaningful filenames. Include the date range or run date, for example
report-2026-09-01-to-2026-09-30.png. Avoid overwriting the only copy if you need a history. - Choose a destination and retention period. Workflow artifacts are convenient for short-term retrieval. For longer retention or downstream processing, upload the file to storage your organization already uses. The right destination depends on access controls and retention requirements.
- Keep authentication secrets private. Store credentials in the runner’s secret store, grant only the required access, and avoid printing tokens or page content that could expose private data in logs.
- Make failures visible. Use workflow notifications or monitoring already available to your team. A successful schedule trigger does not prove the app loaded, the range applied, or the artifact was saved.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Locator times out | The label, role, or test ID differs from the app, or the page has not reached the expected state. | Inspect the live page’s accessible names and update the app-specific locator. Wait for a real navigation or UI signal. |
| Screenshot shows the previous range | The app did not apply the fields, or the script captured before results refreshed. | Click the actual Apply control if required and assert the displayed range and refreshed results before capture. |
| Date appears shifted by one day | The app interprets the range in a different timezone or treats an endpoint as inclusive or exclusive. | Confirm the app’s timezone and boundary semantics. Compute dates in that timezone and verify the rendered range. |
| Login loops or is denied | The app requires MFA, SSO, a trusted network, or a session flow not represented by the placeholder login. | Use an approved automation login or session-state approach supported by the app. Do not hard-code credentials or bypass access controls. |
| Browser executable missing in CI | The Playwright browser was not installed for the runner. | Run npx playwright install --with-deps chromium in the workflow after installing the package. |
| Artifact upload says no files found | The screenshot path differs from the upload path, or capture failed before writing the file. | Use the same path in the screenshot and upload steps. Let the script fail on capture errors so the job does not appear successful. |
| Images differ even though data is unchanged | Rendering environment, fonts, dynamic content, timestamps, or animations changed. | Keep the browser and runner environment consistent; hide or stabilize irrelevant dynamic regions if your app permits it. |
| Scheduled run never starts | The workflow is not on the default branch, its cron syntax is invalid, or a public repository’s schedule was disabled after inactivity. | Check the default branch and Actions settings, validate the cron expression, and consult the schedule event documentation. |
Performance, reliability, and cost
Browser startup and page loading usually dominate the work. Install only the browser you need, capture one panel instead of an entire long page when that meets the goal, and avoid waiting for every network request if the app has analytics or persistent connections. Wait for the specific data you need instead. Set timeouts that reflect the app’s normal load while still allowing the job to fail visibly when the app is stuck.
GitHub Actions usage and artifact storage can contribute to operational cost depending on repository plan and usage; this guide does not assume a particular plan or calculate a price. Check the terms for your account and size artifact retention to the actual need. Keep in mind that a scheduled workflow is a trigger, not a guarantee of exact execution time or application readiness.
For image comparisons, use the same operating system and browser setup that produced the baseline. Playwright documents environment variation as a source of visual differences. A plain capture should still verify app readiness explicitly; screenshot stabilization in visual assertions does not establish that a report query has completed.
Or skip the browser setup
If the desired date range can be represented by a URL the app serves without an interactive session, ScreenshotNeo can capture that URL with one API request. If the app requires authentication or interaction, configure the URL and any required request details accordingly; a screenshot API cannot infer which date controls to operate. See the ScreenshotNeo API documentation and ScreenshotNeo.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Can GitHub Actions choose the date range for me?
No. The schedule starts the workflow. Your Playwright script must operate the app’s controls or navigate to a supported date-specific URL.
Can I run the capture manually before scheduling it?
Yes. The example includes workflow_dispatch so you can start it from the Actions interface, and you can run the Node.js script locally with the same environment variables.
Should I capture the whole page or just the report?
Capture the smallest area that answers your reporting need. Use a full-page image when below-the-fold content matters; use an element screenshot for a specific chart or panel.
Can I use a different scheduler?
Yes. Any runner that can execute Node.js, install Playwright’s browser, access the app, provide secrets, and store the output can run the same script. Scheduler timing, timezone handling, retention, and secret management depend on that provider.


