Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

The documented Puppeteer method is page.addStyleTag(), not setStyleTag(). Use { path: ... } for a local stylesheet, or { content: ... } for CSS you already have in memory. When a path fails, verify the Node process working directory, the resolved filename, the file contents, and the frame receiving the style.

Use the documented method name first

Puppeteer’s Page API documents addStyleTag(options). The Page method is a shortcut for adding the style to the page’s main frame. A call using setStyleTag will not work as written because that is not the documented Page method.

The API supports two different inputs:

  • path: load CSS from a local file.
  • content: inject CSS text that is already available as a string.

Conceptually, Puppeteer adds either a <link rel="stylesheet"> element for a stylesheet URL or a <style type="text/css"> element for CSS content. A failure involving a filename is therefore a different problem from invalid CSS or styling the wrong document frame.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Minimal working Puppeteer example

This example creates a browser, opens a page, resolves a stylesheet to an absolute filename, checks that it is readable, and injects it into the main document.

const puppeteer = require('puppeteer');
const path = require('node:path');
const fs = require('node:fs/promises');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const cssPath = path.resolve(process.cwd(), 'styles/site.css');
    await fs.access(cssPath); // fails early if the file cannot be read
    await page.addStyleTag({ path: cssPath });

    await page.screenshot({ path: 'styled-page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Run the script from the project directory that contains styles/site.css, or change the path to match your layout. Keeping the path in a variable makes it easy to log and inspect when a capture fails.

Choose the input form that matches your CSS

Load a local file with path

Use path when the stylesheet exists on the machine running Node. Prefer an absolute path while diagnosing a failure:

const path = require('node:path');
const cssPath = path.resolve('/srv/render-app', 'assets', 'print.css');
await page.addStyleTag({ path: cssPath });

Do not pass CSS text as the value of path; Puppeteer will treat that value as a filename. Likewise, a local filename is not the same thing as a web URL.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Inject CSS text with content

If your application already read the stylesheet, bypass path handling entirely:

const fs = require('node:fs/promises');
const css = await fs.readFile('./styles/site.css', 'utf8');
await page.addStyleTag({ content: css });

This is also a useful controlled comparison. If content works while path fails, investigate filename resolution, permissions, or file access rather than the browser’s ability to apply CSS. Success with inline content does not prove that every rule is valid; it only isolates the file-loading part of the diagnosis.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Resolve the path from the Node process

Relative paths are evaluated in the context of the process that runs your script, not necessarily relative to the JavaScript file you are viewing in your editor. The official Puppeteer path note found for script injection states that a relative path resolves from process.cwd(); use that as a diagnostic clue for CSS paths, while checking your installed version’s API behavior.

  1. Print the working directory:
    console.log('cwd:', process.cwd());
  2. Print the exact filename:
    console.log('cssPath:', cssPath);
  3. Check existence and type:
    const stat = await fs.stat(cssPath);
    console.log({ isFile: stat.isFile(), bytes: stat.size });
  4. Check spelling and case. A path that works on a case-insensitive development machine can fail on a case-sensitive Linux runner.
  5. Check the process user. A container or service account may not have permission to read a file that your interactive account can open.

Using path.resolve() removes ambiguity about the current directory. Once the call works, you can decide whether a relative path is safe for your deployment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make sure the style is added to the right frame

page.addStyleTag() targets the main frame. If the elements you want to style are inside an iframe, adding CSS to the top-level page will not modify the iframe’s document.

const frames = page.frames();
const widgetFrame = frames.find(frame => frame.url().includes('/widget'));

if (!widgetFrame) {
  throw new Error('Widget iframe was not found');
}

await widgetFrame.addStyleTag({ path: cssPath });

Identify the frame using a stable URL or another property available in your page, and wait until the iframe has loaded before injecting the style. Cross-origin restrictions still apply to browser scripting: Puppeteer can operate on a frame it controls, but a selector in the parent document cannot reach into a separate iframe document.

Validate the stylesheet and injection timing

Confirm that the file is really CSS

A successful file lookup does not guarantee useful styling. Inspect the first bytes or print a short preview:

const css = await fs.readFile(cssPath, 'utf8');
console.log(css.slice(0, 200));
await page.addStyleTag({ content: css });

An HTML error page, an empty response saved with a .css extension, or a build artifact containing unexpected markup can all produce a page that appears unstyled. Check braces, selectors, and syntax in the CSS itself. A path error and a CSS parsing or cascade issue should be fixed separately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Inject after navigation and before capture

Navigate first, then call addStyleTag, then capture. If a single-page application replaces the document after your injection, add the style again after the route change or wait for the application’s stable selector before injecting. If the stylesheet depends on elements that are added later, wait for those elements before taking the screenshot; the style element can exist even while the target markup is not present.

Check the cascade

Use a deliberately visible diagnostic rule to distinguish “not injected” from “overridden”:

await page.addStyleTag({
  content: 'body { outline: 8px solid magenta !important; }'
});

If the outline appears, the injection worked. Remove the test rule and inspect selector specificity, !important usage, shadow DOM boundaries, and later stylesheets when the intended rule has no visible effect.

Common path errors and fixes

Symptom Likely cause What to do
page.setStyleTag is not a function Undocumented or incorrect method name. Change the call to page.addStyleTag(options).
ENOENT, “file not found,” or a similar filesystem message The resolved filename is wrong, the working directory differs, or the file was not copied into the runtime image. Log process.cwd() and the absolute path, call fs.access(), and verify the deployment includes the file.
Works locally but fails in CI or a container Different working directory, case-sensitive filesystem, missing asset, or unreadable permissions. Use path.resolve(), package the CSS explicitly, check filename case, and test access as the same user that runs Puppeteer.
The call completes but nothing changes Wrong frame, CSS overridden, selectors do not match, or the page later navigated. Use a visible diagnostic rule, inspect the target frame, wait for navigation or route completion, and check the cascade.
Inline content works but path does not The browser can apply the CSS; local file resolution or reading is the remaining suspect. Keep the absolute path, inspect permissions and file contents, and remove path construction assumptions one at a time.
Only part of the page is styled Some elements are in an iframe or shadow DOM, or the selector is too narrow. Inject into each required frame and inspect component boundaries; ordinary document CSS does not automatically cross those boundaries.
Styles disappear after a click or route change The application replaced the document or mounted a new frame. Wait for the new state, locate the current frame, and inject the stylesheet after the transition.

Keep the complete thrown exception, the resolved path, the working directory, and the URL of the target frame in your logs. The title alone cannot identify one universal root cause, so those details are more useful than changing several settings at once.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • 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

Reliable patterns for repeated captures

Read once, reuse when appropriate

For a batch of pages that use the same CSS, read the file once and pass the string as content to each new page. This avoids repeated filesystem lookups and makes the input deterministic. If pages require different builds, keep separate CSS strings and label them in your logs.

Keep capture order explicit

Use this order for each page: create or reuse a page, navigate, wait for the required application state, inject CSS, verify a known selector or diagnostic rule when debugging, and capture. Do not hide errors by catching and ignoring a failed addStyleTag promise.

Separate browser failures from file failures

A browser installation or launch problem occurs before a stylesheet can be injected. Puppeteer’s general troubleshooting guidance covers browser installation and runtime issues, but those should not be assumed to explain a path failure. First prove that the browser launches and the page loads; then isolate filesystem access, frame selection, and CSS behavior.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean image or PDF rather than running Puppeteer yourself, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for all options. The simplest call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in 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)

And in 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 buffer = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', buffer);

ScreenshotNeo also exposes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It supports full-page and element captures, device presets, custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, wait conditions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to try it without adding a card.

Frequently Asked Questions

Can I use a stylesheet URL instead of a local path?

Yes. Use the API input intended for a stylesheet URL when the CSS is hosted remotely; use path only for a file available to the Node process, and use content for CSS text.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does a successful addStyleTag call still produce an unchanged screenshot?

Check the target frame, selector matching, cascade and timing. A style element can be present while the desired elements are in an iframe, mounted later, or overridden by more specific rules.

Should I keep using a relative path after fixing the error?

Only if your launcher always sets the same working directory and packages the asset in the same location. An absolute path built with path.resolve() is safer for CI, containers and service processes.

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.