How to Test a Chrome Extension End-to-End

· TestDriver Team

Chrome extension testing with plain-language steps: open the real popup and check the page, no selectors.

Above: the test below running against Dark Reader, sped up with idle time removed.

You can do Chrome extension testing end-to-end by describing each step in plain language. TestDriver clicks the extension’s real toolbar popup and checks the page the way a user sees it, so:

  • you test the popup where users open it, not a copy in a tab
  • you don’t write selectors into the popup’s or the page’s DOM
  • you can check visual effects, like a page turning dark

TestDriver generates and runs black-box end-to-end tests. It starts your app in a fresh virtual machine, looks at the screen and uses a real mouse and keyboard, so it never needs selectors or access to your code. Each test is a normal Vitest file, and every run is recorded as a replay.

The example loads Dark Reader from the Chrome Web Store, switches it off and on from its popup, and checks the page’s colors each time. The full project is on GitHub: testdriverai/example-chrome-extension.

Why not Puppeteer or Playwright

Chrome’s end-to-end testing guide points to Puppeteer, Playwright, Selenium and WebdriverIO. It suggests opening the popup with action.openPopup() where your library supports it, or loading the popup’s URL in a new tab. Either way, you then click elements by selector:

// Puppeteer-style popup test (illustrative, not run)
const popup = await browser.newPage();
await popup.goto(`chrome-extension://${extensionId}/popup.html`);
await popup.click("#on-off-toggle");

TestDriver opens the popup from the toolbar and clicks what’s on screen:

await testdriver.find('"Dark Reader" in the Extensions menu').click();
await testdriver.find('the "On" option of the On/Off switch at the top of the Dark Reader popup').click();

The selector version depends on the popup’s file path and markup. The description depends only on what the user sees.

Watch the full replay

The replay shows every step with its screenshot and log.

The test

import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/vitest/hooks";

describe("Dark Reader (Chrome extension)", () => {
  it("turns a light page dark from the extension popup", async (context) => {
    const testdriver = TestDriver(context);

    // Download Dark Reader from the Chrome Web Store and start Chrome with it loaded.
    await testdriver.provision.chromeExtension({ extensionId: "eimadpbcbfnmbkopoojfekhnkhdbieeh" });

    // Open a plain, light website.
    await testdriver.pressKeys(["ctrl", "l"]);
    await testdriver.type("https://example.com");
    await testdriver.pressKeys(["enter"]);
    await testdriver.find('the "Example Domain" heading on the page', { timeout: 30000 });

    // Open the Dark Reader popup from the Extensions menu.
    await testdriver.find("the puzzle-piece Extensions button at the right end of the Chrome toolbar").click();
    await testdriver.find('"Dark Reader" in the Extensions menu').click();
    await testdriver.find("the Dark Reader popup window", { timeout: 15000 });

    // Switch the extension off. The page should go back to its normal light colors.
    await testdriver.find('the "Off" option of the On/Off switch at the top of the Dark Reader popup').click();
    await testdriver.pressKeys(["escape"]);
    const lightPage = await testdriver.assert(
      'the "Example Domain" page has a light background with dark text',
    );
    expect(lightPage).toBeTruthy();

    // Open the popup again and switch the extension on.
    await testdriver.find("the puzzle-piece Extensions button at the right end of the Chrome toolbar").click();
    await testdriver.find('"Dark Reader" in the Extensions menu').click();
    await testdriver.find('the "On" option of the On/Off switch at the top of the Dark Reader popup').click();
    await testdriver.pressKeys(["escape"]);

    // The same page is now dark.
    const darkPage = await testdriver.assert(
      'the "Example Domain" page has a dark background with light text',
    );
    expect(darkPage).toBeTruthy();
  });
});

How it works

Load the extension and open a page

provision.chromeExtension() downloads the extension by its Chrome Web Store ID and starts Chrome with it loaded. Pass extensionPath instead to load an unpacked build from a folder in the sandbox. Dark Reader opens its welcome tab on install, so the test types the URL into that tab’s address bar.

Describe elements by what users see

Each find() names the label, the look and the position, such as “the puzzle-piece Extensions button at the right end of the Chrome toolbar”. The same descriptions work for Chrome’s own UI and for your popup. See locating elements.

Wait, then assert what the page looks like

find() with a timeout waits for the page and the popup without a fixed sleep. assert() checks a plain-language statement against the screen, so “a dark background with light text” is a check you can write directly. See making assertions.

Run it yourself

Clone the example repo, or set up TestDriver in your own project and run the test:

npx testdriverai init
npx vitest run chrome-extension.test.mjs

To test your own extension:

  • Swap the extensionId for yours, or pass extensionPath with an unpacked build you copy into the sandbox.
  • Replace the Dark Reader steps with your popup’s labels, as a user would read them.
  • Assert the effect on the page as well as the popup’s state.

FAQ

Do I need selectors or test IDs in my popup? No. Steps are matched on screenshots, so there are no data-testid attributes to add or keep in sync.

Can it test the real toolbar popup? Yes. The test clicks the Extensions menu and your extension’s entry, the same way a user opens it.

Can I run it in CI? Yes. It’s a normal Vitest file and Chrome runs in TestDriver’s cloud sandbox. See CI/CD.

Automate and scale manual testing with AI

TestDriver uses computer-use AI to test any app - write tests in plain English and run them anywhere.