Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Use Playwright for the most direct Python workflow: launch Playwright’s bundled Chromium with a persistent context, load the unpacked extension with Chromium flags, then test the page, popup, and Manifest V3 service worker as separate targets. Selenium can also install an extension, but Chrome’s documented Selenium path has important service-worker inspection and lifecycle limitations.
Choose the target you are actually testing
A browser extension can affect several different surfaces. Decide which one your test must exercise before writing selectors or waiting logic.
| Target | What to verify | Best access pattern |
|---|---|---|
| Ordinary web page | Injected content scripts, modified DOM, blocked requests, or user-visible changes | Open a normal URL and assert the resulting page behavior |
| Extension popup | Controls shown when the toolbar icon is opened | Use the library’s popup-opening capability when available, or navigate to the popup document |
| Manifest V3 service worker | Background events, message handling, alarms, and lifecycle-sensitive logic | Obtain the worker target and inspect it only when the test requires internals |
| Options or other extension page | Settings and internal UI | Navigate to its chrome-extension:// URL after discovering the extension ID |
Chrome for Developers describes the goal of an automation tool as communicating with the browser to test the same flows a user would go through. In practice, make user-visible assertions your default; internal worker checks should be reserved for behavior that cannot be observed through the page or extension UI.
Recommended setup: Playwright with Python
Install Playwright and its Chromium build
- Create and activate a virtual environment.
- Install the Python package:
pip install playwright. - Download the browser binaries:
playwright install chromium.
Playwright’s official extension recipe requires a persistent context. Use the Chromium bundled with Playwright rather than assuming that system Google Chrome or Microsoft Edge can load the extension: those browsers removed the command-line flags needed for this side-loading workflow. The documented headless-capable route uses channel="chromium"; run headed when you need to watch the browser during debugging.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
Minimal end-to-end test of an unpacked extension
The example below assumes an unpacked extension directory at ./my-extension. Its manifest should already be valid, and the directory must contain the files referenced by that manifest.
from pathlib import Path
from playwright.sync_api import sync_playwright
EXTENSION_DIR = Path(__file__).parent.joinpath("my-extension").resolve()
PROFILE_DIR = Path(__file__).parent.joinpath(".pw-profile").resolve()
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir=str(PROFILE_DIR),
channel="chromium",
headless=True,
args=[
f"--disable-extensions-except={EXTENSION_DIR}",
f"--load-extension={EXTENSION_DIR}",
],
)
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://example.com", wait_until="domcontentloaded")
# Replace this with an assertion about your extension's visible effect.
page.locator("body").wait_for()
assert page.title() == "Example Domain"
context.close()
For visual debugging, change headless=True to headless=False. Keep the profile directory dedicated to the test. Reusing a profile that is open in another Chromium process can fail or leak state between tests.
Use a worker event for Manifest V3 background logic
Manifest V3 background code runs in a service worker instead of a persistent background page. Wait for the worker event before deriving the extension ID:
from pathlib import Path
from playwright.sync_api import sync_playwright
extension_dir = Path("my-extension").resolve()
profile_dir = Path(".pw-profile-worker").resolve()
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
str(profile_dir),
channel="chromium",
headless=True,
args=[
f"--disable-extensions-except={extension_dir}",
f"--load-extension={extension_dir}",
],
)
worker = context.service_workers[0] if context.service_workers else None
if worker is None:
with context.expect_event("serviceworker") as worker_info:
# Opening a page gives Chromium a chance to start the extension.
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://example.com", wait_until="domcontentloaded")
worker = worker_info.value
extension_id = worker.url.split("/")[2]
assert worker.url.startswith(f"chrome-extension://{extension_id}/")
print("Extension ID:", extension_id)
context.close()
The exact startup trigger depends on the extension. If no worker appears, verify that the manifest is Manifest V3, the service-worker file exists, and the extension actually has background work that causes Chromium to start it.
Recommended Free Tools
Rank #2
Test the popup and extension-owned pages
Navigate directly to the popup document
Once you have the ID, open the popup as a tab. This is deterministic and works when the popup is a normal HTML document:
popup = context.new_page()
popup.goto(
f"chrome-extension://{extension_id}/popup.html",
wait_until="domcontentloaded",
)
popup.get_by_role("button", name="Enable").click()
assert popup.get_by_text("Enabled").is_visible()
Replace popup.html and the locators with the paths and accessible labels in your manifest and UI. A popup opened as a tab does not always have the same active-tab context as a toolbar popup. If your code reads the active tab, pass an explicit tab override or arrange the test so the intended page is active before opening the popup.
Prefer the popup-opening API when your library provides one
Chrome’s extension testing guidance recommends invoking the automation library’s popup-opening capability when available. That more closely represents a user opening the toolbar UI. If your installed Playwright version or test harness does not expose such a method, direct navigation to the popup URL is the documented fallback.
Assert outcomes before implementation details
- For a content script, assert the page element, text, attribute, or behavior a user can see.
- For a blocked request, assert the resulting page state rather than a private background variable.
- For popup actions, assert the setting reflected in the popup or on the active page.
- Inspect service-worker messages, storage, or functions only when no stable user-facing assertion can prove the requirement.
Headless, headed, and CI choices
Headless execution
Playwright’s extension guide points to the chromium channel for headless extension runs. Chrome’s testing documentation specifies the modern --headless=new mode for Chrome-based automation. Browser flags and channel support change, so verify the behavior against the versions pinned by your project.
Headed debugging
Run with headless=False when diagnosing permissions, popup rendering, or timing. Add screenshots and traces around the failing step, but keep the production assertion independent of pixels unless visual output is the requirement.
Reproducible CI browsers
For a Selenium or ChromeDriver pipeline, Chrome recommends Chrome for Testing with a matching ChromeDriver. Pin both versions in CI instead of depending on whatever browser happens to be installed on the runner. On machines without a graphical display, use headless mode. Apply the same principle to Playwright: install its browser version during the build and use a fresh, isolated profile per job.
Selenium as an alternative
Selenium can load an extension through Chrome options or its WebExtension installation interface. The exact API depends on the Selenium and Chrome versions in use, so confirm the current Selenium and Chrome documentation before copying a command into a production suite. A basic Chrome-options shape is:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-extensions-except=/absolute/path/to/my-extension")
options.add_argument("--load-extension=/absolute/path/to/my-extension")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com")
assert driver.title == "Example Domain"
finally:
driver.quit()
Chrome’s documented Selenium guidance says Selenium does not directly access the service worker through that approach. Chrome also notes that ChromeDriver attaches a debugger to service workers, preventing their normal automatic termination during Selenium tests. Consequently, Selenium is suitable for page and UI assertions, but lifecycle-sensitive Manifest V3 tests can behave differently from real worker shutdown and restart.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Selenium’s site also demonstrates WebExtension installation with remote debugging and an enable-unsafe-extension-debugging switch. Treat that as version-sensitive configuration: use the installation route documented for the precise Selenium, Chrome, and ChromeDriver versions you pin.
Playwright and Selenium compared
| Concern | Playwright Python | Selenium |
|---|---|---|
| Loading an unpacked extension | Persistent context plus --disable-extensions-except and --load-extension; official recipe uses bundled Chromium |
Chrome options or WebExtension installation interface; verify version-specific API behavior |
| Headless mode | Use the chromium channel documented for extension testing |
Chrome documents --headless=new |
| Service-worker access | Documented service-worker target and event support | Not directly available through Chrome’s documented Selenium method |
| Worker lifecycle tests | Can observe the worker target, subject to extension startup behavior | ChromeDriver’s debugger attachment prevents normal automatic worker termination |
| Popup testing | Use popup API where available, otherwise navigate to the extension URL | Use the corresponding Selenium window or tab navigation strategy |
| CI reproducibility | Install and pin Playwright’s Chromium build | Pin matching Chrome for Testing and ChromeDriver versions |
Common failures and fixes
“The extension is not loaded”
- Use an absolute path, resolved with
Path.resolve(). - Pass the unpacked directory, not a ZIP file.
- Check that
manifest.jsonis at the directory root and valid. - Ensure no other extension flags contradict the two loading flags.
- Use Playwright’s bundled Chromium; system Chrome and Edge may not support the required side-loading flags.
No service-worker event arrives
- Confirm the manifest uses Manifest V3 and names the correct
background.service_worker. - Trigger an extension action or open a page that causes the worker to start.
- Wait for the event before reading the worker URL; do not assume startup is instantaneous.
- For Selenium, do not expect the same direct worker access or termination behavior documented for Playwright.
The popup behaves differently in a tab
- Check whether the popup reads the active tab, selected tab, or window state.
- Open the intended page first and explicitly provide the tab context if the test framework supports it.
- Use the library’s popup-opening capability for interaction semantics closer to a toolbar click.
Tests pass locally but fail in CI
- Pin browser and driver versions.
- Use a clean profile directory for every test job.
- Run headless only when the runner has no display, and capture logs, screenshots, or traces on failure.
- Replace fixed sleeps with waits for a selector, URL, network condition, or service-worker event.
Selectors are flaky
Prefer roles, labels, and stable data attributes over generated class names. Wait for the state the user needs to see, not an arbitrary delay. Keep extension-internal assertions narrow so a harmless implementation refactor does not invalidate a user-flow test.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is a clean screenshot of a page after extension-like browser automation, ScreenshotNeo provides a single HTTP request rather than a local Chromium harness. It is a website screenshot API and MCP server; it does not replace tests that must install and exercise your extension’s popup or service worker.
For a page capture, see the parameter reference in the ScreenshotNeo documentation and call the API:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots; response headers identify the page verdict and billing result.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up for the free ScreenshotNeo plan to try it without a card.
Operational checklist
- Identify whether the requirement concerns a normal page, popup, options page, or Manifest V3 worker.
- For Playwright, use a persistent context, an isolated profile, and the bundled Chromium channel.
- Load the unpacked directory with both extension flags.
- Wait for service-worker startup before deriving the extension ID.
- Prefer visible behavior assertions; use internals only when necessary.
- Pin browser versions and replace sleeps with state-based waits.
- Document Selenium’s worker-access and lifecycle limitations if your team chooses it.
Frequently Asked Questions
Can I load a packed CRX file with the Playwright recipe?
The documented recipe loads an unpacked extension directory. Unpack the extension and pass the directory containing its manifest and files.
Why does the extension ID change between test runs?
An ID derived from a worker URL should be treated as runtime data. Discover it after the worker starts instead of hard-coding an ID.
Should every test inspect the service worker?
No. Inspect it only for background behavior that cannot be established through a page or extension UI; user-visible assertions are generally less brittle.
Is ScreenshotNeo a replacement for extension interaction tests?
No. It captures web pages and PDFs through an API and MCP tools. Use Playwright or Selenium when the test must install and interact with your extension itself.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

