How to Capture a Webpage Screenshot with a Custom User Agent
Set a custom user agent before navigation, wait for the page content you need, and save a viewport, full-page, or element screenshot.
To capture a webpage with a custom user agent in Playwright, set userAgent when creating a browser context, navigate to the page, wait for the content you need, then call page.screenshot(). The user-agent string is only one input to rendering: configure the viewport and other device properties separately if you need a particular layout.
Capture a screenshot with Playwright
This runnable Node.js example saves a viewport screenshot to page.png. Replace the example user-agent value with the exact string required by your test or site owner. It is a placeholder, not a universal or recommended browser identity.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const context = await browser.newContext({
userAgent: 'ExampleBot/1.0 (compatible; ScreenshotCapture/1.0)',
viewport: { width: 1280, height: 800 },
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'page.png' });
await context.close();
} finally {
await browser.close();
}
})();
Install Playwright and its Chromium browser, then run the script:
npm install playwright
npx playwright install chromium
node screenshot.js
The userAgent context option applies before navigation, so the initial request uses the configured value. Playwright documents this setting in its user-agent and device emulation guide; see its screenshot guide for capture options.
Choose the right capture and wait strategy
Viewport, full page, or one element
- Viewport:
await page.screenshot({ path: 'page.png' });captures the visible viewport. - Full page:
await page.screenshot({ path: 'page-full.png', fullPage: true });captures the full scrollable page as a tall image. - One element:
await page.locator('main article').screenshot({ path: 'article.png' });captures the selected element. Use a selector that uniquely identifies the intended content.
For other formats, use an appropriate extension and the screenshot options supported by your Playwright version. PNG is suitable when lossless output matters. JPEG supports a quality setting and is often smaller for photographic pages. Playwright also supports screenshot buffers when the image should be uploaded or processed without first writing a file. Consult the Page screenshot API for current options.
Wait for the page you actually need
load waits for the load event, but modern pages can continue fetching data, hydrating components, and loading images afterward. Choose a navigation condition for the site and then wait for a meaningful signal when content is rendered late:
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="report-ready"]').waitFor({ state: 'visible' });
await page.screenshot({ path: 'report.png' });
Playwright navigation conditions include commit, domcontentloaded, load, and networkidle. None is right for every site. A page with long-lived analytics or streaming requests may never become network-idle; a page that renders after an API response may need an explicit selector wait. A fixed delay can be useful for a known animation or scheduled render, but it is less reliable than waiting for the actual content.
Custom user agent is not full device emulation
A user-agent string can influence server responses and application logic, but it does not by itself set a mobile viewport, screen size, touch support, or other device characteristics. Responsive CSS commonly depends on viewport width, so a desktop-sized context with a mobile user agent can still produce a desktop layout.
Set the properties relevant to the scenario independently:
const context = await browser.newContext({
userAgent: 'Example mobile test user agent',
viewport: { width: 390, height: 844 },
deviceScaleFactor: 1,
isMobile: true,
hasTouch: true,
});
Use a Playwright device preset when you need a coordinated profile, then override values that matter to your test. Presets can set user agent, viewport, screen, and touch behavior together. Playwright notes that its Desktop Chrome preset uses a Windows-specific user-agent string; set userAgent: undefined if you want the platform’s own user agent instead. See the official emulation documentation.
For repeatable screenshots, also consider locale, timezone, color scheme, geolocation, and permissions if the page uses them. Keep these inputs fixed across runs so differences in output are easier to diagnose.
Alternative: Puppeteer
If your project already uses Puppeteer, set the page’s user agent before navigation, then navigate and save the screenshot. This example is an ES module; save it as screenshot.mjs.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setUserAgent('ExampleBot/1.0 (compatible; ScreenshotCapture/1.0)');
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Install and run with npm install puppeteer and node screenshot.mjs. Puppeteer’s official screenshot guide documents page and element screenshot workflows. Choose the library that fits your existing browser automation setup; the sources cited here do not establish a performance winner for this task.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. Its API can return an image or PDF from one GET request. The supplied example captures a URL; see the ScreenshotNeo documentation for API options, including custom user-agent configuration.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie banners are accepted like a visitor and removed, along with supported newsletter popups and chat widgets, before the shot.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
Options and edge cases to plan for
| Need | What to configure | What to watch for |
|---|---|---|
| Target a server’s user-agent branch | Set the exact string before the first navigation. | Redirects or client-side requests may behave differently; inspect the final page and relevant requests. |
| Match a responsive layout | Set viewport dimensions separately from the user agent. | Screen and touch settings can also affect application behavior. |
| Capture lazy content | Scroll relevant sections into view and wait for images or content to load before a full-page capture. | Full-page capture does not guarantee every site’s lazy loader has populated every section. |
| Capture authenticated pages | Establish the required cookies or storage state in the context before navigating. | Do not put secrets in source control or public logs. |
| Handle overlays | Dismiss the site’s modal when permitted, or capture the state intentionally. | A consent banner or popup can cover content in a DIY browser capture. |
| Save or transmit output | Write to a path or use a screenshot buffer. | Ensure the destination directory exists and that the process can write to it. |
Only set a user agent you are authorized to use. A changed user-agent header does not make an automated browser a real device or a human visitor, and sites may use other signals. Respect the site’s access rules and avoid using identity changes to bypass controls.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot shows the wrong layout. | The user agent changed, but the viewport or device properties did not. | Set the viewport and any needed screen or touch properties explicitly; verify the page’s computed layout. |
| The page is blank or missing data. | The capture happened before client-side rendering or an API response completed. | Wait for a visible content selector or another page-specific readiness signal before capturing. |
| Navigation times out. | The site is slow, unavailable, or keeps connections open. | Choose an appropriate navigation event, allow a suitable timeout for your environment, and wait for the content you need rather than requiring network idle on every page. |
| The image has missing lower-page sections. | Content or images load lazily as the page scrolls. | Scroll through the relevant areas, wait for images to load, then take the full-page screenshot. |
| The script reports that the browser executable is missing. | The Playwright browser binary was not installed in the environment. | Run npx playwright install chromium; in Linux environments, install the required system dependencies as directed by Playwright. |
| The saved image is missing or truncated. | The output path is invalid, its directory does not exist, or the process ended before the screenshot promise completed. | Use a writable path, create the directory, and await page.screenshot() before closing the browser. |
| A server still returns a different response. | The site may consider cookies, client hints, IP, or other request signals in addition to the user-agent string. | Check the actual response and the site’s documented behavior; configure only the additional context properties relevant to the authorized test. |
Performance, reliability, and cost
Launching a browser has setup and memory overhead; for a batch of captures, reuse a browser process while creating isolated contexts for different user-agent scenarios. Close pages, contexts, and the browser when finished. Limit concurrency to what the host can support, and set timeouts so a single slow site does not hold a worker indefinitely.
For reliable output, keep the user-agent string, viewport, locale, timezone, cookies, and wait condition stable between captures. Dynamic content, rotating promotions, font loading, animation, and third-party resources can still make images differ. Save diagnostic details such as the final URL and page errors when a capture is unexpected.
With a self-hosted browser, cost depends on the compute and maintenance needed to run it; the research sources do not provide a benchmark or a universal cost estimate. For ScreenshotNeo, the stated plans are Free: 1,000 shots per month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; and Business: $249 for 1,000,000. Yearly billing gives two months free. Every feature is on every plan. Only clean shots are billed, and response headers indicate verdict and billing status. Check the product documentation for current details.
FAQ
Does changing the user agent make the screenshot mobile?
No. Set the viewport and any required device properties separately, or start from a device preset and adjust it.
Should I use networkidle for every page?
No. Choose a wait condition based on how that page renders. Long-lived requests can prevent network idle, while a specific selector can provide a clearer readiness signal.
Can I capture just one component?
Yes. In Playwright, take a screenshot of a locator; in Puppeteer, use an element handle’s screenshot method.
Will a custom user agent always produce the same result as a real browser or device?
No. The string is only one input, and sites can vary responses based on other browser, device, session, and network properties.


