Skip to content

Latest commit

 

History

History
91 lines (81 loc) · 3.64 KB

testing-pdf.md

File metadata and controls

91 lines (81 loc) · 3.64 KB

Custom matcher for testing PDFs

Here's a simple example of a custom matcher for Playwright that can be used for testing PDFs in a TypeScript test project. This custom matcher convert the PDF file to PNGs (one PNG per page in the pdf) using pdf-to-png-converter and then uses Pixelmatch for checking the pixel diffs. If there's pixel diffs the matcher will attach the 'expected', 'actual' and 'diff' images to the test report.

import { test, expect as baseExpect } from '@playwright/test';
import Pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
import { pdfToPng } from 'pdf-to-png-converter';

export const expect = baseExpect.extend({
  async toMatchPdf(actual: Buffer, expected: Buffer) {
    // check if the buffers are PDF by checking the first 4 bytes if they match the PDF magic number (see https://en.wikipedia.org/wiki/Magic_number_(programming))
    if (actual.subarray(0, 4).toString('hex') !== '25504446' || expected.subarray(0, 4).toString('hex') !== '25504446') {
      return {
        message: () => 'Some of the inputs are not PDFs',
        pass: false,
      };
    }
    // convert the PDFs to PNGs
    const [actualPngPics, expectedPngPics] = await Promise.all([pdfToPng(actual), pdfToPng(expected)]);
    if (actualPngPics.length !== expectedPngPics.length) {
      return {
        message: () => `Different number of pages in the PDFs. Actual: ${actualPngPics.length}, Expected: ${expectedPngPics.length}`,
        pass: false,
      };
    }
    let pass = true;
    let msg = 'Pixel diff found on the following pages: ';
    for (let i = 0; i < actualPngPics.length; i++) {
      // encode the PNG buffers to PNG objects so Pixelmatch can be used (see https://github.com/mapbox/pixelmatch/issues/66)
      const actual = PNG.sync.read(actualPngPics[i].content);
      const expected = PNG.sync.read(expectedPngPics[i].content);

      const {width, height} = actual;
      const diffPng = new PNG({width, height});
      const pixelDiffNbr = Pixelmatch(actual.data, expected.data, diffPng.data, width, height);
      if (pixelDiffNbr > 0) {
        pass &&= false;
        msg += `${i + 1}, `;

        await Promise.all([
          test.info().attach(`Expected PDF page ${i + 1}`, {
            body: PNG.sync.write(expected), // decode the PNG object to a buffer
            contentType: 'image/png',
          }),
          test.info().attach(`Actual PDF page ${i + 1}`, {
            body: PNG.sync.write(actual),
            contentType: 'image/png',
          }),
          test.info().attach(`Pixel diff on page ${i + 1}`, {
            body: PNG.sync.write(diffPng),
            contentType: 'image/png',
          }),
        ]);
      }
    }

    return {
      message: () => pass ? '' : msg,
      pass: pass,
    };
  },
});

and here is a sample snippet on how to use it:

function readableToBuffer(readable): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks = [];
    readable.on('data', chunk => chunks.push(chunk));
    readable.on('error', reject);
    readable.on('end', () => resolve(Buffer.concat(chunks)));
  });
}

test('testing PDF', async ({page}) => {

  const pathToPdf = "<path_to_golden_pdf_file>"
  const originalPdfBuffer = fs.readFileSync(pathToPdf);
  const [download] = await Promise.all([
    page.waitForEvent('download'),
    downloadPdfButton.click(),
  ]);

  // create a buffer from Readable stream
  const downloadPdfBuffer = await readableToBuffer(await download.createReadStream());
  await expect(downloadPdfBuffer).toMatchPdf(originalPngBuffer);
});