| name | extension-test |
| description | Set up and run unit, integration, and E2E tests for Chrome extensions. Covers Jest mocks for chrome.* APIs and Puppeteer E2E with real Chrome. |
Extension Testing
Testing Layer Architecture
Unit Tests (Jest) → Test isolated logic, chrome.* API mocks
Integration Tests (Jest) → Test service interactions, message passing
E2E Tests (Puppeteer) → Test in real Chrome with extension loaded
Critical constraint: Extensions CANNOT run in headless mode. E2E requires headless: false or Chrome's --headless=new (Chrome 112+).
Layer 1: Unit + Integration (Jest)
Install
npm install -D jest @types/jest ts-jest jest-chrome
npm install -D @testing-library/react @testing-library/jest-dom jsdom
jest.config.ts
export default {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./jest.setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};
jest.setup.ts (minimal)
import chrome from 'jest-chrome';
Object.assign(global, { chrome });
Full mock setup → chrome-api-mocks.md
Unit/integration patterns → unit-integration-testing.md
Layer 2: E2E (Puppeteer)
Install
npm install -D puppeteer
Launch extension in Chrome
import puppeteer from 'puppeteer';
import path from 'path';
const browser = await puppeteer.launch({
headless: false,
args: [
`--disable-extensions-except=${path.resolve('dist')}`,
`--load-extension=${path.resolve('dist')}`,
],
});
Get extension ID
const targets = await browser.targets();
const extTarget = targets.find(t => t.type() === 'service_worker');
const extUrl = extTarget?.url() ?? '';
const [, , extId] = extUrl.split('/');
Full E2E patterns → e2e-testing-puppeteer.md
Chrome API Mocking Strategy
| API | Approach |
|---|
chrome.storage | Mock with in-memory store |
chrome.runtime.sendMessage | jest.fn() + mock response |
chrome.tabs | jest.fn() with createMockTab helper |
chrome.action | jest.fn() stubs |
chrome.alarms | jest.fn() stubs |
All mocks → chrome-api-mocks.md
package.json Scripts
{
"scripts": {
"test:unit": "jest --testPathPattern=unit",
"test:integration": "jest --testPathPattern=integration",
"test:e2e": "jest --testPathPattern=e2e --runInBand",
"test": "npm run test:unit && npm run test:integration"
}
}
Run E2E separately (--runInBand) — Puppeteer tests must run serially.
Reference Files