WebExtension Module
The WebExtension module allows you to manage browser extensions programmatically, enabling automated testing of extension functionality.
Overview
The WebExtension module allows you to:
- Install browser extensions
- Uninstall extensions
- Test extension functionality
- Automate extension-based workflows
Accessing the Module
WebExtensionModule webExtension = driver.WebExtension;
Timeout and Cancellation
All commands in this module accept optional timeoutOverride and CancellationToken parameters. Use timeoutOverride to set a per-command timeout (defaults to BiDiDriver.DefaultCommandTimeout when omitted). Use CancellationToken for cooperative cancellation. See the API Design Guide for details and examples.
Installing Extensions
InstallAsync accepts one of three ExtensionData forms: ExtensionArchivePath (a packed extension archive file), ExtensionPath (an unpacked extension directory), or ExtensionBase64Encoded (a packed extension supplied inline as a base64-encoded string).
Install from Archive Path
InstallCommandParameters @params = new InstallCommandParameters(
new ExtensionArchivePath("/path/to/extension.zip"));
InstallCommandResult result = await driver.WebExtension.InstallAsync(@params);
string extensionId = result.ExtensionId;
Console.WriteLine($"Extension installed: {extensionId}");
Install Unpacked Extension
// Install from unpacked extension directory
InstallCommandParameters @params = new InstallCommandParameters(
new ExtensionPath("/path/to/extension-directory"));
InstallCommandResult result = await driver.WebExtension.InstallAsync(@params);
Console.WriteLine($"Extension installed: {result.ExtensionId}");
Uninstalling Extensions
Uninstall by ID
UninstallCommandParameters @params = new UninstallCommandParameters(extensionId);
await driver.WebExtension.UninstallAsync(@params);
Console.WriteLine("Extension uninstalled");
Common Patterns
Testing with Extension
// Install extension
InstallCommandParameters installParams = new InstallCommandParameters(
new ExtensionPath("/path/to/my-extension"));
InstallCommandResult installResult =
await driver.WebExtension.InstallAsync(installParams);
string extensionId = installResult.ExtensionId;
Console.WriteLine($"Extension installed: {extensionId}");
// Navigate and test
await driver.BrowsingContext.NavigateAsync(
new NavigateCommandParameters(contextId, "https://example.com")
{ Wait = ReadinessState.Complete });
// Test extension functionality
EvaluateResult result = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
"document.querySelector('[data-extension-injected]') !== null",
new ContextTarget(contextId),
true));
if (result is EvaluateResultSuccess success &&
success.Result is BooleanRemoteValue boolValue)
{
bool extensionActive = boolValue.Value;
Console.WriteLine($"Extension active: {extensionActive}");
}
// Clean up
await driver.WebExtension.UninstallAsync(
new UninstallCommandParameters(extensionId));
Testing Extension Content Scripts
// Install extension with content script
InstallCommandParameters @params = new InstallCommandParameters(
new ExtensionPath("/path/to/content-script-extension"));
InstallCommandResult result = await driver.WebExtension.InstallAsync(@params);
// Navigate to test page
await driver.BrowsingContext.NavigateAsync(
new NavigateCommandParameters(contextId, "https://example.com")
{ Wait = ReadinessState.Complete });
await Task.Delay(1000);
// Check if content script modified the page
EvaluateResult evalResult = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
"document.body.dataset.contentScriptLoaded",
new ContextTarget(contextId),
true));
if (evalResult is EvaluateResultSuccess success &&
success.Result is StringRemoteValue loadedValue)
{
string loaded = loadedValue.Value;
Console.WriteLine($"Content script loaded: {loaded}");
}
// Clean up
await driver.WebExtension.UninstallAsync(
new UninstallCommandParameters(result.ExtensionId));
Testing Multiple Extensions
List<string> extensionIds = new List<string>();
// Install multiple extensions
string[] extensionPaths = new[]
{
"/path/to/extension1",
"/path/to/extension2",
"/path/to/extension3"
};
foreach (string path in extensionPaths)
{
InstallCommandParameters @params = new InstallCommandParameters(
new ExtensionPath(path));
InstallCommandResult result = await driver.WebExtension.InstallAsync(@params);
extensionIds.Add(result.ExtensionId);
Console.WriteLine($"Installed: {result.ExtensionId}");
}
// Run tests with all extensions active
// ...
// Clean up all extensions
foreach (string id in extensionIds)
{
await driver.WebExtension.UninstallAsync(
new UninstallCommandParameters(id));
}
Testing Extension Permissions
// Install extension that requires permissions
InstallCommandParameters @params = new InstallCommandParameters(
new ExtensionPath("/path/to/permission-extension"));
InstallCommandResult result = await driver.WebExtension.InstallAsync(@params);
// Navigate to page where extension needs permissions
await driver.BrowsingContext.NavigateAsync(
new NavigateCommandParameters(contextId, "https://example.com")
{ Wait = ReadinessState.Complete });
// Test that extension has required permissions
EvaluateResult evalResult = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
@"new Promise((resolve) => {
chrome.permissions.contains({
permissions: ['storage']
}, (result) => resolve(result));
})",
new ContextTarget(contextId),
true));
if (evalResult is EvaluateResultSuccess success &&
success.Result is BooleanRemoteValue hasPermissionValue)
{
bool hasPermission = hasPermissionValue.Value;
Console.WriteLine($"Extension has storage permission: {hasPermission}");
}
// Uninstall
await driver.WebExtension.UninstallAsync(
new UninstallCommandParameters(result.ExtensionId));
Extension Formats
The protocol defines three forms, and both archive forms are plain zip archives rather than browser-specific packaging. The remote end extracts the archive before installing.
ExtensionArchivePath: the path, on the remote end's file system, to a zip archive containing the extension.ExtensionBase64Encoded: the same zip archive, base64-encoded, for when the file is not on the remote end's file system.ExtensionPath: a directory containingmanifest.jsonand the extension files — an unpacked extension.
A browser-specific package such as a Chrome .crx or a Firefox .xpi is not one of these forms. Repackage
the extension as a zip archive, or install it unpacked from a directory.
Safari
- App Extensions: Safari extensions are packaged differently
- Limited BiDi support: WebExtension module has limited Safari support
Browser Support
| Browser | Support Level | Format |
|---|---|---|
| Chrome/Edge | ✅ Full support | zip archive, unpacked |
| Firefox | ⚠️ Different API | zip archive, unpacked |
| Safari | ⚠️ Limited | App extensions |
Best Practices
- Use unpacked extensions for development: Easier to modify and debug
- Store extension ID: Save the ID returned from InstallAsync for cleanup
- Wait for extension initialization: Add delays after installation if needed
- Clean up after tests: Always uninstall extensions to prevent conflicts
- Test with real extension builds: Use production-ready extension packages
Common Issues
Extension Installation Fails
Problem: Cannot install browser extension.
Solution:
- Verify extension path is correct and absolute
- Check extension file format (a zip archive, or a directory for an unpacked extension)
- Ensure manifest.json is valid
- Try loading as unpacked extension for development
- Check browser console for extension errors
Extension Not Active
Problem: Extension installs but doesn't work.
Solution:
- Wait for extension to initialize after installation
- Check if page URL matches extension's content script patterns
- Verify extension permissions in manifest.json
- Reload the page after extension installation
Extension ID Not Found
Problem: Cannot uninstall extension by ID.
Solution:
- Save the extension ID returned from InstallAsync
- Don't manually construct extension IDs
- Ensure extension is still installed before uninstalling
Manifest Version Issues
Problem: Extension manifest version incompatible.
Solution:
- Use Manifest V3 for Chrome/Edge (preferred)
- Manifest V2 support varies by browser version
- Update extension manifest to supported version
- Check browser's extension documentation
Next Steps
- Bluetooth Module: Web Bluetooth API control
- Permissions Module: Granting or denying web-platform permissions (geolocation, notifications, and so on) to the pages an extension runs against
- Browser Module: Browser-level operations
- API Reference: Complete API documentation