Puppeteer 2.1.0, with native Firefox support

Late January Puppeteer 2.1.0 got released, with native support for Firefox:

The launcher now has an option to run Puppeteer with different browsers, starting with Firefox. Puppeteer can now talk to a real, unpatched Firefox binary. This is a first step towards eventually deprecating the separate puppeteer-firefox package in favor of supporting Firefox directly in puppeteer itself.

You can define which browser to use by setting product on the options object to either "chrome" or "firefox", or by setting the PUPPETEER_PRODUCT env variable to one of those values.

puppeteer.launch({product: 'firefox'});

Installation still per NPM/Yarn

npm i puppeteer

Puppeteer →

Embeddable CanIUse Images

Ire Aderinokun, author of the CanIUse Embed, has added an extra option where you can embed static images of features as mentioned on CanIUse.com. The images are generated using Puppeteer, are stored on Cloudinary, and are updated daily using Heroku Scheduler.

What I wanted to do was have a URL linking to an image (hosted on Cloudinary) that I would periodically update with the support table for that feature. For example, to get the latest image of CSS Grid support, anyone could use this URL: https://caniuse.bitsofco.de/image/css-grid.png

A detailed writeup on how she created those images (and how she updates) takes you through the whole process.

How I created 488 “live images” →

πŸ’β€β™‚οΈ You can also check CanIUse data using the command line tool caniuse-cmd

Building a Website Screenshot API with Puppeteer and Google Cloud Functions

Here’s the source of a Google Cloud function that, using Puppeteer, takes a screenshot of a given website and store the resulting screenshot in a bucket on Google Cloud Storage:

const puppeteer = require('puppeteer');
const { Storage } = require('@google-cloud/storage');

const GOOGLE_CLOUD_PROJECT_ID = "screenshotapi";
const BUCKET_NAME = "screenshot-api-net";

exports.run = async (req, res) => {
  res.setHeader("content-type", "application/json");
  
  try {
    const buffer = await takeScreenshot(req.body);
    
    let screenshotUrl = await uploadToGoogleCloud(buffer, "screenshot.png");
    
    res.status(200).send(JSON.stringify({
      'screenshotUrl': screenshotUrl
    }));
    
  } catch(error) {
    res.status(422).send(JSON.stringify({
      error: error.message,
    }));
  }
};

async function uploadToGoogleCloud(buffer, filename) {
    const storage = new Storage({
        projectId: GOOGLE_CLOUD_PROJECT_ID,
    });

    const bucket = storage.bucket(BUCKET_NAME);

    const file = bucket.file(filename);
    await uploadBuffer(file, buffer, filename);
  
    await file.makePublic();

  	return `https://${BUCKET_NAME}.storage.googleapis.com/${filename}`;
}

async function takeScreenshot(params) {
	const browser = await puppeteer.launch({
		args: ['--no-sandbox']
	});
	const page = await browser.newPage();
	await page.goto(params.url, {waitUntil: 'networkidle2'});

	const buffer = await page.screenshot();

	await page.close();
	await browser.close();
  
  	return buffer;
}

async function uploadBuffer(file, buffer, filename) {
    return new Promise((resolve) => {
        file.save(buffer, { destination: filename }, () => {
            resolve();
        });
    })
}

Usage:

curl -X POST -d '{"url": "https://github.com"}' https://google-cloud-endpoint/my-function

Building a Website Screenshot API →

πŸ’‘ If I were to run this in production I’d extend the code to first check the presence of an existing screenshot in the bucket or not, and – if the screenshot is not too old – redirect to it.

Take both Light and Dark Mode screenshots with Puppeteer

dark-mode-screenshot is a Puppeteer script to take screenshots of both the light and dark mode versions of a website.

$ npx dark-mode-screenshot --url https://googlechromelabs.github.io/dark-mode-toggle/demo/index.html --output screenshot --fullPage

Works in somewhat odd way first requiring the OS to have dark mode enabled (?), and then launch Chromium:

  1. Once with prefers-color-scheme disabled (using --disable-blink-features=MediaQueryPrefersColorScheme)
  2. Once with Dark Mode force enabled (using --force-dark-mode)

dark-mode-screenshot (GitHub) →

❓ New to Dark Mode? No worries, this post on CSS Color Scheme Queries has got you covered.

Run your tests using Jest & Puppeteer with jest-puppeteer

With jest-puppeteer – and its included expect-puppeteer assertion library – it’s possible to use Puppeteer within your Jest tests.

Writing integration test can be done using Puppeteer API but it can be complicated and hard because API is not designed for testing.

To make it simpler, an expectPage() is automatically installed and available, it provides a lot of convenient methods, all documented in expect-puppeteer API.

describe('Google', () => {
  beforeAll(async () => {
    await page.goto('https://google.com')
  })

  it('should display "google" text on page', async () => {
    expectPage().toMatch('google')
  })
});

With expect-puppeteer it’s also easy peasy to click buttons, complete forms, etc.

// Assert that a form will be filled
await expectPage().toFillForm('form[name="myForm"]', {
  firstName: 'James',
  lastName: 'Bond',
})

jest-puppeteer

Automatic visual diffing with Puppeteer

A few years ago we got Wraith and Huxley to perform visual regression testing. Monica Dinculescu has created a likewise thingy, powered by Puppeteer:

I did a little song-and-dance that sets up Puppeteer, takes screenshots of your app (like, all the routes you care about), and then compares them to the β€œgolden” ones. If they match, your test passes!

The diffing can be integrated in your current testing setup, as the testing scripts themselves are written using Mocha and Chai. The core of the script is the compareScreenshots function, which checks all pixels of both screenshots, taking an allowed treshold/variance into account.

function compareScreenshots(fileName) {
  return new Promise((resolve, reject) => {
    const img1 = fs.createReadStream(`${testDir}/${fileName}.png`).pipe(new PNG()).on('parsed', doneReading);
    const img2 = fs.createReadStream(`${goldenDir}/${fileName}.png`).pipe(new PNG()).on('parsed', doneReading);

    let filesRead = 0;
    function doneReading() {
      // Wait until both files are read.
      if (++filesRead < 2) return;

      // The files should be the same size.
      expect(img1.width, 'image widths are the same').equal(img2.width);
      expect(img1.height, 'image heights are the same').equal(img2.height);

      // Do the visual diff.
      const diff = new PNG({width: img1.width, height: img2.height});
      const numDiffPixels = pixelmatch(
          img1.data, img2.data, diff.data, img1.width, img1.height,
          {threshold: 0.1});

      // The files should look the same.
      expect(numDiffPixels, 'number of different pixels').equal(0);
      resolve();
    }
  });
}

Automatic visual diffing with Puppeteer →

Using DevTools Features Without Opening DevTools using Puppeteer

Keeping a feature of the Chrome Devtools – such as the FPS Meter – running with the DevTools closed unfortunately is not possible (yet?). Kayce Basques provides us with a little workaround though:

You can hack together a Puppeteer script that launches Chromium, opens a remote debugging client, then turns on the DevTools feature that you like (via the Chrome DevTools Protocol), without ever explicitly opening DevTools.

const page = await browser.newPage();
const devtoolsProtocolClient = await page.target().createCDPSession();
await devtoolsProtocolClient.send('Overlay.setShowFPSCounter', { show: true });
await page.goto('https://example.org/');

Check out Chrome DevTools Protocol View for an entire list of commands you can send.

Using DevTools Features Without Opening DevTools →