Skip to content

Latest commit

 

History

History
90 lines (74 loc) · 5.52 KB

testing.md

File metadata and controls

90 lines (74 loc) · 5.52 KB

Testing without a browser

@addon-core/browser/testing is a framework-agnostic test kit for code that uses @addon-core/browser. It provides deterministic fixtures, configurable methods and events, and a stateful in-memory browser harness. Importing the testing subpath does not install globals or otherwise change the process.

Quick start with plain Node

import assert from "node:assert/strict";
import {getManifest, queryTabs} from "@addon-core/browser";
import {
    createBrowserHarness,
    createManifestFixture,
    createTabFixture,
    installBrowserGlobals,
} from "@addon-core/browser/testing";

const harness = createBrowserHarness({
    manifest: createManifestFixture({name: "Consumer extension"}),
    tabs: [createTabFixture({active: true, id: 7})],
});
const restore = installBrowserGlobals(harness, {profile: "chrome"});

try {
    assert.equal(getManifest().name, "Consumer extension");
    assert.deepEqual(
        (await queryTabs({active: true})).map(tab => tab.id),
        [7]
    );
} finally {
    restore();
}

The production functions are imported from @addon-core/browser and execute unchanged. Only the browser globals they read are supplied by the test.

What the kit provides

  • Fixtures create fresh, deterministic Chrome-typed data.
  • Primitives provide runner-independent methods and browser events.
  • Harness and globals provide browser profiles, state, capabilities, reset, and exact global restoration.
  • URL patterns and host permissions cover wildcard tab queries and granted-origin checks.
  • Contexts and documents provide a shared registry, context-local subscriptions and cleanup.
  • Storage provides isolated areas, managed policy fixtures, change events and real @addon-core/storage examples.
  • Offscreen shares context state with runtime queries and supports controlled creation, closure and cleanup.
  • Context messaging routes messages between registered extension contexts and content-script frames.
  • Scripting targets and executors selects registered documents and uses an explicitly supplied executor; no code runs implicitly.
  • Node execution provides an opt-in testing/node executor with fresh VM realms and data-only globals.
  • Persistent Node runtime adds classic-script bootstrap, document/world state and optional document lifecycle binding.
  • Guest virtual clocks explicitly controls VM timers and time sources without patching host globals.
  • Jest usage shows how to combine the kit with Jest without making the kit depend on Jest.
  • Limitations describes intentional differences from real browsers.

Choosing the right kind of test double

A fixture is only data. A configurable stub records calls and returns or throws exactly what the test configured. A stateful fake models a small documented part of browser behavior, such as tabs and permissions. A real-browser integration test runs the extension in Chrome, Firefox, Safari, or another browser.

Use this kit for deterministic unit and integration tests around your application code. Keep real-browser tests for browser compatibility, manifest behavior, security boundaries, lifecycle timing, and vendor-specific behavior.

The package's own production-wrapper tests run in actual extensions under tests/browser/. After building, run npm run test:browser -- --browser chromium --api search --binary /path/to/browser or select firefox. This shared infrastructure groups scenarios by API and uses a fresh extension/profile for each permission set.

Search methods are configurable through harness.configurable.active.search: query models callback/Promise calls, while Firefox's get and search use Promise-only calls. Configure an engine list with get.setResult(engines) and operation completion with query.setResult(undefined) or search.setResult(undefined). These stubs do not navigate tabs or simulate a search provider. The configurable surface is a cross-browser superset; use harness.capabilities.set("search.get", false) and harness.capabilities.set("search.search", false) to model Chromium's missing engine APIs, or install globals without the namespace to model Safari.

Top Sites is configurable through harness.configurable.active.topSites.get.setResult(sites). The Chromium facade models callback calls and the Firefox facade supports callback/Promise calls, including the options overload. This stub returns configured data; it does not rank browsing history or implement Firefox's filtering. Configure native failures with failNext(error) and remove the capability to model an unavailable API. See Top Sites browser coverage for the real-browser scenarios.

Reset and isolation

Every harness owns independent state. Call harness.reset() between tests when reusing one harness, or create a fresh harness per test. Always call the restore function returned by installBrowserGlobals() in finally; it is safe to call more than once and restores the original property descriptors. Restore nested installations in reverse order. Use environment: "preserve" in jsdom or when the test already owns window, document, location, or navigator; see preserving an existing environment.