如何获取Python Selenium功能测试对NodeJS浏览器扩展的代码覆盖率?
Great question—measuring code coverage from end-to-end Selenium tests for browser extensions can be tricky because the extension runs in the browser's context, not a Node.js runtime. Here's a practical, step-by-step approach to make this work with Istanbul (nyc):
1. Instrument Your Extension Code Properly
First, ensure your extension's runtime code (background scripts, content scripts, popup JS) is instrumented with Istanbul to track execution.
Configure nyc
Add a nyc section to your package.json to specify which files to instrument and how:
{ "nyc": { "include": [ "src/background/**/*.js", "src/content-scripts/**/*.js", "src/popup/**/*.js" ], "instrument": true, "sourceMap": true, "report-dir": "./coverage" } }
Build with Instrumentation
If you're using a bundler like Webpack, use the istanbul-instrumenter-loader to instrument code during the build. Alternatively, run nyc directly on your built files:
npx nyc instrument ./dist ./dist-instrumented
Use the dist-instrumented folder to package your test extension.
Critical Note: For Manifest V3 extensions, temporarily adjust your Content Security Policy to allow unsafe-eval (required for Istanbul's instrumentation):
"content_security_policy": { "extension_pages": "script-src 'self' 'unsafe-eval'; object-src 'self'" }
Don’t forget to revert this for production!
2. Capture Coverage Data from the Browser
After running your Selenium tests, extract the __coverage__ object (added by Istanbul) from all parts of your extension. Add these helper functions to your Python Selenium code:
Get Background Script Coverage (Manifest V2)
def get_background_coverage(driver): try: background_page = driver.execute_script("return chrome.runtime.getBackgroundPage();") if background_page: return background_page.execute_script("return window.__coverage__;") except Exception as e: print(f"Failed to get background coverage: {e}") return None
Get Content Script Coverage from Tabs
def get_tab_coverage(driver): coverage_list = [] for handle in driver.window_handles: driver.switch_to.window(handle) try: coverage = driver.execute_script("return window.__coverage__;") if coverage: coverage_list.append(coverage) except Exception as e: print(f"Failed to get coverage for tab {handle}: {e}") return coverage_list
Get Popup Coverage
def get_popup_coverage(driver): try: # Retrieve the popup view if it's open popup_view = driver.execute_script("return chrome.runtime.getViews({type: 'popup'})[0];") if popup_view: return popup_view.execute_script("return window.__coverage__;") except Exception as e: print(f"Failed to get popup coverage: {e}") return None
For Manifest V3 Service Workers
Manifest V3 uses service workers instead of background pages. Use the Chrome DevTools Protocol to attach to the service worker and fetch coverage:
def get_service_worker_coverage(driver): # Enable Debugger domain driver.execute_cdp_cmd("Debugger.enable", {}) # Get all service worker targets targets = driver.execute_cdp_cmd("Target.getTargets", {}) sw_targets = [t for t in targets["targetInfos"] if t["type"] == "service_worker"] coverage = None for target in sw_targets: # Attach to the service worker driver.execute_cdp_cmd("Target.attachToTarget", {"targetId": target["targetId"], "flatten": True}) # Fetch coverage data result = driver.execute_cdp_cmd("Runtime.evaluate", {"expression": "window.__coverage__"}) if result.get("result", {}).get("value"): coverage = result["result"]["value"] # Detach from target driver.execute_cdp_cmd("Target.detachFromTarget", {"targetId": target["targetId"]}) driver.execute_cdp_cmd("Debugger.disable", {}) return coverage
3. Merge Coverage Data and Generate Reports
Once you’ve collected all coverage parts, merge them into a single file and generate human-readable reports:
import json import os import subprocess def save_and_merge_coverage(background_cov, tab_covs, popup_cov): # Create directory for partial coverage files partial_dir = "./coverage-partials" os.makedirs(partial_dir, exist_ok=True) # Save background/service worker coverage if background_cov: with open(os.path.join(partial_dir, "background.json"), "w") as f: json.dump(background_cov, f) # Save tab coverages for i, cov in enumerate(tab_covs): with open(os.path.join(partial_dir, f"tab-{i}.json"), "w") as f: json.dump(cov, f) # Save popup coverage if popup_cov: with open(os.path.join(partial_dir, "popup.json"), "w") as f: json.dump(popup_cov, f) # Merge all partials into a single coverage file subprocess.run(["npx", "nyc", "merge", partial_dir, "./coverage/coverage-final.json"], check=True) # Generate HTML report subprocess.run(["npx", "nyc", "report", "--reporter=html", "--report-dir=./coverage/html"], check=True) # Usage after running your tests: background_cov = get_background_coverage(driver) # Use get_service_worker_coverage for MV3 tab_covs = get_tab_coverage(driver) popup_cov = get_popup_coverage(driver) save_and_merge_coverage(background_cov, tab_covs, popup_cov)
Key Tips
- Source Maps: Ensure your build process generates source maps so the coverage report links back to your original source files, not the instrumented ones.
- Cleanup: Delete the
coverage-partialsdirectory after each run to avoid leftover data from previous tests. - Manifest V3: The service worker approach relies on Chrome DevTools Protocol, which is fully supported in Selenium for Chrome.
This workflow will give you accurate code coverage metrics from your Selenium functional tests, showing exactly which parts of your extension code are exercised during end-to-end testing.
内容的提问来源于stack exchange,提问作者Divyanshu Juneja

