> [!NOTE]
> **Note:** For general advice on testing extensions, see [End-to-end testing of Chrome Extensions](https://developer.chrome.com/docs/extensions/mv3/end-to-end-testing) and [Unit testing in Chrome Extensions](https://developer.chrome.com/docs/extensions/mv3/unit-testing).

[Puppeteer](https://github.com/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](https://github.com/GoogleChrome/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](https://nodejs.org/) 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:

```javascript
{
  "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:

> [!NOTE]
> **Note:** Jest adds `beforeEach` and `afterEach` to our environment when running the tests, so we don't need to import them. However, you may need to configure [ESLint](https://www.npmjs.com/package/eslint-plugin-jest) or [TypeScript](https://jestjs.io/docs/getting-started#type-definitions) accordingly.

index.test.js:

```javascript
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

> [!NOTE]
> **Note:** In this example, `headless` is set to `false`. This causes the browser window to be visible while the tests are running which can be helpful during development. Outside of development, consider setting it to `'new'` which uses Chrome's [new headless mode](https://developer.chrome.com/docs/chromium/new-headless).

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:

```javascript
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:

```javascript
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:

```javascript
{
  "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:

```javascript
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('browser.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:

```javascript
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('browser.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](https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/functional-samples/tutorial.puppeteer) 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](https://pptr.dev/api) contains more information about what's possible - there are many
capabilities not described here.