What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Playwright does not add the browser’s address bar to an image produced by page.screenshot(). To make the URL visible, read the final address with page.url(), render that value as an overlay inside the page, and then capture the screenshot. If you only need to identify the file, store the URL in a filename or metadata record instead. Playwright’s documented screenshot options cover viewport, full-page, element and buffer captures, not browser-window chrome (screenshots guide; Page API).
What Playwright can—and cannot—capture
page.screenshot() captures rendered web content in the page. It does not capture the operating-system window, browser tabs or address bar. A full-page screenshot means the full scrollable page is rendered as though it were very tall; it changes the captured area but does not add browser interface elements (official screenshots guide).
Therefore, “include the URL” has two different solutions:
- Visible URL: inject a label into the document before the screenshot.
- Non-visible identification: write
page.url()to a log, metadata store or filename beside the image.
Use page.url() after navigation and redirects. It represents the address currently loaded, which may differ from the URL you originally requested.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall#1 Best Overall
Inject a URL overlay before taking the screenshot
The following Node.js example launches Chromium, navigates to a page, adds a fixed URL bar, captures a PNG and removes the bar. The marker makes repeated runs safe by replacing an existing overlay rather than creating duplicates.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1440, height: 900 }
});
await page.goto('https://example.com', { waitUntil: 'networkidle' });
const url = page.url();
await page.evaluate((url) => {
document.getElementById('__playwright_url_overlay')?.remove();
const label = document.createElement('div');
label.id = '__playwright_url_overlay';
label.textContent = url;
Object.assign(label.style, {
position: 'fixed',
top: '0',
left: '0',
right: '0',
zIndex: '2147483647',
boxSizing: 'border-box',
padding: '8px 12px',
background: '#fff',
color: '#111',
font: '14px sans-serif',
lineHeight: '20px',
overflowWrap: 'anywhere',
boxShadow: '0 1px 4px #0004'
});
document.body.appendChild(label);
}, url);
await page.screenshot({ path: 'screenshot.png', fullPage: true });
// Keep later captures unmodified.
await page.evaluate(() => {
document.getElementById('__playwright_url_overlay')?.remove();
});
await browser.close();
Install Playwright with npm install playwright and install the browser binaries with npx playwright install chromium when your project does not already provide them. Save the script as an ES module (for example, shot.mjs) and run node shot.mjs.
Overlay versus a content banner
position: fixed keeps the label at the top of the viewport. On a full-page capture, Playwright stitches the page; the fixed element can appear at the top of the resulting image, but exact behavior can vary with page layout and the installed Playwright version. If you want the URL to consume layout space instead, use position: static (or insert the label as the first child of body) so the page moves down. That is less likely to cover content, but it changes the page’s visual layout.
For a viewport-only image, omit fullPage: true. For one component, locate it and call locator.screenshot(); the overlay must be inside the element being captured if the URL should appear in that element image.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesControl long and sensitive URLs
Long query strings wrap because of overflowWrap: anywhere. You can instead display a shortened label while preserving the complete URL in a sidecar log. Do not expose tokens, session identifiers or personal data in an image: URLs can contain credentials or sensitive query parameters. A safer pattern is to redact before setting textContent and retain the unredacted value only in an access-controlled log.
Rank #2
Make the overlay reusable
Put the injection in a helper so every capture uses identical styling and cleanup:
async function addUrlLabel(page, {
selector = '#__playwright_url_overlay',
background = '#fff',
color = '#111'
} = {}) {
const url = page.url();
await page.evaluate(({ url, selector, background, color }) => {
document.querySelector(selector)?.remove();
const label = document.createElement('div');
label.id = selector.startsWith('#') ? selector.slice(1) : '__playwright_url_overlay';
label.textContent = url;
Object.assign(label.style, {
position: 'fixed', top: '0', left: '0', right: '0',
zIndex: '2147483647', boxSizing: 'border-box', padding: '8px 12px',
background, color, font: '14px sans-serif', overflowWrap: 'anywhere'
});
document.body.appendChild(label);
}, { url, selector, background, color });
}
await addUrlLabel(page);
await page.screenshot({ path: 'labeled.png' });
await page.evaluate(() => document.getElementById('__playwright_url_overlay')?.remove());
Using textContent rather than innerHTML treats the URL as text, so characters in a query string cannot become markup. The very high z-index helps the label sit above ordinary site elements, although a page can still create unusual stacking contexts.
Capture the URL without changing the image
If the requirement is traceability rather than a visible label, keep the screenshot clean and write a sidecar record:
import { writeFile } from 'node:fs/promises';
const requested = 'https://example.com';
await page.goto(requested, { waitUntil: 'domcontentloaded' });
const finalUrl = page.url();
await page.screenshot({ path: 'example.png', type: 'png' });
await writeFile('example.json', JSON.stringify({ requested, finalUrl }, null, 2));
This preserves the exact final URL after redirects while leaving page pixels untouched. You can also include a sanitized URL in a filename, but filesystem limits and special characters make a JSON or database record more reliable.
Choose the right capture mode
| Need | Playwright approach | URL-label consideration |
|---|---|---|
| Visible browser-sized image | page.screenshot({ path }) |
Fixed overlay appears in the viewport. |
| Entire scrollable page | page.screenshot({ fullPage: true }) |
Plan whether the label overlays the top or pushes content down. |
| One component | locator.screenshot({ path }) |
Inject the label inside the captured element, or use sidecar metadata. |
| In-memory processing | page.screenshot({ type: 'png' }) returns a buffer |
Store page.url() with the buffer in your own record. |
| Printable document | page.pdf() |
Use the documented PDF header/footer templates instead of an image overlay. |
Use a PDF header when an image is not required
Playwright’s Page API documents displayHeaderFooter for PDFs. PDF header and footer templates provide a url class that prints the document location. This is a separate output path from screenshots: template scripts are not evaluated and page styles are not visible inside the templates (Page API reference).
Rank #3
const finalUrl = page.url();
await page.pdf({
path: 'page.pdf',
format: 'A4',
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:9px;width:100%;padding:0 12px"><span class="url"></span></div>',
footerTemplate: '<div style="font-size:9px;width:100%;padding:0 12px;text-align:right">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
margin: { top: '32px', bottom: '32px' }
});
Use this when a URL on every printed page is preferable to modifying screenshot pixels. If you need both a PDF and an image, perform the PDF operation separately and keep the overlay helper for the image.
Timing, redirects and dynamic pages
Read the URL at the correct point
Call page.url() after the navigation you want to document. If a login flow, canonical redirect or client-side route change occurs afterward, wait for that transition and read the URL again immediately before injection. A URL captured too early can identify a different state than the pixels.
Wait for the content that matters
waitUntil: 'networkidle' can be useful for relatively quiet pages, but applications with analytics, polling or WebSockets may never become idle. Prefer a specific readiness condition when possible:
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="dashboard-ready"]').waitFor({ state: 'visible' });
const url = page.url();
After injecting the overlay, take the screenshot in the same task. If a framework re-renders body, it may remove your label; inject it after the final render or use a page-level hook that runs at the required point.
Lazy-loaded content and full-page captures
Full-page capture can expose content that was not visible in the initial viewport. Scroll or wait for the site’s own lazy-load trigger before adding the URL label. Otherwise the screenshot can contain incomplete images even though the URL is correct.
Common failures and fixes
- The address bar is missing. This is expected:
page.screenshot()is a page capture, not a browser-window capture. Inject a label or use a separate desktop/window capture tool. - The label is not visible. Confirm that the injection runs after
page.goto(), thatdocument.bodyexists, and that you are capturing the same page or locator into which you inserted it. Check the generated image format and path. - The label is behind a modal or header. Use a high
z-index, fixed positioning and a solid background. Site-specific stacking contexts can still require inserting the label nearer the top-level document. - The URL appears twice. Remove an earlier element by a stable ID before appending. The helper above is idempotent.
- The URL is stale. Read
page.url()immediately before injection, after redirects and route changes have settled. - Full-page output covers content. Change the label to normal document flow, add top padding to the page, or place the URL in a sidecar record instead of overlaying it.
- Characters look wrong. Set an explicit font and ensure the page is captured after fonts load. Keep the URL in
textContent; do not build HTML from it. - Navigation times out. Increase the navigation timeout only when justified, wait for a specific selector instead of network idle, and record the failed URL and error for retry handling.
- Secrets leak into screenshots. Redact credentials and sensitive query parameters before display, and avoid storing unprotected images.
Performance, reliability and repeatable output
Launching a browser for every URL is expensive. Reuse one browser process and create isolated contexts or pages for batches. Set a fixed viewport, color scheme, locale, timezone and device scale factor when visual consistency matters. Use PNG for lossless text and UI, JPEG or WebP when smaller files are more important.
Free tools Windows power users keep installed
One-click scans. No signup required.
Keep navigation, readiness, overlay injection and capture in one recorded job. Save the requested URL, final page.url(), timestamp, viewport and capture options alongside the artifact. For retries, create a fresh page or context after a failed navigation; do not assume a partially loaded document is valid. Treat bot checks, authentication walls and blank responses as distinct outcomes rather than silently storing them as successful screenshots.
When many pages share a template, centralize the overlay CSS and use a versioned helper. That prevents a style change from making historical images difficult to compare. Remove the overlay after each capture if the same page will produce an unlabeled image later.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server if you would rather make one request than maintain Playwright browser code. It can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and whether the request was billed.
Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. Every plan includes features such as full-page capture, CSS-selector element shots, custom CSS and JavaScript, waits, request blocking, cookies and headers, device presets, PDFs and signed links.
Recommended Free Tools
For a direct image request, see the ScreenshotNeo API documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
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 failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
The Free plan includes 1,000 screenshots each month without a card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it.
Practical decision checklist
- Need the URL printed in pixels? Inject a fixed or flow-based label immediately before
page.screenshot(). - Need an untouched image with auditability? Store
page.url()beside the file. - Need a URL on every printed page? Generate a PDF with
displayHeaderFooterand the documentedurltemplate class. - Need stable comparisons? Fix viewport, device scale, locale, timezone, readiness conditions and overlay styling.
- Need high-volume or low-maintenance capture? Use an API such as ScreenshotNeo and inspect its verdict and billing headers.
Frequently Asked Questions
Does page.screenshot() capture the browser address bar?
No. It captures rendered page content. Add the URL as page content or record it separately.
Which URL should I display after a redirect?
Read page.url() after the redirect and after any client-side route change you want the screenshot to represent.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Can I add a URL to an element screenshot?
Yes, but the label must be inside the element being captured. Otherwise keep the element image clean and store the URL as metadata.
Is a PDF URL header the same as a screenshot overlay?
No. PDF headers are a separate Page API feature and can repeat on printed pages; they do not add a header to screenshot pixels.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




