Test Chrome Extensions with Puppeteer

Puppeteer provides a framework for building automated tests of websites, which also includes the ability to test Chrome Extensions. These are high-level end-to-end tests that test the functionality of a website or extension once it has been built into the final product. In this tutorial, we demonstrate how to write a basic test for an extension from our samples repository.

Before you start

Clone or download the chrome-extensions-samples repository. We'll use the history API demo in api-samples/history/showHistory as our test extension.

You'll also need to install Node.JS which is the runtime Puppeteer is built on.

Writing your test

Step 1: Start your Node.JS project

We need to set up a basic Node.JS project. In a new folder, create a package.json file with the following:

package.json:

{
  "name": "puppeteer-demo",
  "version": "1.0"
}

Similar to an extension's manifest.json file, this file is required by all Node projects.

Step 2: Install Puppeteer and Jest

Run npm install puppeteer jest to add Puppeteer and Jest as dependencies. They will be automatically added to your package.json file.

It is possible to write standalone Puppeteer tests, but we'll use Jest as a test runner to provide some additional structure to our code.

Step 3: Create an entry point

Create a new file called index.test.js and add the following code:

index.test.js:

const puppeteer = require('puppeteer');

const EXTENSION_PATH = '../../api-samples/history/showHistory';

let browser;
let worker;

beforeEach(async () => {
  // TODO: Launch the browser.
});

afterEach(async () => {
  // TODO: Close the browser.
});

Step 4: Launch the browser

Update beforeEach and afterEach to launch and close the browser. When running many tests, you may want to consider using the same browser. However, this is generally discouraged as it reduces the isolation between your tests and may cause one test to impact the outcome of another.

index.test.js:

beforeEach(async () => {
  browser = await puppeteer.launch({
    headless: false,
    pipe: true,
    enableExtensions: [EXTENSION_PATH]
  });
});

afterEach(async () => {
  await browser.close();
  browser = undefined;
});

Step 5: Wait for the extension service worker

We need to wait for the extension service worker to start so that we can use it later to open the popup. Update your beforeEach function with the following code:

beforeEach(async () => {
  browser = await puppeteer.launch({
    headless: false,
    pipe: true,
    enableExtensions: [EXTENSION_PATH]
  });

  const workerTarget = await browser.waitForTarget(
    // Assumes that there is only one service worker created by the extension
    // and its URL ends with service-worker.js.
    (target) =>
      target.type() === 'service_worker' &&
      target.url().endsWith('service-worker.js')
  );

  worker = await workerTarget.worker();
});

Step 6: Add an alias

To make running the tests easier, add an alias to your package.json file:

package.json:

{
  "name": "puppeteer-demo",
  "version": "1.0",
  "dependencies": {
    "puppeteer": "^24.8.1"
  },
  "scripts": {
    "start": "jest ."
  }
}

This will run all files ending in .test.js in the current directory.

Step 7: Open the popup

Add a basic test. We will first open a new page so that there is an item in the browser history. Then, we will open the popup to see the history contents. Add the following code:

index.test.js:

test('popup renders correctly', async () => {
  // Open a page to add a history item.
  const page = await browser.newPage();
  await page.goto('https://example.com');

  // Open the extension popup.
  await worker.evaluate('chrome.action.openPopup();');

  const popupTarget = await browser.waitForTarget(
    // Assumes that there is only one page with the URL ending with popup.html
    // and that is the popup created by the extension.
    (target) => target.type() === 'page' && target.url().endsWith('popup.html')
  );
});

Step 8: Assert the current state

Assert something, so that our test can fail if the extension isn't behaving as expected. We know that our popup should show recently visited pages, so check that we see one:

index.test.js:

test('popup renders correctly', async () => {
  // Open a page to add a history item.
  const page = await browser.newPage();
  await page.goto('https://example.com');

  // Open the extension popup.
  await worker.evaluate('chrome.action.openPopup();');

  const popupTarget = await browser.waitForTarget(
    // Assumes that there is only one page with the URL ending with popup.html
    // and that is the popup created by the extension.
    (target) => target.type() === 'page' && target.url().endsWith('popup.html')
  );

  const popup = await popupTarget.asPage();

  const list = await page.$('ul');
  const children = await list.$$('li');

  expect(children.length).toBe(1);
});

Step 9: Run your test

To run the test, use npm start. You should see output indicating that your test passed.

You can see the full project in our chrome-extensions-samples repository.

Next Steps

After mastering the basics, try building a test suite for your own extension. The Puppeteer API reference contains more information about what's possible - there are many capabilities not described here.