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 →Inject the stylesheet after navigation and before the screenshot. In Playwright, use page.addStyleTag({ content: cssString }) when the override should remain active in the page, or pass the same string through the screenshot’s style option for a capture-only change. Wait for the application, fonts, images, and a rendering frame before saving pixels; otherwise the CSS may arrive before the elements it targets or before the layout settles.
Playwright: inject a CSS string before a screenshot
This complete example hides consent and chat UI, freezes motion, waits for fonts, and captures the full document.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const cssString = `
.cookie-banner, .chat-widget { display: none !important; }
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
`;
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.addStyleTag({ content: cssString });
await page.evaluate(() => document.fonts.ready);
await page.evaluate(() => new Promise(requestAnimationFrame));
await page.screenshot({ path: 'capture.png', fullPage: true });
await browser.close();
addStyleTag adds a <style type="text/css"> element containing your string (or a linked stylesheet when given a URL) and resolves after injection into the frame. Because it is a normal document style, you can inspect it in DevTools, measure the resulting layout, and remove it later.
Use the capture-only style option
For a one-off screenshot, avoid mutating the page by applying the stylesheet only while Playwright renders the image:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems#1 Best Overall
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
const cssString = `
.cookie-banner, .chat-widget { display: none !important; }
* { animation: none !important; transition: none !important; }
`;
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
path: 'capture.png',
fullPage: true,
style: cssString
});
The screenshot style value is the text of a stylesheet applied while making the screenshot. It is designed for repeatable capture overrides such as hiding dynamic elements. Playwright documents this mode as piercing Shadow DOM and applying to inner frames, which makes it broader than inserting a style element in only the top-level document.
Choose between persistent and capture-scoped CSS
| Need | Best choice | Reason |
|---|---|---|
| Inspect, measure, or take several captures with the override | addStyleTag |
The style remains in the document until you remove it. |
| Change pixels for exactly one screenshot | Screenshot style |
No lasting DOM mutation; the rule is scoped to capture. |
| Support both Playwright and Puppeteer | addStyleTag({ content }) |
Both libraries expose this API. |
| Reach Shadow DOM and inner frames in Playwright | Screenshot style |
Playwright documents that coverage for this option. |
How to hide an element only in a screenshot
Target a stable class, ID, or attribute and use display: none !important when removing the node’s layout space is intended. Use visibility: hidden when you need to preserve its dimensions, or opacity: 0 when it must still participate in layout and hit testing. Keep selectors narrow: a broad rule such as div { display:none } can destroy the page structure.
const cssString = `
[data-testid="newsletter-modal"],
.cookie-banner,
.support-chat { display: none !important; }
header .promo { visibility: hidden !important; }
`;
If the site’s own selector wins the cascade, add !important to the capture rule. If a component is rendered later, wait for that component first, then inject:
Rank #2
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-testid="app-ready"]');
await page.addStyleTag({ content: cssString });
Why injected CSS did not affect an iframe
A top-level stylesheet does not automatically rewrite a separately loaded cross-origin iframe. Obtain the frame and inject in that frame’s context when browser security permits access:
const frame = page.frame({ name: 'report' });
if (!frame) throw new Error('report frame not found');
await frame.addStyleTag({ content: cssString });
For a same-origin frame without a name, select it by URL or inspect page.frames(). A cross-origin frame may reject DOM access; in that case you cannot use page-side JavaScript to alter its document. The Playwright screenshot style option is the preferable capture-only attempt because it is documented to reach inner frames, but the frame must still be available to the browser and fully loaded.
Timing that makes CSS captures deterministic
- Navigate first. Use
waitUntil: 'networkidle'for pages that settle cleanly, or use'domcontentloaded'plus an application-specific ready selector when analytics keep connections open. - Wait for the target nodes. Injecting before a client-rendered banner exists does not guarantee the later component will use the intended cascade; wait for its selector or your app’s ready signal.
- Freeze motion. Disable animations and transitions in the injected rules. Also consider hiding blinking cursors and caret colors.
- Wait for fonts. Run
await page.evaluate(() => document.fonts.ready); otherwise fallback fonts can change line breaks after capture. - Wait for important images. For application-controlled assets, await the app’s image-ready promise or verify
img.completeandimg.naturalWidth. CSS injection itself does not wait for images. - Allow a paint. If the rules change layout, await
new Promise(requestAnimationFrame)after injection so style and geometry are committed. - Select the capture area deliberately. Use
fullPage: truefor the document’s complete height, the default viewport for a screen-sized image, or an element locator’s screenshot for one component.
Puppeteer equivalent
Puppeteer supports the same persistent style-tag approach:
Rank #3
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const cssString = `
.cookie-banner, .chat-widget { display: none !important; }
* { animation: none !important; transition: none !important; }
`;
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.addStyleTag({ content: cssString });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'capture.png', fullPage: true });
await browser.close();
When you need custom logic, insert the element yourself. page.evaluate runs in the page context and waits for a returned promise:
await page.evaluate((css) => {
const style = document.createElement('style');
style.setAttribute('data-capture-override', 'true');
style.textContent = css;
(document.head || document.documentElement).appendChild(style);
}, cssString);
The manual method is useful when you need to tag, replace, or remove the style node. It follows normal document and frame boundaries, so execute it through the relevant frame for iframe content.
Advanced CSS override patterns
Change colors or layout for a report
const cssString = `
body { background: #fff !important; color: #111 !important; }
.sidebar { display: none !important; }
.main { max-width: 100% !important; margin: 0 !important; }
`;
Make a transparent capture
Set the page background to transparent only when the browser and output format support an alpha channel, such as PNG or WebP:
Rank #4
await page.screenshot({
path: 'transparent.png',
style: 'html, body { background: transparent !important; }'
});
Remove a persistent override after a batch
await page.addStyleTag({ content: cssString });
// ...capture several states...
await page.locator('style[data-capture-override]').evaluate(el => el.remove());
Give the injected element a marker yourself if you need this cleanup pattern, or retain the handle returned by addStyleTag and dispose of it after the final capture.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Nothing is hidden | Selector is wrong or the component appears later. | Inspect the live DOM, wait for the component selector, then inject; use !important only if the cascade requires it. |
| Layout shifts between runs | Fonts, images, lazy content, or animations are unfinished. | Await document.fonts.ready, application image readiness, a rendering frame, and disable motion. |
| Iframe remains unchanged | The rule was inserted in the parent document or the frame is cross-origin. | Use page.frame(...).addStyleTag when permitted; otherwise the browser’s same-origin policy prevents DOM injection. |
| Page never reaches network idle | Long-lived analytics or WebSocket requests. | Use domcontentloaded and wait for a concrete ready selector instead of waiting for every connection to close. |
| Hidden element leaves a blank gap | visibility: hidden or opacity: 0 preserves layout. |
Use display: none !important when the space should collapse. |
| Screenshot cuts off content | Full-page height changed after capture measurement, or the viewport was intended. | Wait one frame after injection and choose fullPage: true only for a document capture. |
| Override leaks into a later screenshot | A persistent style tag was not removed. | Use capture-scoped style, or remove the tagged style between states. |
Performance, reliability, and security notes
- Prefer one stylesheet string over many individual DOM mutations; it reduces script round trips and makes the capture reproducible.
- Keep selectors efficient and specific. Very broad selectors increase style recalculation and can accidentally alter widgets that your test needs.
- Reuse a browser process for a batch, but create an isolated page or context when cookies, headers, or CSS state must not leak between URLs.
- Do not put secrets in CSS. The string executes in the page you are visiting, so treat navigation targets and injected JavaScript as untrusted input.
- CSS cannot bypass a consent dialog that blocks the underlying page. Handle the dialog as a visitor, or wait for the application state that reveals the content before styling it away.
- For visual regression, fix viewport, device scale, timezone, locale, reduced-motion behavior, and font availability in addition to the stylesheet.
Or skip the browser setup
ScreenshotNeo provides a single HTTP request for a clean PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners as a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
For a screenshot with an injected CSS string, use the custom CSS option documented at ScreenshotNeo’s API documentation (the API accepts the parameter names used by other screenshot services, which eases migration). The basic request pattern is:
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://example.com
--data-urlencode css='.cookie-banner,.chat-widget{display:none!important} *{animation:none!important;transition:none!important}'
-o shot.webp
The same endpoint can be called from Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com",
"css": ".cookie-banner,.chat-widget{display:none!important} *{animation:none!important;transition:none!important}",
},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Or Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
css: '.cookie-banner,.chat-widget{display:none!important} *{animation:none!important;transition:none!important}'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
await Bun.write('shot.webp', res);
ScreenshotNeo also supports full-page lazy-image loading, element selectors, dark mode, 12 device presets and arbitrary viewports, retina scale, custom JavaScript, clicks, hidden selectors, waits for selectors or network idle, request/resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification.
Best Value
The Free plan includes 1,000 screenshots per month without a card. Paid plans are 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, and every feature is available on every plan. Create a free ScreenshotNeo account to start with those 1,000 monthly screenshots.
FAQ
Does addStyleTag wait for a page to finish rendering?
No. It waits for the CSS content to be injected into the frame, not for fonts, images, lazy components, or application rendering. Add those waits explicitly.
Can I inject CSS into a cross-origin iframe?
Normally no. Browser same-origin policy blocks DOM access. You can inject through a Playwright frame only when the browser context permits access.
Recommended Free Tools
Should I use display:none or visibility:hidden?
Use display:none to remove layout space; use visibility:hidden to preserve geometry while hiding pixels.
Is capture-scoped styling available in Puppeteer?
The documented screenshot style option is a Playwright feature. In Puppeteer, insert a temporary style element with addStyleTag or evaluate, capture, then remove it.
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.




