npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

extrig

v0.2.0

Published

The only way to reliably test browser extensions without pain

Downloads

309

Readme


Why Extrig?

Testing Chrome extensions is painful. launchPersistentContext boilerplate, dynamic extension IDs, service worker suspension flakiness in CI, manual chrome.* API stubs that return undefined — every developer hits these walls.

Extrig solves all of them in one package.

pnpm add -D extrig @playwright/test vitest
npx extrig init
npx extrig test --unit    # ⚡  fast — no browser
npx extrig test --e2e     # 🌐  real Chrome

Features

  • Stateful Chrome API mockingchrome.storage.local.get() actually returns stored values across calls. chrome.runtime.sendMessage() routes to registered listeners. chrome.alarms uses real timers. Not thin stubs — real in-memory implementations.
  • Zero-config Playwright fixtures — Never write launchPersistentContext again. extContext, extensionId, sw, openPopup(), openOptions() — all auto-wired.
  • Flakiness-free service worker handlingwaitForSWReady() polls the SW before proceeding. forceSWTermination() + waitForSWWake() test the suspend/resume cycle reliably.
  • Shadow DOM helpersshadowLocator(['host', '#inner']) pierces shadow roots without manual evaluate() gymnastics.
  • Network interceptionmockNetwork() and captureNetwork() on the context level, including SW-initiated requests.
  • Built-in flakiness detection — Tracks every test result across runs, surfaces flaky tests automatically.
  • Beautiful terminal UI — Built on Ink v5, with live elapsed time, SW status indicator, and per-test timings.
  • VS Code integration — Run tests inline with CodeLens, see results in the Test Explorer sidebar, get inline error diagnostics.

Quick start

1. Install

pnpm add -D extrig @playwright/test vitest

2. Initialize

npx extrig init

This auto-detects your extension framework (WXT, Plasmo, or generic), then creates:

| File | Purpose | |---|---| | extrig.config.ts | Extrig configuration | | vitest.config.ts | Unit test runner (with chrome mock auto-setup) | | playwright.config.ts | E2E test runner | | tests/unit/ | Example unit test | | tests/e2e/ | Example E2E test | | .github/workflows/test.yml | CI pipeline |

3. Run

npx extrig test --unit    # Unit tests only (no browser, instant)
npx extrig test --e2e     # E2E tests only (real Chrome)
npx extrig test           # Both
npx extrig watch          # Watch mode
npx extrig ci             # CI-optimized run

Unit tests (no browser)

Extrig provides stateful, in-memory Chrome API mocks. They behave like the real APIs:

// tests/unit/background.test.ts
import { describe, it, expect } from 'vitest';
import { mockRuntime, mockStorage, mockAlarms } from 'extrig/mock';

describe('background', () => {
  it('responds to PING', async () => {
    mockRuntime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg.type === 'PING') sendResponse({ pong: true });
    });
    const result = await mockRuntime.sendMessage({ type: 'PING' });
    expect(result).toEqual({ pong: true });
  });

  it('stores and retrieves data', async () => {
    await mockStorage.local.set({ user: { name: 'Alice' } });
    const data = await mockStorage.local.get('user');
    expect(data).toEqual({ user: { name: 'Alice' } });
  });

  it('fires alarms instantly with _fire()', async () => {
    const fired = [];
    mockAlarms.onAlarm.addListener(a => fired.push(a.name));
    await mockAlarms.create('sync', { periodInMinutes: 30 });
    await mockAlarms._fire('sync'); // ⚡ no real timer needed
    expect(fired).toContain('sync');
  });

  it('throws when no listener is registered', async () => {
    await expect(mockRuntime.sendMessage({ type: 'UNKNOWN' }))
      .rejects.toThrow('Could not establish connection');
  });
});

Available mocks

| Module | Import | What it mocks | |---|---|---| | Storage | extrig/mockmockStorage | local, sync, session, managed areas with real Map storage | | Runtime | extrig/mockmockRuntime | sendMessage, connect, onMessage, onInstalled, getURL | | Tabs | extrig/mockmockTabs | query, create, update, remove with tab store and events | | Alarms | extrig/mockmockAlarms | create, get, clear, _fire() for instant triggering | | Action | extrig/mockmockAction | setBadgeText, setTitle, enable/disable, onClicked |


E2E tests (real Chrome)

E2E tests use Playwright with automatic extension loading:

// tests/e2e/popup.test.ts
import { test, expect } from 'extrig/fixtures';

test('popup opens without errors', async ({ openPopup }) => {
  const popup = await openPopup();
  await expect(popup.locator('body')).not.toHaveText(/error/i);
});

test('loads data from storage', async ({ openPopup, seedStorage }) => {
  await seedStorage('local', { userName: 'Alice' });
  const popup = await openPopup();
  await expect(popup.locator('#username')).toHaveText('Alice');
});

test('survives service worker termination', async ({
  openPopup, forceSWTermination, waitForSWWake
}) => {
  await openPopup();
  await forceSWTermination();  // kill SW via CDP
  await waitForSWWake(15_000); // wait for restart
  // Extension is still functional
});

test('network requests can be mocked', async ({ openPage, mockNetwork }) => {
  const unmock = await mockNetwork({
    url: 'https://api.example.com/**',
    response: { status: 200, body: { mocked: true } },
  });
  const page = await openPage('popup.html');
  await expect(page.locator('body')).toBeVisible();
  await unmock();
});

Available fixtures

| Fixture | Type | Description | |---|---|---| | extContext | BrowserContext | The persistent Chromium context with extension loaded | | extensionId | string | The extension's 32-character ID | | sw | Worker | The extension's service worker | | openPopup() | () => Page | Opens popup.html | | openOptions() | () => Page | Opens options.html | | openSidePanel() | () => Page | Opens sidepanel.html | | openPage(path) | (path) => Page | Opens any extension page | | waitForSWReady() | (ms?) => Worker | Polls SW until responsive | | forceSWTermination() | () => void | Kills SW via CDP | | waitForSWWake() | (ms?) => Worker | Waits for SW restart | | seedStorage(area, data) | (area, data) => void | Pre-populates extension storage | | getStorage(area, keys) | (area, keys) => data | Reads extension storage | | assertStorage(area, data) | (area, data) => void | Asserts storage matches | | sendMessage(msg) | (msg) => response | Sends message to SW | | captureMessages() | () => { messages } | Captures all incoming messages | | mockNetwork(opts) | (opts) => unmock | Intercepts network requests | | captureNetwork(pattern) | (pat) => { requests } | Spies on network requests | | shadowLocator(page, c) | (page, c[]) => Loc | Pierces shadow DOM |


What Extrig solves

| Problem | Without Extrig | With Extrig | |---|---|---| | Chrome API mocking | Manual stubs returning undefined | Stateful in-memory implementations | | SW suspension flakiness | Random 30s timeouts in CI | waitForSWReady() + auto-retry | | Dynamic extension ID | Breaks between launches | Auto-detected from SW URL | | Headless Chrome in CI | Xvfb + 15 flags + trial and error | channel: 'chromium' auto-configured | | Shadow DOM | Playwright can't pierce shadow roots | shadowLocator(['host', '#inner']) | | Alarm testing | setTimeout(3600000) in tests | mockAlarms._fire('alarmName') | | Flaky test detection | Grep through 200 CI log lines | Built-in flakiness score tracking | | Extension launch boilerplate | 40+ lines of launchPersistentContext | One fixture: extContext |


CI

# .github/workflows/test.yml (auto-generated by `extrig init`)
name: Extension Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'pnpm' }
      - run: pnpm install
      - run: pnpm build
      - run: pnpm exec playwright install chromium --with-deps
      - run: pnpm extrig test --unit
      - run: pnpm extrig test --e2e
        env: { CI: true }

Configuration

extrig.config.ts:

import { defineConfig } from 'extrig';

export default defineConfig({
  extensionPath: 'dist',         // path to built extension
  headless: true,                // run Chrome headless (default in CI)
  retries: 2,                    // retry failed tests
  workers: 1,                    // Playwright workers (extensions need 1)
  viewport: { width: 1280, height: 720 },
  launchTimeout: 60_000,
  swTimeout: 30_000,
  extraBrowserArgs: [],          // extra Chrome flags
  testMatch: ['tests/e2e/**/*.test.ts'],
  unitTestMatch: ['tests/unit/**/*.test.ts'],
  reporter: ['terminal', 'json'], // 'html', 'studio' also available
  studio: {                      // Extrig Studio (Pro)
    projectId: 'your-project-id',
    apiKey: 'your-api-key',
  },
});

Extrig Studio (Pro)

Track every test run, eliminate flakiness, and ship with confidence. Learn more →


Packages in this ecosystem

| Package | npm | Purpose | |---|---|---| | extrig | npm | Core CLI + test runner | | extrig-vscode | VS Code Marketplace | VS Code extension |


License

MIT © SRoyLabs