vitest: Running multiple test files crashes when canvas is installed (Error: Module did not self-register: '.../canvas/build/Release/canvas.node'.)

Describe the bug

I’ve started exprimenting with Vitest and got all tests to pass individually; however, running npx vitest run . (or any set of test files together like npx vitest run src/*.test.js) crashes with the following error:

 RUN  /home/phil/dev/packages/vue-accessible-color-picker

node:internal/process/promises:265
            triggerUncaughtException(err, true /* fromPromise */);
            ^

Error: Module did not self-register: '/home/phil/dev/packages/vue-accessible-color-picker/node_modules/canvas/build/Release/canvas.node'.
    at Object.Module._extensions..node (node:internal/modules/cjs/loader:1185:18)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/home/phil/dev/packages/vue-accessible-color-picker/node_modules/canvas/lib/bindings.js:3:18)
    at Module._compile (node:internal/modules/cjs/loader:1103:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1155:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)

I’ve removed the node_modules directory contents several times, re-installed the dependencies, and the issue still happens. I’m writing this only because that’s the only tangible workaround I found for this problem.

Reproduction

git clone https://github.com/kleinfreund/vue-accessible-color-picker.git
cd vue-accessible-color-picker
npm install

npm test
# or
npx vitest run .

Expected behavior:

Test suite runs with all tests passing.

Actual behavior:

Vitest crashes and no tests are run.

Workaround: turn off threads

In https://github.com/vitest-dev/vitest/issues/740#issuecomment-1042648373, a workaround was presented. Turning off threads stops the issue from happening. Using the following configuration, the tests start working:

/// <reference types="vitest" />

import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    Vue(),
  ],
  test: {
    threads: false,
    environment: 'jsdom',
  },
})

Notes

System Info

System:
    OS: Linux 5.13 Ubuntu 20.04.3 LTS (Focal Fossa)
    Container: Yes
    Shell: 5.8 - /usr/bin/zsh
  Binaries:
    Node: 16.14.0 - /usr/bin/node
    Yarn: 1.22.5 - ~/.yarn/bin/yarn
    npm: 8.5.0 - ~/dev/packages/vue-accessible-color-picker/node_modules/.bin/npm
  npmPackages:
    @vitejs/plugin-vue: ^2.2.0 => 2.2.0 
    vite: ^2.8.1 => 2.8.1 
    vitest: ^0.3.4 => 0.3.4

Used Package Manager

npm

Validations

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 25
  • Comments: 22 (7 by maintainers)

Commits related to this issue

Most upvoted comments

workaround: in your config set threads: false

Is there any way to use threads: true while mocking or disabling jsdom? Disabling threads means losing many of vitest’s benefits.

Apparently, npm 7+ always installs canvas since it’s a peer dependency of jsdom. So this code in jsdom require()s canvas even if it is not mentioned in my package.json dependencies.

I’m using vite-plugin-svelte and run into the “Module did not self-register” error if a test just includes a “Hello world” Svelte Component, without using the canvas API at all.

I‘ve tried vi.mock('canvas', () => ({}));. But this does not work, I guess because Vitest cannot mock what jsdom require()s. Any other ideas? Thanks.

@MichaelFBA try calling require('sharp') in config file. Or in globalSetup. These are the only places user can call code outside of a worker thread.

threads: false makes things even worse. I’m using @testing-library/react and somehow setting threads: false breaks down all the screen usages while keeping threads: true breaks canvas 😅 so far the only solution I’ve found is this configuration:

{   
       //...
        minThreads: 0,
        maxThreads: 1,
}

in the above example canvas seems to be happy and all the tests run without any changes. Nevertheless in this configuration everything is extremely slow.

I also tried older versions of canvas, all with the same result.

UPDATE

As we can see here (thanks to @molily) and here jsdom only tries to include canvas if it was already installed directly as dependency in package.json, or indirectly by some other module. In my case the other module turned out to be ascii-art which installed canvas as it’s dependency and jsdom included it automatically - welcome to the 2022 ¯\(ツ)

TEMPORARY SOLUTION

works only if you can uninstall library which included canvas for you. And of course it’s only a messy workaround, not a real solution to the problem.

npm ls canvas # we want to see "-- (empty)" here

if you see anything other than “empty” try to identify which library included canvas as it’s dependency and uninstall it with canvas:

npm uninstall --save canvas LIBRARY_NAME
rm -r node_modules/canvas # just to be sure ;)

then remove lock:

rm package-lock.json

and try again:

npm ls canvas # we want to see "-- (empty)"

Had the same problem with pdfjs-dist including canvas as optional, npm i --no-optional did the work for me

This problem now happens even if you disable threads with threads: false

@SebCorbin Did you manage to find the issue? I’m also dealing with the same issue with pdfjs-dist

Since canvas is an optional peer dependency, we’re considering a workaround to just not install peer dependencies locally and in our build agent. This only works for us because we don’t have any other optional peer dependencies that we need to use locally or during testing. Though, if you did find yourself in that situation, you could list your optional peer dependencies as explicit dependencies.

npm ci --omit=optional

@MichaelFBA try calling require('sharp') in config file. Or in globalSetup. These are the only places user can call code outside of a worker thread.

Yes I tried adding it to our global setup file for vitest. The issue seems to occur when running watch and only when you change a test does it break

Just want to add that because I am also using setup files, having threads turned off has additional performance hits.

TIP Note, that if you are running --no-threads, this setup file will be run in the same global scope multiple times. Meaning, that you are accessing the same global object before each test, so make sure you are not doing the same thing more than you need.

https://vitest.dev/config/#setupfiles

Is there any way to use threads: true while mocking or disabling jsdom? Disabling threads means losing many of vitest’s benefits.

What kind of benefits?

Apparently, npm 7+ always installs canvas since it’s a peer dependency of jsdom. So this code in jsdom require()s canvas even if it is not mentioned in my package.json dependencies.

As a workaround, maybe there is an old version of this module that doesn’t throw? And you can pin it in your package.json.

Also, you can try using happy-dom 😃

I have a feeling this might be caused by https://github.com/Automattic/node-canvas/issues/1394.

This is caused by the package canvas being installed in my dependencies and used in my tests. JSDom automatically detects the package being present (see https://github.com/jsdom/jsdom#canvas-support) and allows me to test code that relies on document.createElement('canvas').getContext('2d').

Removing it allows me to run multiple tests at once (e.g npm test -- src/utilities/color-conversions/* works), but naturally, the tests relying on the canvas integration into JSDom fail now.

Interestingly, if ran individually, the tests relying on canvas work fine with Vitest (e.g. npm t -- src/ColorPicker.test.js passes). Just running them collectively results in the error reported originally.