Testland
Browse all skills & agents

manifest-v3-test-surface-reference

Pure-reference catalog of the Manifest V3 test surface for Firefox + Chromium browser extensions. Maps each manifest field that changed from MV2 (manifest_version, background.service_worker vs background.scripts, action vs browser_action / page_action, host_permissions split, web_accessible_resources object-form, content_security_policy object-form), the runtime restrictions service workers impose (no DOM, no XMLHttpRequest, no localStorage, ephemeral lifecycle, synchronous listener registration, alarms instead of setTimeout), and the Firefox-vs-Chrome key matrix (browser_specific_settings.gecko, externally_connectable / offline_enabled gaps, MV2-only user_scripts manifest key). Use as the manifest-surface reference when authoring extension tests across both browsers.

manifest-v3-test-surface-reference

Overview

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 skill is the pure reference consumed by the per-tool wrappers in this plugin (web-ext-cli-mozilla, chrome-extension-test-loader, playwright-extension-fixtures) and the two builders (mv2-to-mv3-migration-test-checklist, extension-storage-test-author).

For Playwright-driven MV3 popup / content-script fixtures see qa-modern-web/browser-extension-tests. That skill is a Chromium-only, popup + content-script + service- worker-fixture skill. This reference is browser-agnostic, manifest- field-keyed, and covers the Firefox column explicitly.

When to use

  • Authoring a new MV3 extension test - which manifest fields exist, which produce permission prompts, which are Firefox-only.
  • Migrating an existing MV2 test suite - the field-for-field rename table.
  • Triaging a behaviour gap between Chrome and Firefox - the divergence matrix.
  • Reviewing a PR that adds a new manifest field - does it need a Firefox counterpart, a host-permission consequence, or a CSP adjustment.

The manifest fields that change between MV2 and MV3

Per Update the manifest and Manifest V3 migration overview:

FieldMV2MV3Test implication
manifest_version23First 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_actionbrowser_action or page_actionaction (unified)Popup-rendering tests target the unified action slot
permissionsAPI + host strings mixedAPI strings onlyAPI-permission tests stay; host-permission tests move (see next row)
host_permissions(did not exist)match patterns moved here from permissionsEach host pattern is a runtime permission prompt - testable as a user gesture flow
optional_host_permissions(did not exist)runtime-requestable hostsTests must drive permissions.request from a user gesture
web_accessible_resourcesflat string arrayarray of { resources: [...], matches: [...] } objectsCross-origin fetch of an extension resource is only allowed from a matching matches pattern
content_security_policystringobject with extension_pages / sandbox keysNo inline <script>, no remotely hosted code, no eval - testable via load-time CSP violations

background field - exact shape

MV2 (per Update the manifest and Migrate to a service worker):

{
  "background": {
    "scripts": ["backgroundContextMenus.js", "backgroundOauth.js"],
    "persistent": false
  }
}

MV3 (per Migrate to a service worker):

{
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
}

The MV3 service_worker field is a single string (not an array); type is optional and only valid as "module". The MV2 persistent flag is removed entirely.

host_permissions split

Per Update the manifest:

"Host permissions in Manifest V3 are a separate field; you don't specify them in "permissions" or in "optional_permissions"."

MV2:

"permissions": ["tabs", "bookmarks", "https://www.blogger.com/"],
"optional_permissions": ["unlimitedStorage", "*://*/*"]

MV3:

"permissions": ["tabs", "bookmarks"],
"optional_permissions": ["unlimitedStorage"],
"host_permissions": ["https://www.blogger.com/"],
"optional_host_permissions": ["*://*/*"]

"content_scripts[].matches" is unchanged between MV2 and MV3.

web_accessible_resources shape change

Per Update the manifest:

MV2 (flat string array):

"web_accessible_resources": [
  "images/*",
  "style/extension.css",
  "script/extension.js"
]

MV3 (array of objects, each scoping resources to URL patterns or extension IDs):

"web_accessible_resources": [
  { "resources": ["images/*"], "matches": ["*://*/*"] },
  {
    "resources": ["style/extension.css", "script/extension.js"],
    "matches": ["https://example.com/*"]
  }
]

Test implication: a page-context fetch(chrome.runtime.getURL('...')) that worked under MV2 may 404 under MV3 if the requester's origin isn't covered by a matches pattern.

Service-worker runtime restrictions (MV3-only test surface)

Per Migrate to a service worker, the background context in MV3 is a service worker - not a persistent page - and inherits the standard service-worker constraints plus a few extension-specific ones:

ConstraintMV2MV3Test implication
DOM / windowavailableunavailableAnything touching DOM moves to an offscreen document (chrome.offscreen.createDocument)
XMLHttpRequestavailableunavailable - use fetch()XHR-using test fixtures must be rewritten
localStorageavailableunavailable - use chrome.storage.localTests asserting persisted state must use chrome.storage.* (see extension-storage-test-author)
setTimeout / setIntervalreliablecancelled when worker terminates - use chrome.alarmsTests timing background work must use alarms, not timers
Listener registrationtop-level or asyncmust be synchronous at top levelAsync-registered listeners are "not guaranteed to work in Manifest V3"
Lifecyclepersistentephemeral (start → run → terminate, repeated)Globals reset; storage is source of truth

Quote from Migrate to a service worker:

"Registering a listener asynchronously (for example inside a promise or callback) is not guaranteed to work in Manifest V3."

"[Service workers] are ephemeral, which means they'll likely start, run, and terminate repeatedly."

The keep-alive heartbeat pattern (calling chrome.runtime.getPlatformInfo on a ~25s interval to reset the idle timer, or writing to chrome.storage.local every 20s) is documented but Chrome explicitly limits its use to enterprise/education managed extensions, "reserves the right to take action" against others, and notes a waitUntil()-style API is under discussion in the W3C WebExtensions Community Group (WECG).

Offscreen documents

Per Migrate to a service worker, DOM-requiring work in MV3 goes to an offscreen document:

chrome.offscreen.createDocument({
  url: chrome.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

Offscreen documents communicate with the service worker via runtime.sendMessage / runtime.onMessage only - they don't share other extension APIs.

Firefox vs Chrome manifest key matrix

Per MDN manifest.json keys reference, the table below enumerates every documented manifest key with its Firefox / Chrome availability and MV2 / MV3 status. Tests must either gate on browser detection or split into per-browser fixtures when the key appears as "not supported" in one column.

KeyMV2MV3FirefoxChromeTest note
manifest_versionyesyesyesyesMandatory; first assertion
nameyesyesyesyesMandatory
versionyesyesyesyesMandatory; semver in Firefox AMO
actionnoyesyesyesUnified popup slot
browser_actionyesnoyes (MV2 only)yes (MV2 only)Renames to action in MV3
page_actionyesnoyes (MV2; different in MV3)yes (MV2 only)Firefox keeps a different page-action shape
backgroundyesyesyesyesShape changes per above
browser_specific_settingsyesyesyesnoTests asserting extension ID stability rely on this in Firefox
content_scriptsyesyesyesyesSame shape
content_security_policyyesyesyesyesShape changes (object in MV3)
declarative_net_requestyesyesyesyesReplaces webRequest-blocking surface
externally_connectableyesyesnoyesCross-origin runtime messaging - Chrome only
host_permissionsnoyesyesyesNew in MV3; permission-prompt test surface
offline_enabledyesyesnoyesChrome-only manifest key
optional_host_permissionsnoyesyesyesRuntime host grants
optional_permissionsyesyesyesyesRuntime API grants
permissionsyesyesyesyesAPI permissions only in MV3
protocol_handlersyesyesyes (Firefox only)noFirefox-only register-protocol surface
sidebar_actionyesyesyes (Firefox/Opera)noSidebar UI - not in Chrome
storage (as manifest key)yesyesnoyesNote: the storage API works in both
theme_experimentyesyesyes (Firefox only)noExperimental theming
user_scripts (manifest key)yesnoyes (MV2 only)yes (MV2 only)MV3 replaces with userScripts API
web_accessible_resourcesyesyesyesyesShape changes (object array in MV3)

browser_specific_settings.gecko

Per MDN manifest.json keys reference, Firefox- specific metadata (extension ID, min Firefox version) lives under browser_specific_settings.gecko. Chrome silently ignores this key:

{
  "browser_specific_settings": {
    "gecko": {
      "id": "@addon-example",
      "strict_min_version": "42.0"
    }
  }
}

Test note: AMO ([addons.mozilla.org]) submission validation requires a stable gecko.id for signing - a test asserting that the built zip carries a deterministic ID prevents accidental ID drift across builds.

MV3 platform availability

Per Manifest V3 migration overview: "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 itself 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-patterns

Anti-patternWhy it failsFix
Assuming MV3 permissions still accepts host match patternsLint passes; runtime drops them silentlyMove all host patterns to host_permissions per cr-mig-manifest
Testing background.scripts array under MV3Field doesn't exist in MV3; manifest fails to loadUse background.service_worker single string per cr-mig-sw
Using localStorage in service-worker testsThrows in MV3Use chrome.storage.local (see extension-storage-test-author)
Registering chrome.runtime.onMessage inside a promiseListener may not fire after worker restart in MV3Register synchronously at top level per cr-mig-sw
Testing flat-string web_accessible_resources under MV3Resources unreachable from page contextUse object form { resources, matches } per cr-mig-manifest
Treating Firefox MV3 as Chrome MV3externally_connectable, offline_enabled not supported; browser_specific_settings requiredRun Firefox tests with web-ext (see web-ext-cli-mozilla) and gate Chrome-only assertions
Using setTimeout to delay background workCancelled on worker terminationUse chrome.alarms.create per cr-mig-sw
Polling via 1s heartbeat to keep SW aliveChrome explicitly limits keepalive abuseRestructure to event-driven; offscreen document for long DOM work

Limitations

  • Firefox MV3 parity is incomplete. Several MV3 APIs landed on Firefox after Chrome; check MDN's per-API browser-compat tables before asserting cross-browser behaviour.
  • 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.
  • No coverage of 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.
  • Edge / Opera / Brave coverage. Chromium-fork browsers track Chrome's MV3 timeline but may apply different store-side policies (Edge add-ons store, Opera add-ons). Cite the upstream Chrome source and add per-store rerun if needed.

References