cwspy.com · July 30, 2026 · 8 min read
How to Test a Chrome Extension Before You Submit It

Testing a Chrome extension before you submit it catches the problems that are cheapest to fix before review and most expensive to fix after: a broken permission, a service worker that silently stops responding, a popup that only works in one theme. This is a practical guide to testing a chrome extension end to end, written for the gap between "it works on my machine" and "it works for the reviewer, and for every user after that."
Chrome extension testing does not need a heavy framework to be effective. Most of what follows is a manual pass you can run consistently before every submission, plus a small automated layer for the handful of things that are genuinely worth automating.
Load and smoke-test before anything else
- Load the unpacked extension from chrome://extensions (developer mode on) and confirm it installs cleanly, with no console errors on load.
- Click through every surface the extension exposes — popup, options page, side panel, any injected UI — once each, on a real page, not a blank tab.
- Check the service worker (or background page) logs specifically; errors there are easy to miss because they do not surface in the popup's own console.
- Reload the extension and repeat the first click-through. A surprising number of bugs only appear on the second load, when stored state from the first one is already present.
What actually varies, and why a matrix beats a to-do list
The reason a linear checklist misses bugs is that extension failures are usually combinations, not single conditions: a permission denied and the service worker restarted, or dark theme and a second window. Before you start clicking, write down the handful of dimensions your extension actually varies across, then test the combinations that are plausible rather than every cell.
- Install state — fresh install, update from the previous version, reinstall after removal.
- Permission state — everything granted, an optional permission denied, a host permission revoked after the fact.
- Page context — a normal page, a page the extension has no host access to, a Chrome internal page, a PDF, an iframe-heavy page.
- Session state — signed in, signed out, token expired mid-session if your extension talks to a backend.
- Environment — light and dark theme, more than one window, a second Chrome profile.
Five dimensions with two or three values each is far more coverage than anyone will test exhaustively by hand. Pick the combinations where the two conditions interact, and let the rest ride on the smoke test.
The checklist that catches what a quick smoke test misses
| Area | What to test | Why it matters |
|---|---|---|
| Permissions | Does the extension still work if you deny an optional permission, and does it fail gracefully rather than crash? | Reviewers and users both notice over-broad or unused permission requests |
| Fresh install vs. update | Test both a clean install and updating from a previous version, especially if you changed stored data formats | Update-path bugs only show up on real users, not fresh test installs |
| Light and dark theme | Check every UI surface in both | A popup readable only in one theme loses half your users without a single bug report explaining why |
| Multiple tabs and windows | Confirm behavior when the extension runs in more than one tab or window simultaneously | State bugs that only appear under concurrency are among the hardest to debug from a support ticket |
| Offline / slow network | Test with network throttled or disabled if the extension talks to any API | A silent hang with no error message reads as "broken" to a user, not "waiting" |
| Uninstall and reinstall | Confirm no orphaned state causes odd behavior on reinstall | Common source of confusing bug reports from returning users |
| Restricted pages | Open the popup on chrome:// pages, the Web Store itself, and a PDF viewer tab | Content scripts cannot run there; the UI should explain that, not throw |
| Long-running sessions | Leave a tab open for an hour with the extension active, then interact with it | Catches the service worker being torn down and state that never came back |
If you only test the happy path — extension loads, one click works, done — you are testing what already worked in development. The checklist above is for the parts that only break in front of a real user.
What is worth automating, and what is not
Most extensions do not need a full test pyramid. They need two thin layers: unit tests around the logic that would be tedious to re-verify by hand, and one or two end-to-end tests that prove the extension still loads and does its main job.
Unit tests for the pure parts
Anything that takes data in and gives data out — a parser, a URL matcher, a settings migration, a formatting helper — should be a plain unit test with no browser involved. This is the cheapest coverage you will ever buy, and the settings-migration case in particular pays for itself the first time you ship a storage format change to existing users.
End-to-end tests for the parts that need a real Chrome
Puppeteer and Playwright can both launch Chrome with an unpacked extension already loaded, which is enough to assert that the popup renders, the content script injects, and the main flow completes. With Playwright it is a persistent context plus two flags:
const context = await chromium.launchPersistentContext('', {
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});Keep this layer small. Two or three end-to-end tests that cover the flows you would be embarrassed to ship broken are worth more than a suite that is slow enough that people stop running it.
Running it in CI
Extension end-to-end tests need a real browser binary, so the job has to install one rather than rely on whatever the runner image has. Beyond that, the useful CI checks are the boring ones: build the package, confirm the manifest parses, confirm the version number was bumped, and fail the build if the packaged output contains source maps or development-only hosts you did not intend to ship.
Testing a Manifest V3 migration specifically
If this test pass follows a Manifest V3 migration, add a few migration-specific checks: does state survive the service worker being unloaded and restarted, does anything that used to rely on blocking webRequest still function under declarativeNetRequest, and did any host_permissions get dropped that a feature still depends on. Our Manifest V3 guide and, for blocker-type extensions specifically, Manifest V3 and ad blockers cover what typically breaks.
The service worker lifecycle deserves its own deliberate test, because it is the single biggest behavioural difference from Manifest V2 and the easiest to miss in development, where you are constantly interacting with the extension and it therefore never idles out. Stop the worker manually from the extensions page, then use the extension again and confirm it recovers rather than silently doing nothing.
Check the cost your extension imposes on the browser
Users rarely file a bug that says "this extension made my browser slow." They remove it and, if you asked, tell you it was heavy — which is why performance belongs in the test pass rather than in a support queue. The three things worth measuring are how much work you do on every page, how much memory you hold onto, and how long your UI takes to appear.
- Content script cost. Open a heavy, script-dense page with the extension on and off, and compare. If your script runs on every page, it should do almost nothing on the pages it does not care about.
- Memory over time. Use the browser task manager after an hour of normal browsing. A number that only ever grows means something is being retained — usually listeners or cached DOM references.
- Time to first paint in the popup. A popup that shows a spinner for a second on every open feels broken even when it is merely slow.
Ship it to a few people before everyone
The last test you cannot run yourself is whether the extension works in the messy profiles real people have — dozens of other extensions installed, unusual settings, a language you never tested in. A small trusted-tester group, or a staged rollout that reaches part of your user base first, converts that unknown into a day of waiting instead of a week of one-star reviews. Watch the ratings and the user count over the first few days rather than assuming silence means success.
Test for the things review actually rejects
A working extension can still fail review, and the recurring reasons are less about bugs than about the gap between what the listing claims and what the package does.
- Permissions you no longer use. Every permission needs a visible feature behind it. Remove the ones left over from an earlier design.
- A purpose that does not match the listing. If the description promises one thing and the extension does another as well, that is a mismatch, not a bonus feature.
- Undisclosed data collection. If anything leaves the browser, it belongs in your privacy disclosure — including anything you send when someone removes the extension. Our post on uninstall tracking and privacy covers what is fair to collect and how to disclose it.
- Remote code. Everything executable has to ship inside the package. A script pulled in at runtime is a rejection, and it is easy to introduce by accident through a dependency.
- Stale screenshots. Not strictly a rejection reason on its own, but a listing showing a UI that no longer exists reads as abandoned to both reviewers and users.
Before you hit submit
- Re-read your manifest permissions list and remove anything the current build no longer needs.
- Test on a second, clean machine or profile if you can — your dev profile has state a fresh install never will.
- Install the packaged build, not the unpacked folder — packaging itself can drop files.
- Confirm the store listing screenshots still match the current UI, not an older version.
- Have one other person click through it — a second pair of eyes catches what familiarity hides from you.
If you also publish on other Chromium-based stores, run the smoke test there too rather than assuming parity. The extension APIs are largely shared, but the review rules, the listing fields, and the default settings users arrive with are not.
None of this replaces automated testing if your extension is complex enough to warrant it, but for most Chrome extensions this manual pass plus a thin automated layer, run consistently before every submission, catches the bugs that actually generate one-star reviews. If you are about to submit for the first time, our publishing guide covers what happens after you hit the button.