Use when writing or debugging Playwright tests for a Chromium browser extension: the persistent-context fixture that loads the extension and finds its ID, why an edit seems to have no effect (reload matrix), and popup, content-script, storage and MV3 service-worker tests.
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Manifest V3 (MV3) is the current packaging contract for Chromium-family browser extensions; Firefox supports it as a peer with a documented divergence list. The manifest is the only declarative input both browsers see - every test surface (background lifecycle, permission prompt, content-script injection, web-accessible-resource fetch) hangs off a manifest field. Knowing which field maps to which runtime behaviour is what lets a test author decide whether a behaviour is testable in unit, integration, or full-browser scope.
This reference backs the fixtures in the main skill and its reload-matrix and storage-tests references. The summary tables below are the quick lookup; exact MV2/MV3 field shapes, the full Firefox/Chrome key matrix, and the offscreen-document
web-ext CLI (Firefox lint / run / build / sign) lives in
web-ext-firefox.md. All sources
are consolidated in the References section.For Playwright-driven MV3 popup / content-script fixtures and assertion recipes see the main skill (Chromium-only) and extension-surface-tests.md. This reference is browser-agnostic, manifest-field-keyed, and covers the Firefox column explicitly.
Summary lookup; the exact MV2/MV3 shapes for background, host_permissions,
and web_accessible_resources are in
manifest-matrix.md.
| Field | MV2 | MV3 | Test implication |
|---|---|---|---|
manifest_version | 2 | 3 | First assertion in any conformance test |
background | { "scripts": [...], "persistent": false } | { "service_worker": "sw.js", "type": "module"? } | Lifecycle test moves from "always running" to "wake on event, terminate idle" |
action / browser_action / page_action | browser_action or page_action | action (unified) | Popup-rendering tests target the unified action slot |
permissions | API + host strings mixed | API strings only | API-permission tests stay; host-permission tests move (see next row) |
host_permissions | (did not exist) | match patterns moved here from permissions | Each host pattern is a runtime permission prompt - testable as a user gesture flow |
optional_host_permissions | (did not exist) | runtime-requestable hosts | Tests must drive permissions.request from a user gesture |
web_accessible_resources | flat string array | array of { resources: [...], matches: [...] } objects | Cross-origin fetch of an extension resource is only allowed from a matching matches pattern |
content_security_policy | string | object with extension_pages / sandbox keys | No inline <script>, no remotely hosted code, no eval - testable via load-time CSP violations |
The MV3 background context is a service worker - not a persistent page - and inherits the standard service-worker constraints plus a few extension-specific ones. The offscreen-document construct and the keep-alive heartbeat are detailed in manifest-matrix.md.
| Constraint | MV2 | MV3 | Test implication |
|---|---|---|---|
DOM / window | available | unavailable | Anything touching DOM moves to an offscreen document (chrome.offscreen.createDocument) |
XMLHttpRequest | available | unavailable - use fetch() | XHR-using test fixtures must be rewritten |
localStorage | available | unavailable - use chrome.storage.local | Tests asserting persisted state must use chrome.storage.* (see storage-tests.md) |
setTimeout / setInterval | reliable | cancelled when worker terminates - use chrome.alarms | Tests timing background work must use alarms, not timers |
| Listener registration | top-level or async | must be synchronous at top level | Async-registered listeners are "not guaranteed to work in Manifest V3" |
| Lifecycle | persistent | ephemeral (start -> run -> terminate, repeated) | Globals reset; storage is source of truth |
Two load-bearing quotes from cr-mig-sw: "Registering a listener asynchronously (for example inside a promise or callback) is not guaranteed to work in Manifest V3," and "[Service workers] are ephemeral, which means they'll likely start, run, and terminate repeatedly."
The full 23-key availability matrix (MV2 / MV3 x Firefox / Chrome) and the
browser_specific_settings.gecko example are in
manifest-matrix.md. The divergences
that most often break a cross-browser test:
externally_connectable, offline_enabled, storage (manifest key) - Chrome
only; gate Chrome assertions on browser detection.browser_specific_settings - Firefox requires it (stable gecko.id for AMO
signing); Chrome silently ignores it.protocol_handlers, sidebar_action, theme_experiment - Firefox only.user_scripts (manifest key) - MV2 only; MV3 replaces it with the
userScripts API.page_action - Firefox keeps a different page-action shape in MV3; Chrome
drops it for the unified action.Manifest V3 is supported generally in Chrome 88 or later, with some replacement
APIs landing after 88. minimum_chrome_version in the manifest pins a floor for
users on stable channels.
The MV2 deprecation timeline lives on a separate Chrome "Manifest V2 support
timeline" page - cite by stable URL
(developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline)
and read live, as dates have shifted multiple times.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Assuming MV3 permissions still accepts host match patterns | Lint passes; runtime drops them silently | Move all host patterns to host_permissions |
Testing background.scripts array under MV3 | Field doesn't exist in MV3; manifest fails to load | Use background.service_worker single string |
Using localStorage in service-worker tests | Throws in MV3 | Use chrome.storage.local (see storage-tests.md) |
Registering chrome.runtime.onMessage inside a promise | Listener may not fire after worker restart in MV3 | Register synchronously at top level |
Testing flat-string web_accessible_resources under MV3 | Resources unreachable from page context | Use object form { resources, matches } |
| Treating Firefox MV3 as Chrome MV3 | externally_connectable, offline_enabled not supported; browser_specific_settings required | Run Firefox tests with web-ext (see web-ext-firefox.md) and gate Chrome-only assertions |
Using setTimeout to delay background work | Cancelled on worker termination | Use chrome.alarms.create |
| Polling via 1s heartbeat to keep SW alive | Chrome explicitly limits keepalive abuse | Restructure to event-driven; offscreen document for long DOM work |
declarativeNetRequest rule shape detail lives on a separate Chrome
migration page (not extracted here) - fetch
developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
before authoring rule-equivalence tests.content_security_policy.extension_pages exact-string
syntax. The MV3 content_security_policy object shape is documented
separately at
developer.chrome.com/docs/extensions/reference/manifest/content-security-policy.browser_specific_settings) - mdn-manifest.web-ext CLI (Firefox lint / run / build / sign) -
web-ext-firefox.md.