Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsUse 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')orwebgl2returnsnull, 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.
#1 Best Overall
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:
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:
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:
Rank #3
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.
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://gpuand 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
networkidle2only 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 |
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.
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.
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.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →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.
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.

