Extension
The extension fixture is available in your tests and lets you open extension pages, run code in the
service worker, access storage, and manage the extension's lifecycle.
launchWithExtension() returns the same API for custom fixtures.
Properties
context
The persistent Chromium context hosting the extension. The extension fixture creates one per test.
With launchWithExtension(), close it through await extension.close() to release the browser
and temporary extension copies.
Usage
const page = await extension.context.newPage();
Arguments
None.
Returns
hasServiceWorker
Returns whether the loaded build declares a service worker, or undefined before installation.
A declared worker may currently be stopped or disabled. The value refreshes on install and upgrade.
Usage
expect(extension.hasServiceWorker).toBe(false);
Returns
boolean | undefined
id
The installed extension's ID. The ID is preserved during unpacked upgrades.
Usage
const extensionId = extension.id;
Arguments
None.
Returns
string
ready
Whether initial installation and manifest acquisition have completed. Starts as false with deferred
installation, becomes true once ready, and stays true while disabled. Successful uninstall or browser
context closure sets it to false. It does not query changes made outside this instance.
Usage
if (!extension.ready) await extension.install();
Returns
boolean
manifest
A resolved manifest snapshot read through chrome.runtime.getManifest() when the extension becomes
ready. Uses the service worker when declared, or a temporary extension document otherwise. The
temporary document is closed after reading.
Installation, reload, upgrade, and re-enabling refresh the snapshot.
Usage
expect(extension.manifest.version).toBe('1.0.0');
Arguments
None.
Returns
chrome.runtime.ManifestV3
optionsUrl
The full extension URL of options_ui.page, falling back to options_page, from the manifest
snapshot. Throws when neither is declared or the extension is not ready.
Usage
const optionsUrl = extension.optionsUrl;
Arguments
None.
Returns
string (readonly)
popupUrl
The full extension URL of action.default_popup from the manifest snapshot. Throws when no popup
is declared or the extension is not ready.
Usage
const popupUrl = extension.popupUrl;
Arguments
None.
Returns
string (readonly)
sidePanelUrl
The full extension URL of side_panel.default_path from the manifest snapshot. Throws when no
default path is declared or the extension is not ready. Ignores runtime chrome.sidePanel.setOptions()
overrides, including per-tab settings.
Usage
const sidePanelUrl = extension.sidePanelUrl;
Arguments
None.
Returns
string (readonly)
storage
Provides access to chrome.storage.local, sync, session, and the read-only managed area through
the current worker. See ExtensionStorage for methods.
Usage
await extension.storage.local.set({ theme: 'dark' });
Arguments
None.
Returns
worker
Looks up the currently running extension service worker on each access. Throws when no worker is available, including after disabling or uninstalling. Read it again after a reload, upgrade, or enable operation instead of retaining an old handle.
Usage
const worker = extension.worker;
Arguments
None.
Returns
Methods
close
Closes the browser context and removes temporary extension copies. Use this to release instances
created with launchWithExtension(); the built-in extension fixture handles teardown automatically.
Cleanup still runs if closing the context fails. Repeated calls await the same teardown and report its result.
Usage
await extension.close();
Returns
Promise<void>
disable
Disables the installed extension and waits for its worker to stop. Worker evaluation and storage access are unavailable while the extension is disabled.
Usage
await extension.disable();
Arguments
None.
Returns
Promise<void>
dispatch
Emulates a Chrome event in the current service worker. Use it to exercise event handlers without staging a browser action or another extension build.
Usage
await extension.dispatch('runtime.onInstalled', {
reason: 'update',
previousVersion: '1.0',
});
Event names are relative to chrome, with TypeScript checking the event's listener arguments.
Events can take zero or multiple arguments:
await extension.dispatch('runtime.onStartup');
await extension.dispatch('storage.onChanged', { theme: { newValue: 'dark' } }, 'local');
Dispatch only notifies listeners in the current worker. For example, dispatching storage.onChanged
does not change storage. Requires a running worker, an available event with Chromium's internal
dispatch method, and arguments supported by Playwright's
evaluation serialization.
Callbacks and ports cannot be passed from the test process.
The call does not wait for listeners to register. Use waitForFunction()
before dispatching when listeners may still be registering, such as after a worker reload:
await extension.waitForFunction(() => chrome.runtime.onInstalled.hasListeners());
await extension.dispatch('runtime.onInstalled', {
reason: 'update',
previousVersion: '1.0',
});
hasListeners() checks whether at least one listener is registered; it does not guarantee that all
application listeners are ready. Dispatch does not wait for asynchronous listener work to finish;
use retrying assertions for resulting changes. Dispatch is not retried, including if a listener
closes the worker.
Arguments
name(ExtensionEventName) — Chrome event path, such asruntime.onInstalledorstorage.local.onChanged....args(ExtensionEventArgs<typeof name>) — Arguments accepted by the event's listener.
Returns
Promise<void> — Resolves after dispatch returns. Rejects if the worker or event is unavailable,
or the event does not expose a callable dispatch method.
enable
Enables the installed extension, waits for readiness, and refreshes the manifest. If already enabled, returns without restarting the worker.
Usage
await extension.disable();
await extension.enable();
Arguments
None.
Returns
Promise<void>
enableDevMode
Enables Chromium's Developer mode for esxtensions. Requires an installed extension, but works while it is disabled.
Usage
await extension.enableDevMode();
Resolves when the setting is applied. Usefull when your extension triggers reload internally with chrome.runtime.reload().
Arguments
None.
Returns
Promise<void>
evaluate
Evaluates a function or expression in the current service worker. Follows
Worker.evaluate(), including its
argument serialization and return-value behavior. Functions run outside the test's JavaScript scope;
pass values as the optional argument.
Usage
await extension.evaluate(() => chrome.tabs.create({ url: 'https://example.com' }));
Requires a running worker and the permissions needed by the Chrome API being called. A worker has
no page DOM; use a Playwright Page to interact with extension UI.
Arguments
pageFunction— Function or string expression to evaluate. SeeWorker.evaluate().arg— Optional argument passed to the function, using Playwright's evaluation argument serialization.
Returns
Promise<R> — Resolves to the evaluated result, awaiting it if it is a promise.
getURL
Resolves a resource path under chrome-extension://<id>/. A leading slash is accepted.
Does not emulate dynamic URLs from
web_accessible_resources entries with use_dynamic_url.
Usage
const url = extension.getURL('settings/advanced.html');
Arguments
path(string, optional) — Resource path inside the extension. Defaults to''.
Returns
string — The fully qualified extension resource URL.
install
Installs extensionPath when no path is supplied. With a custom path, installs a private copy of
that build so it can later be replaced by upgrade().
Refreshes the manifest and waits for the extension worker if one is declared. It does not wait for
all application startup handlers to finish. Use with extensionAutoInstall: false
to control initial installation. Each instance supports one successful installation;
calling install() again, including after uninstalling, rejects.
Usage
await extension.install();
See Welcome page for checking pages opened during automatic installation.
Arguments
path(string, optional) — Build directory to install. Defaults to the configuredextensionPath. Explicit relative paths resolve from the current working directory. Callinginstall()without a path uses the original build path; for the built-in fixture, that path is resolved relative to the Playwright configuration file.
Returns
Promise<void>
openOptions
Opens options_ui.page, falling back to options_page, in a new regular tab. Throws when neither
is declared. Ignores options_ui.open_in_tab and does not invoke
chrome.runtime.openOptionsPage().
Usage
const optionsPage = await extension.openOptions();
See the Options page guide for differences from embedded options.
Arguments
None.
Returns
Promise<Page>
openPopup
Opens action.default_popup in a new regular tab and navigates to its extension URL. Throws when the
manifest has no popup declaration.
Usage
const popupPage = await extension.openPopup();
This hosts the popup document in a tab, not the native toolbar popup. See the Popup guide for active-tab, focus, and permission differences.
Arguments
None.
Returns
Promise<Page>
openSidePanel
Opens side_panel.default_path in a new regular tab and returns it after navigation. Throws before
creating a tab when the manifest has no default path. Ignores runtime side panel configuration.
Usage
const sidePanelPage = await extension.openSidePanel();
See the Side panel guide for differences from the native panel.
Arguments
None.
Returns
Promise<Page>
reload
Reloads the enabled extension, refreshes the manifest, and waits for a replacement service worker if one is declared. Works without a background worker. Enables Chromium's Developer mode so the unpacked extension can reload. Rejects while the extension is disabled.
Usage
await extension.reload();
Arguments
None.
Returns
Promise<void>
setFileAccess
Controls Chromium's Allow access to file URLs setting.
Usage
await extension.setFileAccess(true);
Arguments
allowed(boolean) — Whether to allow access tofile://URLs.
Returns
Promise<void>
uninstall
Uninstalls the extension. Subsequent worker and storage operations are unavailable. Cached id and
manifest values do not prove that the extension is still installed.
Usage
await extension.uninstall();
See Uninstall for checking the feedback page.
Arguments
None.
Returns
Promise<void>
upgrade
Updates the installed extension with the build at the configured extensionPath and reloads it.
Preserves the extension ID and profile state, refreshes the manifest, and waits for a worker if
the new build declares one. Supports upgrades that add or remove a background worker. Requires a
prior install(path) with a custom path; an extension instance supports one upgrade. Repeated calls
reject.
Usage
await extension.install('./dist-old');
await extension.upgrade();
Use this inside a test with automatic installation disabled. See Migration for a complete example, including waiting for data migration.
Arguments
None.
Returns
Promise<void>
waitForFunction
Polls a function or expression in the current service worker until its evaluated result is truthy. Asynchronous results are awaited on each attempt. Retries while a declared worker is unavailable. Rejects immediately if the installed manifest declares no worker; evaluation errors also reject the call.
Usage
Wait for settings saved asynchronously by the extension (requires the storage permission):
const theme = await extension.waitForFunction(
async (key) => (await chrome.storage.local.get(key))[key],
'theme',
{ timeout: 5_000 },
);
expect(theme).toBe('dark');
Arguments
pageFunction— Function or string expression, with the same serialization asevaluate().arg— Optional argument passed to the function. Passundefinedto supply options without an argument.options(object, optional) —timeoutandintervals, followingpoll(), including its defaults.
Returns
Promise<Awaited<R>> — Resolves to the first truthy evaluated value, rather than the JSHandle
returned by Playwright's page.waitForFunction(). Rejects if the polling timeout expires.
pollForPage
Waits for a page in extension.context matching a URL, regular expression, URL pattern, or predicate.
Relative paths, including paths
with a leading slash, resolve under the extension URL. Absolute URLs can match any page in the
context by exact resolved URL. Checks both existing pages and pages that open or navigate later. If several pages match, the first matching page is returned.
Usage
const welcomePage = await extension.pollForPage('welcome.html');
const feedbackPage = await extension.pollForPage(/feedback/);
For pattern matching, pass a URLPattern instance or a raw URLPatternInit object. This requires
global URLPattern support, available in Node.js 24+. The package does not include a polyfill.
// Match /welcome.html on any host, allowing any query string or hash.
const page = await extension.pollForPage({ pathname: '/welcome.html' });
Raw objects are passed to new URLPattern() without an implicit base URL. Set baseURL to
extension.getURL() to scope the pattern to the extension:
// Match the extension's welcome page, allowing any query string or hash.
const welcomePage = await extension.pollForPage({
pathname: '/welcome.html',
baseURL: extension.getURL(),
});
For custom matching, pass a predicate that receives the Playwright Page instance. The predicate
runs in your test and is polled until it returns true; asynchronous results are awaited.
const welcomePage = await extension.pollForPage(async (page) => (await page.title()) === 'Welcome');
Arguments
urlOrPredicate(string | RegExp | URLPattern | URLPatternInit | ((page: Page) => boolean | Promise<boolean>)) — Extension-relative path, absolute URL, regular expression, URL pattern, pattern initialization object, or predicate to match.options(object, optional):timeout(number, optional) — Polling timeout in milliseconds.intervals(number[], optional) — Delays between polling attempts in milliseconds.
Both options follow poll(), including its defaults.
Returns
Promise<Page> — Resolves to the matching page.
Rejects if no matching page appears before the timeout, the predicate throws or rejects, or the
pattern is invalid or unsupported by the runtime.
pollForPageClosed
Waits until a Playwright page is closed. Also succeeds if the page has already closed, so you can call it after the action that closes the page.
Usage
const popupPage = await extension.openPopup();
await popupPage.getByRole('button', { name: 'Close' }).click();
await extension.pollForPageClosed(popupPage);
Arguments
page(Page) — Page to wait for.options(object, optional) —timeoutandintervals, followingpoll(), including its defaults.
Returns
Promise<void> — Resolves when the page is closed. Rejects if the polling timeout expires.