--- id: chrome-extensions title: "Chrome extensions" --- :::note Extensions only work in Chrome / Chromium launched with a persistent context. ::: The following is code for getting a handle to the [background page](https://developer.chrome.com/extensions/background_pages) of a [Manifest v2](https://developer.chrome.com/docs/extensions/mv2/) extension whose source is located in `./my-extension`: ```js const { chromium } = require('playwright'); (async () => { const pathToExtension = require('path').join(__dirname, 'my-extension'); const userDataDir = '/tmp/test-user-data-dir'; const browserContext = await chromium.launchPersistentContext(userDataDir, { headless: false, args: [ `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}` ] }); let [backgroundPage] = browserContext.backgroundPages(); if (!backgroundPage) backgroundPage = await browserContext.waitForEvent('backgroundpage'); // Test the background page as you would any other page. await browserContext.close(); })(); ``` ```python async import asyncio from playwright.async_api import async_playwright path_to_extension = "./my-extension" user_data_dir = "/tmp/test-user-data-dir" async def run(playwright): context = await playwright.chromium.launch_persistent_context( user_data_dir, headless=False, args=[ f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) if len(context.background_pages) == 0: background_page = await context.wait_for_event('backgroundpage') else: background_page = context.background_pages[0] # Test the background page as you would any other page. await context.close() async def main(): async with async_playwright() as playwright: await run(playwright) asyncio.run(main()) ``` ```python sync from playwright.sync_api import sync_playwright path_to_extension = "./my-extension" user_data_dir = "/tmp/test-user-data-dir" def run(playwright): context = playwright.chromium.launch_persistent_context( user_data_dir, headless=False, args=[ f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) if len(context.background_pages) == 0: background_page = context.wait_for_event('backgroundpage') else: background_page = context.background_pages[0] # Test the background page as you would any other page. context.close() with sync_playwright() as playwright: run(playwright) ``` ## Testing To have the extension loaded when running tests you can use a test fixture to set the context. You can also dynamically retrieve the extension id and use it to load and test the popup page for example. First, add fixtures that will load the extension: ```js title="fixtures.ts" import { test as base, expect, chromium, type BrowserContext } from '@playwright/test'; import path from 'path'; export const test = base.extend<{ context: BrowserContext; extensionId: string; }>({ context: async ({ }, use) => { const pathToExtension = path.join(__dirname, 'my-extension'); const context = await chromium.launchPersistentContext('', { headless: false, args: [ `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`, ], }); await use(context); await context.close(); }, extensionId: async ({ context }, use) => { /* // for manifest v2: let [background] = context.backgroundPages() if (!background) background = await context.waitForEvent('backgroundpage') */ // for manifest v3: let [background] = context.serviceWorkers(); if (!background) background = await context.waitForEvent('serviceworker'); const extensionId = background.url().split('/')[2]; await use(extensionId); }, }); export const expect = test.expect; ``` ```python title="conftest.py" from typing import Generator from pathlib import Path from playwright.sync_api import Playwright, BrowserContext import pytest @pytest.fixture() def context(playwright: Playwright) -> Generator[BrowserContext, None, None]: path_to_extension = Path(__file__).parent.joinpath("my-extension") context = playwright.chromium.launch_persistent_context( "", headless=False, args=[ f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) yield context context.close() @pytest.fixture() def extension_id(context) -> Generator[str, None, None]: # for manifest v2: # background = context.background_pages[0] # if not background: # background = context.wait_for_event("backgroundpage") # for manifest v3: background = context.service_workers[0] if not background: background = context.wait_for_event("serviceworker") extension_id = background.url.split("/")[2] yield extension_id ``` Then use these fixtures in a test: ```js import { test, expect } from './fixtures'; test('example test', async ({ page }) => { await page.goto('https://example.com'); await expect(page.locator('body')).toHaveText('Changed by my-extension'); }); test('popup page', async ({ page, extensionId }) => { await page.goto(`chrome-extension://${extensionId}/popup.html`); await expect(page.locator('body')).toHaveText('my-extension popup'); }); ``` ```python title="test_foo.py" from playwright.sync_api import expect, Page def test_example_test(page: Page) -> None: page.goto("https://example.com") expect(page.locator("body")).to_contain_text("Changed by my-extension") def test_popup_page(page: Page, extension_id: str) -> None: page.goto(f"chrome-extension://{extension_id}/popup.html") expect(page.locator("body")).to_have_text("my-extension popup") ``` ## Headless mode By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using the following code: ```js title="fixtures.ts" // ... const pathToExtension = path.join(__dirname, 'my-extension'); const context = await chromium.launchPersistentContext('', { headless: false, args: [ `--headless=new`, // the new headless arg for chrome v109+. Use '--headless=chrome' as arg for browsers v94-108. `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`, ], }); // ... ``` ```python title="conftest.py" path_to_extension = Path(__file__).parent.joinpath("my-extension") context = playwright.chromium.launch_persistent_context( "", headless=False, args=[ "--headless=new", # the new headless arg for chrome v109+. Use '--headless=chrome' as arg for browsers v94-108. f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) ```