Skip to main content

WebGL

jest-environment-webgl-node gives Jest real, headless WebGL 1 and WebGL 2. document.createElement('canvas').getContext('webgl2') returns a GPU-backed context, so three.js, Babylon.js, regl, pixi and plain WebGL code run unchanged. It is powered by @onirenaud/node-webgl, which statically links Chrome's ANGLE.

Install and configure​

pnpm add -D jest jest-environment-webgl-node
// jest.config.mjs
export default {
testEnvironment: 'jest-environment-webgl-node',
testEnvironmentOptions: { innerWidth: 800, innerHeight: 600 },
};
import * as THREE from 'three';
import { expect, it } from '@jest/globals';

it('renders', () => {
const canvas = document.createElement('canvas');
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(64, 64, false);
renderer.render(new THREE.Scene(), new THREE.PerspectiveCamera());

const gl = renderer.getContext();
const pixels = new Uint8Array(4);
gl.readPixels(32, 32, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
expect(pixels[3]).toBe(255);
});

See Getting started for the complete ESM and CommonJS setups.

Options​

testEnvironmentOptions are those of node-webgl's init() and installDOM():

  • From init(): backend, api.
  • From installDOM(): baseDir, fetch, devicePixelRatio, innerWidth, innerHeight, frameInterval.

The type is exported as WebglNodeOptions; see the API reference.

The native WebGL display is process-wide and is created by the first context in a worker, so set backend and api consistently across all projects that can share a Jest worker. The environment throws if an explicit backend or api disagrees with the display already initialized in that worker, including one initialized with platform defaults by a project that set no options.

What each test file gets​

Each test file receives a fresh document, DOM event state, viewport values, file-fetch base directory and animation-frame queue. The environment never replaces host globals.

Animation frames use Jest's sandbox timers, so jest.useFakeTimers() and jest.advanceTimersByTime() control them. Pending frames are cancelled during environment teardown.

Canvases​

Teardown disposes canvases created by document.createElement, document.createElementNS, new HTMLCanvasElement and new OffscreenCanvas, releasing their native contexts and GPU resources. Canvases are retained until teardown, so call canvas.dispose() when you finish with one. Canvases or contexts created directly through imports from @onirenaud/node-webgl remain your responsibility.

Fetching files​

File fetching is enabled by default. Relative paths resolve from the test file's baseDir (default: process.cwd()), while HTTP, data and blob URLs use Node's native fetch. Set fetch: false to use native fetch for every URL.

TypeScript​

Add import 'jest-environment-webgl-node/globals'; to a .d.ts file your tsconfig includes to type the installed globals.

ESM and CommonJS​

The package ships both builds, so require('jest-environment-webgl-node') works from a plain CommonJS test file. NODE_OPTIONS=--experimental-vm-modules is only needed when a test file itself imports an ESM-only library.

Image snapshots​

jest-gpu ships no screenshot matcher. Pair it with jest-image-snapshot by reading pixels with gl.readPixels and encoding the RGBA data to PNG with sharp. The WebGPU page shows the snippet.

Linux CI​

Install Mesa and force software rendering where there is no GPU; see CI.

Using Vitest?​

The equivalent package for Vitest is vitest-environment-webgl-node, from vitest-gpu.