How to Schedule Screenshots of a Webpage After Scrolling to a Specific Section
Use Playwright to find a section reliably, capture it on a schedule, and save timestamped screenshots you can inspect or compare.
To schedule a screenshot after scrolling to a section, use browser automation to open the page, wait for a stable locator for that section, scroll it into view, and capture either the visible viewport or the element itself. Then configure a scheduler to run the script at your chosen times. A scheduler starts the work; it does not find the section or take the screenshot.
This guide uses Playwright with Node.js. Its screenshot API supports viewport, full-page, and locator screenshots, and its scrolling guide recommends manual scrolling when you need to position a page for a screenshot. Playwright screenshots · Playwright scrolling
1. Choose what the screenshot should contain
Decide what you mean by “screenshot of a section” before writing the script:
| Capture type | What it shows | Use it when |
|---|---|---|
| Viewport | The visible browser area after scrolling to the target | You want the section in context, with nearby content |
| Element | A cropped image of the matched element | You need just the heading, chart, card, or section container |
| Full page | The entire scrollable document in one tall image | You need the whole page, not a view centered on one section |
These results are different: a viewport screenshot does not become an element crop just because the target was scrolled into view. Playwright documents viewport, full-page, and element capture separately. Screenshot options
2. Install Playwright and create the capture script
Use a maintained Node.js installation and create a project directory. Install Playwright and its browser binaries using the commands for your environment; keep the installed package and browser version together in your project so scheduled runs use the same setup.
mkdir section-capture
cd section-capture
npm init -y
npm install playwright
npx playwright install chromium
Save the following as capture-section.mjs. It accepts the page URL and a CSS selector as arguments, checks the target exists, scrolls it into view, pauses briefly for visual settling, and writes a timestamped PNG. By default it captures the viewport; pass --element to save only the matched element, or --full-page to capture the whole document.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import path from 'node:path';
const [url, selector, ...flags] = process.argv.slice(2);
if (!url || !selector) {
console.error('Usage: node capture-section.mjs <url> <css-selector> [--element | --full-page]');
process.exit(2);
}
const mode = flags.includes('--element') ? 'element' : flags.includes('--full-page') ? 'full' : 'viewport';
const outputDir = process.env.OUTPUT_DIR ?? 'captures';
const timeoutMs = Number(process.env.TARGET_TIMEOUT_MS ?? 30000);
const safeName = selector.replace(/[^a-z0-9_-]+/gi, '-').replace(/^-|-$/g, '').slice(0, 60) || 'section';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const outputPath = path.join(outputDir, `${safeName}-${timestamp}.png`);
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 });
page.setDefaultTimeout(timeoutMs);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
const target = page.locator(selector);
await target.waitFor({ state: 'visible' });
await target.scrollIntoViewIfNeeded();
// A short settling delay can help with transitions; prefer a page-specific readiness condition when available.
await page.waitForTimeout(300);
await mkdir(outputDir, { recursive: true });
if (mode === 'element') {
await target.screenshot({ path: outputPath, type: 'png', animations: 'disabled' });
} else {
await page.screenshot({ path: outputPath, type: 'png', fullPage: mode === 'full', animations: 'disabled' });
}
console.log(`Saved ${outputPath}`);
} finally {
await browser.close();
}
Run it once manually before scheduling it:
node capture-section.mjs https://example.com 'main h2#pricing'
Replace the example URL and selector with the real page and a unique target. The selector is a CSS selector; you can also choose a locator directly in code, such as page.getByRole('heading', { name: 'Pricing', exact: true }), when accessible name and role make the target clearer.
3. Select a stable section locator
Selectors are the most common source of scheduled capture failures. A positional selector such as main > div:nth-child(4) may match a different block after a redesign or content insertion. Prefer, in order:
- A unique test attribute intended to remain stable, such as
[data-testid="pricing-section"]. - A unique ID, such as
#pricing. - A distinctive heading or accessible role and name.
- A CSS selector tied to a stable section structure.
Check that the locator is unique. If it matches multiple elements, refine it to the intended section rather than relying on the first match accidentally. If the target is inside an iframe, locate the frame and query within it; a page-level locator cannot see into a frame’s document. If it is inside a nested scroll container, make sure the target itself is visible and that the page’s layout places it where the screenshot should show it.
When a sticky header covers the section after scrolling, adjust the final position deliberately. One option is to scroll the locator into view and then use a small page scroll adjustment. For precise alignment, measure the target and scroll by its position minus the desired top offset. Avoid assuming a fixed pixel distance across responsive viewports.
4. Wait for meaningful page readiness
The script waits for the document’s DOM content and for the target to become visible. That is a starting point, not a guarantee that every page has finished rendering. Single-page apps may populate the target later; charts may draw after data arrives; lazy images may load only near the viewport.
Prefer a page-specific signal where possible. For example, wait for a chart’s loading indicator to disappear, a result count to reach the expected state, or a data attribute to change. A fixed delay can be a fallback, but it adds runtime and still may be too short or unnecessarily long. Playwright’s locator scrolling can position a target for capture; its docs note that manual scrolling can be useful when screenshot positioning matters. Scrolling elements into view
For lazy-loaded images, scrolling the target into view may trigger loading for nearby content. If an entire long page must be captured, consider whether the page’s lazy content has loaded before using full-page mode; inspect the resulting image because a tall full-page capture and a viewport capture answer different questions.
5. Schedule the script
Use a scheduler available in your environment to invoke the same command you tested manually. The shot-scraper documentation provides an example of screenshot automation with GitHub Actions, but the scheduler’s current syntax, quotas, timezone behavior, and artifact retention depend on the provider and configuration. shot-scraper documentation
When choosing or configuring a scheduler, check these items in its current official documentation:
- Recurrence syntax and timezone semantics, including daylight-saving behavior.
- How to install the required Node.js version and browser binaries for each run.
- Where output files go, how long they remain available, and whether you need external storage.
- How failures are surfaced, such as logs, alerts, or retry configuration.
- How secrets are provided if the target requires authentication.
- Whether overlapping runs can occur if a capture takes longer than its interval.
For GitHub Actions or another hosted runner, put the script and dependency manifest in the repository, install the browser during the workflow, invoke the tested command, and explicitly save or upload the output using that runner’s documented artifact or storage mechanism. Verify current scheduling syntax and retention settings directly with the provider before relying on them. The research sources do not establish current limits or a universally best scheduler.
6. Handle authentication and sensitive pages
If the page requires login, use a dedicated account with only the access needed for capture. Keep passwords, cookies, and tokens in the scheduler’s secret store; do not commit them to the repository, embed them in a URL, or print them in logs. Create or load an authenticated browser context using an approved session flow, then navigate to the target. Treat saved browser state as a credential and restrict access to it.
For pages that change by user, locale, or permission, record the intended account and region in your capture configuration. Confirm that the capture is permitted by the site and that the destination where screenshots are stored is appropriate for the page’s contents.
7. Keep repeated captures comparable
If screenshots are used for visual comparison, control the conditions that affect pixels:
- Use the same browser engine and version, viewport dimensions, and device scale factor.
- Use the same locale, timezone, authentication state, and color scheme.
- Disable animations for a stable single frame, as in the example.
- Wait for fonts, images, and data that matter to the section.
- Expect differences when the page content itself changes, ads rotate, or remote assets vary.
Identical code does not guarantee identical pixels across different machines or changing websites. Keep browser and viewport setup consistent, inspect changes, and avoid treating every pixel difference as a product regression.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Target locator times out | Wrong selector, delayed client rendering, target hidden, or login/consent page shown instead | Confirm the URL and selector in a local browser; wait for the app’s meaningful ready state; check whether authentication is required. |
| Screenshot is at the top of the page | The scroll target was not the intended element, or a script reset scroll position after the initial scroll | Wait for late layout changes, scroll immediately before capture, and inspect whether the page performs automatic restoration. |
| Header covers the target | A sticky or fixed header overlaps the section | Adjust the scroll position to leave room for the header or capture the element directly. |
| Element screenshot is clipped or unexpectedly sized | The locator matched a child or wrapper with constrained dimensions, or content overflows its box | Target the correct container; inspect computed layout; use a viewport capture when surrounding context matters. |
| Blank or incomplete chart | Data fetch or client-side drawing was still in progress | Wait for the chart’s actual ready condition rather than adding a large generic delay. |
| Images are missing | Images are lazy-loaded, blocked, or still downloading | Scroll the relevant content into view and wait for the needed image elements to finish loading; check network and browser logs. |
| Browser executable missing in scheduled run | The runner installed the Node package but not its browser binary | Install the matching browser in the scheduler environment and use the documented system dependencies for that runner. |
| Works locally, fails on schedule | Different working directory, environment, permissions, secrets, network access, or browser version | Use explicit paths, log non-secret configuration, reproduce the runner’s setup, and check the scheduler’s run logs. |
| Two runs overwrite each other | Output name is static or run overlap occurs | Include a timestamp or run identifier in filenames and configure overlap handling in the scheduler. |
9. Performance, reliability, and cost
Capture time is mainly affected by page load, application readiness, browser startup, and the waits you choose. A fresh browser for each scheduled run is simple and isolated; a persistent process can reduce repeated startup work but needs lifecycle management and careful isolation. Do not set the schedule interval shorter than the time the job can reasonably take unless the scheduler prevents overlapping runs.
Make each run observable: log the target URL without secrets, the selected section, start and finish time, output path, and failure reason. Use bounded timeouts, and decide whether a transient navigation failure should cause one retry or an alert. Retries can help with transient network issues but may duplicate outputs, so use unique filenames and avoid assuming a failed run produced a valid image.
Browser automation has infrastructure costs: compute time, browser installation and maintenance, and storage for retained files. Scheduler quotas and artifact retention vary and are not established here; verify them with the service you use. Keep only captures you need, and choose image format and dimensions according to the use case. PNG is useful when preserving crisp UI details; a lower-quality JPEG may reduce storage for photographic pages.
10. Alternative: Puppeteer
If your JavaScript project already uses Puppeteer, it can also capture page and element screenshots. Its official guide describes both; an element screenshot attempts to scroll a hidden element into view by default. The same scheduling pattern applies: write a script that waits, targets, captures, and saves, then have your scheduler invoke it. Puppeteer screenshots
Choose between Playwright and Puppeteer based on your existing ecosystem and locator needs. Playwright’s cited scrolling documentation gives direct guidance for positioning a locator before capture.
11. Or skip the browser setup
For a direct webpage capture, ScreenshotNeo takes a screenshot or PDF through a single GET request. See the ScreenshotNeo API documentation for its options. A screenshot API call does not replace the section-scrolling browser workflow above when you specifically need to interactively locate and scroll to a page section; use browser automation for that positioning requirement.
Example request:
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, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month.
FAQ
Should I capture the section or the viewport around it?
Capture the element for a tight crop. Capture the viewport when nearby content and page context matter.
Can I use a heading’s text instead of a CSS selector?
Yes. A role-and-name locator is often easier to understand and maintain when the heading has a stable accessible name. Confirm it uniquely identifies the intended target.
Will scheduled screenshots always be pixel-identical?
No. Remote content, page updates, browser versions, and rendering environments can change the result. Keep the environment consistent and review captures in context.
Can this capture a section inside a login-protected page?
Yes, if the browser context is authenticated and authorized to access it. Store session material securely and avoid exposing it in logs or source control.


