Comprehensive Guide to Building Chrome Extensions with React/Vue, Debugging, and E2E Testing
This article explains the overall architecture of Chrome extensions, how to create popup, DevTools, and content scripts using React or Vue, configure manifest.json, set up multi‑entry Webpack builds, debug each component, and perform end‑to‑end testing with Puppeteer and Xvfb for CI environments.
The article begins by outlining what readers will learn: the overall Chrome extension architecture, how to develop a popup and DevTools extension, using front‑end frameworks (React/Vue), debugging techniques, and E2E testing with Puppeteer.
Overall Architecture
Chrome extensions are special web pages. Key concepts include plugin page, target page, popup, DevTools, and active tab.
manifest.json
The manifest file is analogous to package.json and defines the extension’s name, description, version, permissions, background service worker, and content scripts.
{
"name": "Hello Extensions",
"description": "An introductory tutorial",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_popup": "index.html",
"default_title": "Garfish Module",
"default_icon": {"16": "favicon.ico", "48": "favicon.ico", "128": "favicon.ico"}
},
"permissions": ["storage", "scripting"],
"host_permissions": ["
"],
"background": {"service_worker": "background.js"},
"content_scripts": [{
"js": ["content.js"],
"matches": ["
"],
"run_at": "document_idle"
}]
}content_script
Content scripts run in the context of target pages, can read and modify the DOM, and communicate with the extension via chrome.runtime.sendMessage and chrome.runtime.onMessage .
// send from service worker or content script
chrome.runtime.sendMessage(data);
// receive in the counterpart
chrome.runtime.onMessage.addListener(() => {});service_worker (background)
The background service worker runs in a separate thread, listens to browser events, and cannot access the target page DOM.
popup
The popup is the UI shown when the user clicks the extension icon; it is a regular web page.
DevTools
DevTools extensions add custom panels via chrome.devtools.panels.create .
chrome.devtools.panels.create(
"DevPanel",
"panel.png",
"../index.html",
function(panel) {
console.log("Custom panel created!");
}
);Communication
Because content scripts and the service worker run in isolated contexts, messages are often routed through specific tabs using chrome.tabs.query and chrome.tabs.sendMessage .
// get active tab and send a message to its content script
chrome.tabs.query({active: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, response => {
console.log("background -> content script infos have been sent");
});
});Building Your Own Extension
A minimal popup extension consists of manifest.json , background/index.js , index.html , index.js , and scripts/index.js . Load the folder in Chrome’s extension manager.
Using Front‑End Frameworks
To develop with React or Vue, create a project with create‑react‑app , eject the config, and treat the service worker, content script, and DevTools as separate entry points in a multi‑entry Webpack build.
// webpack entry configuration (production)
entry: {
main: paths.appIndexJs,
devtools: paths.devtools,
background: paths.background,
content_script: paths.content_script
}Adjust HtmlWebpackPlugin to generate both index.html and devtools.html , and disable content‑hash for predictable filenames.
Debugging
Popup: right‑click the extension icon → Inspect.
DevTools panel: open Chrome DevTools, then open the custom panel.
Content script: use the target page’s console.
Service worker: open the extension’s background page in Chrome’s extensions UI.
E2E Testing with Puppeteer
Puppeteer automates the browser to test the extension. The script loads the extension, extracts its ID from the service worker URL, and navigates to chrome-extension://{extensionId}/index.html .
const puppeteer = require('puppeteer');
async function bootstrap({appUrl}) {
const extensionPath = 'xxx';
const browser = await puppeteer.launch({
headless: false,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`
]
});
// ... obtain extensionId from service_worker target ...
const extensionUrl = `chrome-extension://${extensionId}/index.html`;
const extPage = await browser.newPage();
await extPage.goto(extensionUrl, {waitUntil: 'load'});
return {appPage, browser, extensionUrl, extPage};
}
module.exports = {bootstrap};Running Tests in CI
In headless CI environments, use Xvfb to provide a virtual display so that non‑headless Puppeteer runs correctly.
# Install Xvfb in CI
sudo apt-get update && sudo apt-get install xvfb
# Run tests with virtual frame buffer
xvfb-run node test/index.jsWith these steps, the full lifecycle of a Chrome extension—from architecture and development to debugging and CI‑ready E2E testing—is covered.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.