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

Use the rendering mode that matches your runner. For a machine with working GPU drivers, launch headless Chrome with --enable-gpu. For GPU-less or tightly controlled CI, explicitly select SwiftShader with --use-gl=angle --use-angle=swiftshader-webgl --enable-unsafe-swiftshader. First capture Chrome’s stderr and inspect GPU status; otherwise a missing shared library, unwritable profile, absent Linux display, or an application that starts before WebGL is ready can look like a GPU failure.

Classify the failure before changing flags

WebGL failures in Puppeteer usually belong to one of three layers. Separating them prevents random combinations of Chromium switches:

  • Browser startup: Chrome never launches because a shared library is missing, its profile directory is read-only, or the sandbox cannot initialize.
  • Context creation: Chrome launches, but canvas.getContext('webgl') or webgl2 returns null, often because acceleration is disabled or no usable backend is available.
  • Rendering behavior: A context exists, but frames are blank, incomplete, or very slow because the page starts rendering too early, required extensions are absent, or the selected backend is CPU-only.

Turn on launch diagnostics before editing arguments. Puppeteer’s dumpio option forwards the browser process’s stderr and stdout to your Node process; Chromium logging adds more detail:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    dumpio: true,
    args: [
      '--enable-logging=stderr',
      '--v=1',
      '--enable-gpu'
    ]
  });

  const page = await browser.newPage();
  await page.goto('https://example.com', {waitUntil: 'networkidle2'});
  console.log(await page.title());
  await browser.close();
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Look for messages about missing libraries, sandbox permissions, GPU process crashes, SwiftShader, or an unavailable display. Also inspect Chrome’s GPU diagnostics by opening chrome://gpu in a compatible session and recording the “Graphics Feature Status” and driver problems. Treat that page as evidence about the exact browser binary and host, not as a guarantee that a different container or worker will behave the same way.

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.

Use hardware-backed headless rendering when the runner has a GPU

Puppeteer’s troubleshooting documentation states that chrome-headless-shell requires --enable-gpu for GPU acceleration in headless mode. Chromium documents the same switch as disabling forced software rendering. It does not install a driver or create a display, so the host still needs a usable graphics stack.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--enable-gpu']
  });

  const page = await browser.newPage();
  await page.setViewport({width: 1280, height: 800, deviceScaleFactor: 1});
  await page.goto('https://your-app.example/3d-dashboard', {
    waitUntil: 'networkidle2',
    timeout: 60_000
  });
  await page.waitForSelector('#scene-ready', {timeout: 30_000});
  await page.screenshot({path: 'webgl-hardware.png'});
  await browser.close();
})().catch(error => {
  console.error(error);
  process.exit(1);
});

On Linux, automatic OpenGL driver detection generally expects an X11 server and a valid DISPLAY. A headless worker with no X server can therefore fail even when a physical GPU is attached. Chromium documentation notes that forcing the Vulkan backend with --use-angle=vulkan works on some Linux configurations:

const browser = await puppeteer.launch({
  headless: true,
  args: ['--enable-gpu', '--use-angle=vulkan']
});

Use the Vulkan variant only after checking that the installed driver supports it. Keep the argument list minimal: mixing a hardware request with software-forcing switches makes the result difficult to interpret.

Use explicit SwiftShader on GPU-less CI

SwiftShader is Chromium’s CPU-only implementation of Vulkan and OpenGL ES. It is useful when your CI image has no GPU or when reproducibility matters more than hardware performance. The documented WebGL fallback combination is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: [
      '--use-gl=angle',
      '--use-angle=swiftshader-webgl',
      '--enable-unsafe-swiftshader'
    ]
  });

  const page = await browser.newPage();
  await page.goto('https://your-app.example/3d-dashboard', {
    waitUntil: 'networkidle2',
    timeout: 60_000
  });
  await page.screenshot({path: 'webgl-swiftshader.png'});
  await browser.close();
})().catch(error => {
  console.error(error);
  process.exit(1);
});

--enable-unsafe-swiftshader opts into lower security guarantees. Use it for trusted test content, not as a blanket setting for arbitrary untrusted pages. Chromium’s current documentation requires explicit opt-in while automatic WebGL fallback is being deprecated because of security and user-experience risks.

Do not use --disable-gpu as a universal fix

--disable-gpu prevents hardware acceleration. It conflicts with the goal of GPU-backed headless rendering and does not repair a missing driver, a broken profile, or an application-level WebGL error. Remove it when testing --enable-gpu. If the machine has no usable GPU, choose the explicit SwiftShader configuration instead of combining both modes.

Mode Requirements Trade-off Best fit
Hardware GPU Working drivers; --enable-gpu; Linux OpenGL may need X11 and DISPLAY Faster and closer to production GPU behavior, but sensitive to host configuration GPU-backed CI and server rendering
SwiftShader ANGLE/SwiftShader switches and trusted content CPU rendering; potentially slower and lower security guarantees GPU-less, reproducible CI tests
No WebGL path Application fallback such as Canvas2D or an explanatory message Reduced visual features Sites that must remain usable when WebGL is unavailable

Make the container and Puppeteer installation healthy

Keep the browser build aligned

Since Puppeteer v20, the package downloads Chrome for Testing and supports headless and headful modes on the shared browser code path. Keep the Puppeteer package and its downloaded Chrome for Testing revision together; pointing a newer Puppeteer at an unrelated system Chrome can introduce backend differences.

Check shared libraries

On Linux, find unresolved dependencies before investigating WebGL:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ldd /path/to/chrome | grep not

Install the missing libraries required by your Linux distribution, then rerun the same browser binary. A launch error caused by libX11, font, NSS, or other missing components is not fixed by adding GPU flags.

Give Chrome writable directories

Containers frequently mount the home directory read-only. Chrome must write its profile, cache, and crash files. Set writable locations explicitly and pass a writable user-data directory:

const browser = await puppeteer.launch({
  headless: true,
  userDataDir: '/tmp/puppeteer-profile',
  env: {
    ...process.env,
    XDG_CONFIG_HOME: '/tmp/xdg-config',
    XDG_CACHE_HOME: '/tmp/xdg-cache'
  },
  args: ['--enable-gpu']
});

Create those directories in the image or job before launch and ensure the process user can write them.

Preserve the sandbox whenever possible

Puppeteer strongly discourages --no-sandbox. Fix user-namespace or AppArmor permissions and configure a usable sandbox instead. Only use the switch as a last resort in an isolated, trusted runner after accepting its security implications; it does not solve WebGL initialization itself.

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

Verify WebGL inside the page

Test the context before your renderer starts, and test the extensions your application actually requires. Record vendor and renderer strings as diagnostics only; they are not a promise of a particular GPU.

const result = await page.evaluate(() => {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
  if (!gl) return {ok: false, reason: 'WebGL context unavailable'};

  const debug = gl.getExtension('WEBGL_debug_renderer_info');
  return {
    ok: true,
    version: gl.getParameter(gl.VERSION),
    shadingLanguage: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
    vendor: debug ? gl.getParameter(debug.UNMASKED_VENDOR_WEBGL) : 'hidden',
    renderer: debug ? gl.getParameter(debug.UNMASKED_RENDERER_WEBGL) : 'hidden',
    textureFloat: !!gl.getExtension('OES_texture_float')
  };
});
console.log(result);
if (!result.ok) throw new Error(result.reason);

Use an application-level fallback when the result is negative. Chromium notes that browsers do not guarantee WebGL availability. A Canvas2D view, a static preview, or a clear message explaining the required graphics support is safer than allowing a renderer to fail silently.

Fix headless-only and slow-rendering failures

When headful works but headless fails

  • Compare the exact Chrome for Testing revision, command-line arguments, environment variables, and user-data directory.
  • Check whether headful mode supplied X11 while the headless worker has no DISPLAY.
  • Inspect chrome://gpu and stderr in both modes; a “works on my desktop” result does not prove the CI driver is usable.
  • Choose hardware plus --enable-gpu, or switch deliberately to SwiftShader rather than retaining contradictory flags.

When the context exists but the screenshot is blank

  • Wait for the application’s ready selector or a known render-complete signal, not merely domcontentloaded.
  • Use networkidle2 only when the page’s long-lived connections allow it; otherwise wait for a selector and a short, measured delay.
  • Check WebGL extensions and shader compilation errors in the page, then capture a diagnostic frame before the final screenshot.

When SwiftShader is too slow

Reduce viewport and device scale factor for tests that do not need pixel-level fidelity, avoid unnecessary animations, and wait for one stable frame instead of sleeping for a large fixed interval. For sustained rendering workloads, a GPU-enabled worker may be more appropriate than CPU rendering.

Troubleshooting by symptom

Symptom Likely cause Action
Error creating WebGL context Acceleration disabled, no backend, or required driver unavailable Capture stderr; remove --disable-gpu; try --enable-gpu with a valid driver or the explicit SwiftShader switches
Chrome exits before a page opens Missing shared library, sandbox failure, or unwritable profile Run ldd chrome | grep not, repair permissions, set writable userDataDir/XDG_*, and avoid --no-sandbox unless unavoidable
Hardware mode reports software rendering Forced software flag or unavailable Linux display/backend Remove conflicting switches, verify DISPLAY for OpenGL, and test --use-angle=vulkan only when the driver supports it
WebGL works locally but not in CI Different Chrome revision, drivers, libraries, or container permissions Pin the Puppeteer/Chrome for Testing pair and compare GPU status and environment from the failing worker
Rendering is incomplete or timing out Capture begins before the scene is ready or CPU rendering is overloaded Wait for a readiness selector or render signal, then tune viewport, scale, animation, and worker size
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 reliable website screenshot rather than debugging a browser graphics stack, ScreenshotNeo provides a GET-based screenshot API and an MCP server for AI agents. It removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Claude, Cursor, and other MCP clients can use its take_screenshot, get_page_info, and capture_pdf tools.

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.

One request is enough:

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

See the complete parameter list and options in the ScreenshotNeo documentation. The same endpoint accepts PNG, JPEG, WebP, or PDF output and supports controls such as full-page capture, CSS selectors, waits, custom headers, cookies, user agents, JavaScript, request blocking, dark mode, device presets, and signed links.

Equivalent Python call

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)

Equivalent Node.js call

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(`${res.status} ${await res.text()}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to try it without setting up Chrome, drivers, or a CI display.

FAQ

Does headless mode always disable WebGL?

No. Headless Chrome can use a hardware backend when the host graphics environment is correctly configured and --enable-gpu is supplied. Availability still depends on drivers and the selected Linux backend.

Is SwiftShader the same as a real GPU?

No. SwiftShader renders on the CPU. It is useful for trusted, repeatable tests but can be slower and does not reproduce every hardware-driver behavior.

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

Should I test WebGL or WebGL2?

Test the API your application uses, then check its required extensions. A successful WebGL1 context does not prove that WebGL2 features are available.

Why does adding more Chrome flags make the problem worse?

Flags can request mutually exclusive rendering paths. Start with diagnostics and one deliberate mode—hardware or explicit SwiftShader—then add only a switch justified by the observed environment.

Frequently Asked Questions

Does headless mode always disable WebGL?

No. Headless Chrome can use a hardware backend when the host graphics environment is correctly configured and --enable-gpu is supplied. Availability still depends on drivers and the selected Linux backend.

Is SwiftShader the same as a real GPU?

No. SwiftShader renders on the CPU. It is useful for trusted, repeatable tests but can be slower and does not reproduce every hardware-driver behavior.

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

Should I test WebGL or WebGL2?

Test the API your application uses, then check its required extensions. A successful WebGL1 context does not prove that WebGL2 features are available.

Why does adding more Chrome flags make the problem worse?

Flags can request mutually exclusive rendering paths. Start with diagnostics and one deliberate mode—hardware or explicit SwiftShader—then add only a switch justified by the observed environment.

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.