Suggestions for unit testing with headless-gl and WebGL 2

I’m very disappointed because I had an amazing Jest unit testing setup configured with headless-gl: GitHub - stackgl/headless-gl: 🎃 Windowless WebGL for node.js. I could render a frame using WebGLRenderer.render() just like a browser environment and examine object positions and properties for the test.

However, since THREE deprecated WebGL1 in 0.163.0, I can’t use this anymore since the package doesn’t support WebGL2.

Does anyone have unit testing suggestions? Using Pupeteer is not an option. I have 1,000+ tests that I would need to migrate and I would not be interested in testing with a headless browser rendering a page anyway.

I know that I could also mock WebGLRenderer.render() to at least do the essential things like update matrices. This would work well for me because many of my tests are not dependent on actual visual output, but this is less than ideal.

1 Like

I made some progress testing THREE with headless-gl and WebGL 2 and wanted to share. Hopefully it is helpful to some people. I thought this would be a daunting task, but I was able to mock some WebGL 2 methods with empty functions so that WebGLRenderer can initialize successfully and render() can be called without issue. This fixed 99% of my tests.

In a Jest environment using JSDom, this is the code snippet I am using. I am mocking HTMLCanvasElement.getContext() and injecting headless-gl. Perhaps there is a better way, but this is how I’ve always done it. I then mocked only 4 WebGL 2 methods to get things working. These methods are called when WebGLRenderer initializes for various reasons.

// This is how we set up headless GL. Without this, there will be an error when the WebGLRenderer tries to create a context.
const gl = require("gl")(window.innerWidth, window.innerHeight);
const getContextOriginal = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(contextId, options) {
  if (contextId === "webgl2") {
    // headless-gl only supports WebGL 1. However, with a few hacks, we can make THREE work reasonably well with headless-gl.
    // This is far from perfect. Even THREE's default shaders won't compile since they use WebGL 2. However, this is irrelevant for 99% of my tests.
    // Until headless-gl supports WebGL 2, tests that require shader compilation, visual output, or WebGL data will need to use a browser environment via Puppeteer.
    gl.texImage3D = () => {};
    gl.createVertexArray = () => {};
    gl.bindVertexArray = () => {};
    gl.deleteVertexArray = () => {};

    return gl;
  }

  // It's important that we allow other contexts to be created (ex. '2d'). Otherwise, this can cause THREE errors and warnings.
  return getContextOriginal.call(this, contextId, options);
}

This is far from perfect, but allows you to invoke a render loop like a real app and test app logic, object positions, matrices, etc. Depending on the type of project you are developing, this might be very useful. If you need to test shader compilation, visual output, etc, you will still need to use something like Puppeteer.