Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
Chromium

How to Capture WebGL Pages with Puppeteer

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

Use Puppeteer’s Page.screenshot() to capture a WebGL page after its canvas has initialized and the scene has rendered a frame. Set the viewport before navigating, wait for an application-specific ready signal where possible, then capture the viewport, full page, or canvas element. A successful page load alone is not proof that WebGL is ready: shaders, textures, fonts, and animation can still be loading.

What you need before capturing a WebGL page

You need a Puppeteer script, the page URL, and a Chromium environment that can create a WebGL context. The browser may use hardware acceleration when the local machine and configuration support it; otherwise, a software renderer such as SwiftShader may be appropriate for controlled testing. Neither path guarantees that every page can create a WebGL context. The page’s own app code, browser environment, graphics support, and timing all matter.

For a reproducible still image, decide in advance which canvas or scene you want, the viewport size, whether the capture should include the full document, and what “ready” means for that particular app. A generic canvas check is a useful baseline, but the page’s own signal—such as a promise, a scene-loaded flag, or a rendered-frame counter—is more reliable.

Install Puppeteer and prepare the target

In a Node.js project, install Puppeteer with npm install puppeteer. Use an authorized page that is accessible to the browser process. If the page requires authentication, configure that in your test environment rather than putting credentials in source code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
GIGABYTE Radeon RX 9070 XT Gaming OC 16G Graphics Card, PCIe 5.0, 16GB GDDR6, GV-R9070XTGAMING OC-16GD Video Card
  • Powered by Radeon RX 9070 XT
  • WINDFORCE Cooling System
  • Hawk Fan
  • Server-grade Thermal Conductive Gel
  • RGB Lighting

Use a fixed viewport and device scale factor to make the output dimensions predictable. Set these before navigation so the site lays out at the intended size from the start. If the application resizes its canvas in response to viewport changes, do not change the viewport after checking readiness unless you repeat the readiness wait.

A runnable Puppeteer script for a WebGL screenshot

This baseline script opens a page, waits for a canvas with a WebGL or WebGL 2 context, lets two animation frames pass, and saves a viewport screenshot. Replace the URL and, if necessary, the canvas selector. Two animation frames are only a timing aid; for a deterministic capture, replace or extend that wait with the application’s own readiness condition.

import puppeteer from 'puppeteer';

const url = 'https://example.com/webgl-demo';
const outputPath = 'webgl.png';
const canvasSelector = 'canvas';

const browser = await puppeteer.launch({
  headless: true,
  args: process.env.CI ? ['--enable-gpu'] : []
});

try {
  const page = await browser.newPage();
  await page.setViewport({
    width: 1280,
    height: 720,
    deviceScaleFactor: 1
  });

  await page.goto(url, { waitUntil: 'networkidle2' });

  await page.waitForFunction((selector) => {
    const canvas = document.querySelector(selector);
    if (!canvas || canvas.width === 0 || canvas.height === 0) return false;
    const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
    return Boolean(gl);
  }, { timeout: 30000 }, canvasSelector);

  // Allow the page to pass through two animation-frame callbacks.
  await page.evaluate(() => new Promise((resolve) => {
    requestAnimationFrame(() => requestAnimationFrame(resolve));
  }));

  await page.screenshot({ path: outputPath });
  console.log(`Saved ${outputPath}`);
} finally {
  await browser.close();
}

The script deliberately uses networkidle2 as a navigation baseline, not as a WebGL-ready guarantee. A page can be network-idle while a shader is compiling, a texture is being decoded, or the first useful frame has not yet been drawn. Conversely, pages that keep network requests open may not reach network idle. In those cases, choose a navigation wait condition appropriate for the site and rely on an explicit app-level readiness check.

Prefer an application-ready signal

If you control the WebGL application, expose a signal only after the scene is ready to capture. For example, set window.__webglReady = true after required assets have loaded and the first render has completed. Puppeteer can then wait for that exact condition:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
GIGABYTE GeForce RTX 5070 Ti Gaming OC 16G Graphics Card, 16GB 256-bit GDDR7, PCIe 5.0, WINDFORCE Cooling System, GV-N507TGAMING OC-16GD Video Card
  • Powered by the NVIDIA Blackwell architecture and DLSS 4
  • Powered by GeForce RTX 5070 Ti
  • Integrated with 16GB GDDR7 256bit memory interface
  • PCIe 5.0
  • WINDFORCE cooling system
await page.waitForFunction(() => window.__webglReady === true, {
  timeout: 30000
});

Another useful signal is a frame counter that increments after rendering. Wait until it advances past a known value rather than assuming that a canvas with nonzero dimensions contains a useful image. If the app cannot be changed, combine the context check with the most reliable observable condition available on the page, such as a scene-specific element or known loaded-state marker.

Calling canvas.getContext() is a context check, but canvas contexts are tied to their canvas and context type. Target the actual canvas used by the renderer; avoid testing a broad selector when the page contains multiple canvases. If application code has already created a context, querying the same type can return that context. A missing or zero-sized canvas, or a null context, means the script should not treat the page as ready.

Choose the capture area and output format

page.screenshot() is Puppeteer’s direct still-image capture API. The basic call captures the current viewport. Use the options that match what the reader or test needs; a canvas-only image and a full-page screenshot answer different questions.

Need Capture approach Important detail
Visible browser area await page.screenshot({ path: 'webgl.png' }) Captures the viewport at the configured dimensions.
Entire document await page.screenshot({ path: 'webgl-full.png', fullPage: true }) Captures beyond the initial viewport; it does not make an app render or load content that has not become available.
One canvas element Take an element screenshot of the selected canvas. Useful when the page has controls or surrounding layout you do not want in the image. Select the renderer’s actual canvas.
A particular rectangle Use the screenshot clip option with an x, y, width, and height. Coordinates describe the desired page region; verify them against the viewport and device scale used by the capture.
PDF document Use page.pdf(). PDF output uses print CSS by default. Emulate screen media if you need screen layout instead.
Moving scene Use Puppeteer’s screencast capability. This records motion rather than producing one still image and has additional runtime requirements.

For an element screenshot, wait for the page first, then locate the exact element and capture it. For example, with a canvas selector that you have verified:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
ASUS TUF Gaming GeForce RTX™ 5080 16GB GDDR7 OC Edition Graphics Card
  • Powered by the NVIDIA Blackwell architecture and DLSS 4. System Requirements: Minimum 850W PSU with 16-pin 12V-2x6 (12VHPWR) connector required. Verify before purchasing.
  • Military-grade components deliver rock-solid power and longer lifespan for ultimate durability. Compatibility: 348mm (13.7") length, 3.6 slots, 4.3 lbs. Confirm case clearance and slot spacing. GPU bracket included.
  • Protective PCB coating helps protect against short circuits caused by moisture, dust, or debris
  • 3.6-slot design with massive fin array optimized for airflow from three Axial-tech fans
  • Phase-change GPU thermal pad helps ensure optimal thermal performance and longevity, outlasting traditional thermal paste for graphics cards under heavy loads
const canvas = await page.$('#scene canvas');
if (!canvas) throw new Error('WebGL canvas not found');
await canvas.screenshot({ path: 'canvas.png' });

For a clipped region, use a rectangle in the screenshot options:

await page.screenshot({
  path: 'webgl-region.png',
  clip: { x: 100, y: 80, width: 800, height: 500 }
});

Puppeteer documents screenshot options including path, type, encoding, fullPage, clip, and captureBeyondViewport. Choose an image type that suits the downstream use; PNG is a straightforward choice for a lossless still. Keep the viewport and clip dimensions consistent with the element’s actual location and size.

Make the captured frame deterministic

A screenshot of an animated WebGL scene records the state at capture time. If the camera, particles, shader uniforms, or other scene elements move continuously, repeated runs can produce different images even when the URL and viewport are unchanged.

  • For visual regression tests: pause animation or expose a test mode that renders a known state. Capture only after the chosen state has been drawn.
  • For a specific moment: trigger a known render tick, then capture after the renderer has completed it. A delay alone can drift with machine speed and load.
  • For texture-dependent scenes: wait for the application’s texture or asset promises, not merely for the HTML document to load.
  • For text in the scene or UI: wait for document.fonts.ready when web fonts affect the result, and still use the app’s own ready signal for WebGL assets.
  • For responsive canvases: set the viewport first and avoid resizing between the readiness check and screenshot.

These are application-level synchronization choices: Puppeteer’s screenshot API captures what Chromium has rendered, but there is no universal event that means every WebGL app has finished its first useful frame.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
ASUS Dual GeForce RTX 5060 Ti 16GB GDDR7 OC Edition Gaming Graphics Card
  • AI Performance: 767 AI TOPS
  • OC mode: 2632 MHz (OC mode)/ 2602 MHz (Default mode)
  • Powered by the NVIDIA Blackwell architecture and DLSS 4
  • Axial-tech fan design features a smaller fan hub that facilitates longer blades and a barrier ring that increases downward air pressure
  • A 2.5-slot design maximizes compatibility and cooling efficiency for superior performance in small chassis

Choose a renderer for headless Chromium

Headless Chromium may use a local GPU in some circumstances, but GPU availability depends on the machine and browser environment. Chromium’s headless documentation says it can utilize the local machine’s GPU “at least in some circumstances.” The baseline script includes --enable-gpu only when CI is set, matching a common attempt to enable GPU use in an environment configured to support it. That switch is not a guarantee that a usable hardware GPU is present.

Chromium uses SwiftShader by default in headless mode. If the machine has no supported GPU or the normal path cannot provide WebGL, Chromium documents this explicit SwiftShader configuration:

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

Use this deliberately in controlled test environments. Chromium describes SwiftShader as useful for web developers testing on headless systems or systems without a supported GPU, and also warns that WebGL availability is not guaranteed. The --enable-unsafe-swiftshader switch is a security and performance trade-off, not a universal production default. Test context creation and provide a fallback, such as Canvas 2D or a clear error message, in applications that must run across different environments.

Capture a PDF or record WebGL motion

PDF output

page.pdf() creates a PDF using print CSS by default. If the scene should retain screen styling, call await page.emulateMediaType('screen') before generating the PDF. Where print color conversion changes the result, the page’s print styles can use -webkit-print-color-adjust: exact. PDF is useful for document output, but it is not the same as an image screenshot of a particular canvas region.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
GIGABYTE GeForce RTX 5060 WINDFORCE OC 8G Graphics Card, Cooling System, 8GB 128-bit GDDR7, PCIe 5.0, Manufactured by NVIDIA, DisplayPort & HDMI - Video Output Interface, GV-N5060WF2OC-8GD Video Card
  • Powered by the NVIDIA Blackwell architecture and DLSS 4
  • Powered by GeForce RTX 5060
  • Integrated with 8GB GDDR7 128bit memory interface
  • PCIe 5.0
  • WINDFORCE cooling system

WebM screencast

For an animation recording, Puppeteer’s page.screencast() produces WebM with VP9 and a documented default of 30 FPS; it requires ffmpeg. A basic pattern is:

const recorder = await page.screencast({ path: 'webgl.webm' });
// Interact with the scene or wait for its animation.
await page.waitForTimeout(5000);
await recorder.stop();

Choose the recording duration and scene state intentionally, and ensure ffmpeg is available to the environment running the capture. A screencast is appropriate when motion is the deliverable; for a still, page.screenshot() is simpler and avoids recording a sequence of frames. The current Puppeteer Page API also lists an experimental page.record() method that outputs an MP4 stream. Because it is experimental, pin the Puppeteer version in use and verify the installed API before depending on it.

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

Troubleshoot blank or incorrect captures

Symptom Likely cause What to check or change
Screenshot is blank, but navigation completed The capture ran before the first useful frame, or scene assets are still loading. Wait for the app’s ready promise, loaded-scene flag, or frame counter. Do not treat network idle as proof of rendering.
The canvas is absent or has zero dimensions The wrong selector was used, the app has not mounted, or the renderer has not sized the canvas. Inspect the page for the actual canvas, wait for mount and resize, and check its width and height attributes before capturing.
The context check returns false WebGL context creation failed, the environment does not support the required path, or the selector targets the wrong canvas. Confirm the target canvas and inspect the browser environment. In a controlled test, try the documented SwiftShader flags; handle failure in the app instead of assuming WebGL always exists.
Only part of the scene appears The screenshot used the viewport rather than the full document or canvas, or a clip rectangle excluded part of the target. Choose viewport, full-page, element, or clip capture based on the intended output and verify the selected dimensions.
Frames differ between runs The animation was live at capture time, or assets and rendering completed at different moments. Pause the scene or render a known frame, and synchronize on loaded assets and a completed render rather than a fixed delay alone.
Navigation times out waiting for network idle The page keeps requests open or continues background network activity. Use a suitable navigation wait condition, then wait for an app-specific readiness signal. Avoid weakening readiness checks to the point that the capture races the renderer.
Hardware-GPU capture fails in CI The runner may not expose a usable GPU or the required graphics environment. Test whether GPU access is actually available. For controlled testing, evaluate SwiftShader; do not assume --enable-gpu creates hardware support.
PDF colors or layout do not match the page PDF generation uses print media by default. Emulate screen media when screen styling is required and consider the page’s print color adjustment styles.
Screencast cannot start The recording environment may not have ffmpeg available. Install or expose ffmpeg to the capture process and verify the recorder can stop cleanly after the desired duration.

Cost, reliability, and operational trade-offs

A local Puppeteer capture has no screenshot-service charge, but your process owns the browser, page readiness logic, renderer availability, storage, and retries. Hardware acceleration may improve compatibility or speed on an appropriate machine, while software rendering can be useful in headless tests but may have different performance characteristics. No universal timing or performance figure applies across WebGL scenes and environments.

For reliable automation, capture diagnostics alongside failures: the URL, viewport, canvas dimensions, whether context creation succeeded, the app-ready state, browser error messages, and the selected renderer configuration. Close the browser in a finally block so failures do not leave Chromium processes running. Keep the capture deterministic by pinning your project dependencies and controlling the scene state; verify any API that is labeled experimental against the version actually installed.

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.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. For a standard hosted-page screenshot, its one-request API can return an image; it does not replace Puppeteer’s app-specific wait for a known WebGL frame or let this example force a renderer. Use Puppeteer when you need that level of WebGL control. For a straightforward page capture, the request looks like this; see the ScreenshotNeo API documentation for setup and options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/webgl-demo -o shot.webp
  • It removes cookie banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, failed loads, timeouts, and cache hits are not billed; responses identify the page verdict and billing status in headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.
  • The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month without a card.

Quick Recap

SaleBestseller No. 1
GIGABYTE Radeon RX 9070 XT Gaming OC 16G Graphics Card, PCIe 5.0, 16GB GDDR6, GV-R9070XTGAMING OC-16GD Video Card
GIGABYTE Radeon RX 9070 XT Gaming OC 16G Graphics Card, PCIe 5.0, 16GB GDDR6, GV-R9070XTGAMING OC-16GD Video Card
Powered by Radeon RX 9070 XT; WINDFORCE Cooling System; Hawk Fan; Server-grade Thermal Conductive Gel
$799.28
Bestseller No. 2
GIGABYTE GeForce RTX 5070 Ti Gaming OC 16G Graphics Card, 16GB 256-bit GDDR7, PCIe 5.0, WINDFORCE Cooling System, GV-N507TGAMING OC-16GD Video Card
GIGABYTE GeForce RTX 5070 Ti Gaming OC 16G Graphics Card, 16GB 256-bit GDDR7, PCIe 5.0, WINDFORCE Cooling System, GV-N507TGAMING OC-16GD Video Card
Powered by the NVIDIA Blackwell architecture and DLSS 4; Powered by GeForce RTX 5070 Ti; Integrated with 16GB GDDR7 256bit memory interface
$1,149.99
Bestseller No. 3
ASUS TUF Gaming GeForce RTX™ 5080 16GB GDDR7 OC Edition Graphics Card
ASUS TUF Gaming GeForce RTX™ 5080 16GB GDDR7 OC Edition Graphics Card
3.6-slot design with massive fin array optimized for airflow from three Axial-tech fans; Auto-Extreme precision automated manufacturing helps ensure higher reliability
$1,817.42
Bestseller No. 4
ASUS Dual GeForce RTX 5060 Ti 16GB GDDR7 OC Edition Gaming Graphics Card
ASUS Dual GeForce RTX 5060 Ti 16GB GDDR7 OC Edition Gaming Graphics Card
AI Performance: 767 AI TOPS; OC mode: 2632 MHz (OC mode)/ 2602 MHz (Default mode); Powered by the NVIDIA Blackwell architecture and DLSS 4
$794.37
Bestseller No. 5
GIGABYTE GeForce RTX 5060 WINDFORCE OC 8G Graphics Card, Cooling System, 8GB 128-bit GDDR7, PCIe 5.0, Manufactured by NVIDIA, DisplayPort & HDMI - Video Output Interface, GV-N5060WF2OC-8GD Video Card
GIGABYTE GeForce RTX 5060 WINDFORCE OC 8G Graphics Card, Cooling System, 8GB 128-bit GDDR7, PCIe 5.0, Manufactured by NVIDIA, DisplayPort & HDMI - Video Output Interface, GV-N5060WF2OC-8GD Video Card
Powered by the NVIDIA Blackwell architecture and DLSS 4; Powered by GeForce RTX 5060; Integrated with 8GB GDDR7 128bit memory interface

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.