📄 playwright-python/python/docs/chrome-extensions

File: chrome-extensions.md | Updated: 11/18/2025

Source: https://playwright.dev/python/docs/chrome-extensions

Skip to main content

On this page

Introduction


note

Extensions only work in Chromium when launched with a persistent context. Use custom browser args at your own risk, as some of them may break Playwright functionality.

Google Chrome and Microsoft Edge removed the command-line flags needed to side-load extensions , so use Chromium that comes bundled with Playwright.

The snippet below retrieves the service worker of a Manifest v3 extension whose source is located in ./my-extension.

Note the use of the chromium channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.

  • Sync

  • Async

    from playwright.sync_api import sync_playwright, Playwrightpath_to_extension = "./my-extension"user_data_dir = "/tmp/test-user-data-dir"def run(playwright: Playwright): context = playwright.chromium.launch_persistent_context( user_data_dir, channel="chromium", args=[ f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) if len(context.service_workers) == 0: service_worker = context.wait_for_event('serviceworker') else: service_worker = context.service_workers[0] # Test the service worker as you would any other worker. context.close()with sync_playwright() as playwright: run(playwright)

    import asynciofrom playwright.async_api import async_playwright, Playwrightpath_to_extension = "./my-extension"user_data_dir = "/tmp/test-user-data-dir"async def run(playwright: Playwright): context = await playwright.chromium.launch_persistent_context( user_data_dir, channel="chromium", args=[ f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], ) if len(context.service_workers) == 0: service_worker = await context.wait_for_event('serviceworker') else: service_worker = context.service_workers[0] # Test the service worker as you would any other worker. await context.close()async def main(): async with async_playwright() as playwright: await run(playwright)asyncio.run(main())

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.

Note the use of the chromium channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.

First, add fixtures that will load the extension:

conftest.py

from typing import Generatorfrom pathlib import Pathfrom playwright.sync_api import Playwright, BrowserContextimport 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(        "",        channel="chromium",        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 v3:    service_worker = context.service_workers[0]    if not service_worker:        service_worker = context.wait_for_event("serviceworker")    extension_id = service_worker.url.split("/")[2]    yield extension_id

Then use these fixtures in a test:

test_foo.py

from playwright.sync_api import expect, Pagedef 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")