Search code examples
react-testing-librarynode-canvasjest-image-snapshot

How do I image snapshot test a React component using HTML5 canvas?


I'm attempting to get image snapshot testing working (i.e., not mocked) with a React component that renders an HTML5 canvas. I'm using Jest, React Testing Library, Node Canvas, Puppeteer, and Jest Image Snapshot.

Given the following React component's render():

  public render(): React.ReactElement<TestCanvas> {
    const { innerWidth, innerHeight } = window;

    return (
      <div id="canvas" style={{ height: `${innerHeight}px`, width: `${innerWidth}px` }}>
        <canvas ref={this.canvasRef} />
      </div>
    );
  }

Here's what a Jest test might look like:

  it('should render a <TestCanvas/> component', async () => {
    const { container } = render(<TestCanvas />);

    const page: puppeteer.Page = await browser.newPage();
    await page.setContent(container.outerHTML);
    const image: string = await page.screenshot();

    expect(image).toMatchImageSnapshot();
  });

However, this test generates an empty, white, 800x600 PNG image as the baseline.

If, however, I change the test to this:

  it('should render a <TestCanvas/> component', async () => {
    const { container } = render(<TestCanvas />);

    const canvas: HTMLCanvasElement = container.querySelector('canvas') as HTMLCanvasElement;
    const img = document.createElement('img');
    img.src = canvas.toDataURL();

    const page: puppeteer.Page = await browser.newPage();
    await page.setContent(img.outerHTML);
    const image: string = await page.screenshot();

    expect(image).toMatchImageSnapshot();
  });

It generates the baseline PNG snapshot based on my React component just fine.

I'm currently trying to debug where in the pipeline things are going screwy.


Solution

  • I have done html5 canvas image snapshot with a method that does not use puppeteer, but the puppeteer method is interesting. Here is the method I used

      test('canvas image snapshot', async () => {
        const { getByTestId } = render(
          <MyComponent />,
        )
    
        const canvas = await waitForElement(() =>
          getByTestId('mycanvas'),
        )
    
        const img = canvas.toDataURL()
        const data = img.replace(/^data:image\/\w+;base64,/, '')
        const buf = Buffer.from(data, 'base64')
        // may need to do fuzzy image comparison because, at least for me, on
        // travis-ci it was sometimes 2 pixel diff or more for font related stuff
        expect(buf).toMatchImageSnapshot({
          failureThreshold: 0.5,
          failureThresholdType: 'percent',
        })
      })
    

    Uses https://github.com/americanexpress/jest-image-snapshot for toMatchImageSnapshot