|
| 1 | +# `@wxt-dev/runner` |
| 2 | + |
| 3 | +Programmatically open a browser and install a web extension from a local directory. |
| 4 | + |
| 5 | +###### With WXT |
| 6 | + |
| 7 | +> [!WARNING] |
| 8 | +> This package is intended to replace [`web-ext`](https://github.com/mozilla/web-ext) in the future, but it is not ready at the moment. Once it's ready for testing in WXT, more details will be added here. |
| 9 | +
|
| 10 | +```ts |
| 11 | +// ~/wxt.runner.config.ts OR <project>/wxt.runner.config.ts |
| 12 | +import { defineRunnerConfig } from 'wxt'; |
| 13 | + |
| 14 | +export default defineRunnerConfig({ |
| 15 | + // Options go here |
| 16 | +}); |
| 17 | +``` |
| 18 | + |
| 19 | +###### JS API |
| 20 | + |
| 21 | +```ts |
| 22 | +import { run } from '@wxt-dev/runner'; |
| 23 | + |
| 24 | +await run({ |
| 25 | + extensionDir: '/path/to/extension', |
| 26 | + // Other options... |
| 27 | +}); |
| 28 | +``` |
| 29 | + |
| 30 | +## Features |
| 31 | + |
| 32 | +- Supports all Chromium and Firefox based browsers |
| 33 | +- Zero dependencies |
| 34 | +- One-line config for persisting data between launches |
| 35 | + |
| 36 | +## Requirements |
| 37 | + |
| 38 | +`@wxt-dev/runner` requires a JS runtime that implements the `WebSocket` standard: |
| 39 | + |
| 40 | +| JS Runtime | Version | |
| 41 | +| ---------- | ----------- | |
| 42 | +| NodeJS | ≥ 22.4.0 | |
| 43 | +| Bun | ≥ 1.2.0 | |
| 44 | + |
| 45 | +You also need to have a specific version of the browser installed that supports the latest features so extensions can be loaded: |
| 46 | + |
| 47 | +| Browser | Version | |
| 48 | +| -------- | -------- | |
| 49 | +| Chromium | Unknown | |
| 50 | +| Firefox | ≥ 139 | |
| 51 | + |
| 52 | +## TODO |
| 53 | + |
| 54 | +- [x] Provide install functions to allow hooking into already running instances of Chrome/Firefox |
| 55 | + - [ ] Try to setup E2E tests on Firefox with Puppeteer using this approach |
| 56 | + - [ ] Try to setup E2E tests on Chrome with Puppeteer using this approach |
| 57 | + |
| 58 | +## Options |
| 59 | + |
| 60 | +### Target |
| 61 | + |
| 62 | +To open a specific browser, use the `target` option: |
| 63 | + |
| 64 | +```ts |
| 65 | +import { run } from '@wxt-dev/runner'; |
| 66 | + |
| 67 | +await run({ |
| 68 | + extensionDir: 'path/to/extension', |
| 69 | + target: 'firefox', |
| 70 | +}); |
| 71 | +``` |
| 72 | + |
| 73 | +Defaults to opening `chrome`. You may see type-hints for a list of popular browsers, but you can enter any string you want here. |
| 74 | + |
| 75 | +### Data Persistence |
| 76 | + |
| 77 | +Browsers block you from using your normal browser profiles when using the [BiDi and CDP protocols](#implementation-details) for security reasons. |
| 78 | + |
| 79 | +To change how the new profile's data is saved between sessions, use the `dataPersistence` option: |
| 80 | + |
| 81 | +```ts |
| 82 | +import { run } from '@wxt-dev/runner'; |
| 83 | + |
| 84 | +await run({ |
| 85 | + dataPersistence: 'user', |
| 86 | +}); |
| 87 | +``` |
| 88 | + |
| 89 | +- `"none"` (default): Use a brand new browser profile every time the browser is opened (stored in the system's tmp directory) |
| 90 | +- `"project"`: Create a new profile that is re-used for your current directory (by default stored in `.wxt-runner` or `.wxt/runner` for WXT projects) |
| 91 | +- `"user"`: Create a new profile that is re-used for all projects using `@wxt-dev/runner` (by default stored in `$HOME/.wxt-runner`) |
| 92 | + |
| 93 | +These presets configure different flags for different operating systems when spawning the browser process. |
| 94 | + |
| 95 | +If you want to customize your data persistence beyond what these presets define, [you can override the browser flags yourself](#Arguments) to configure persistence. |
| 96 | + |
| 97 | +### Browser Binaries |
| 98 | + |
| 99 | +`@wxt-dev/runner` will look for browser binaries/executables in [a hard-coded list of paths](https://github.com/wxt-dev/wxt/blob/main/packages/runner/src/browser-paths.ts). It does not and will not explore your filesystem/`$PATH` to find where the browser is installed. That means there are times you will need to specify the path to a browser's binary on your system: |
| 100 | + |
| 101 | +- Your browser's path is non-standard or missing from the hard-coded list. |
| 102 | +- You want to use a specific version/release of the browser. |
| 103 | +- You're using a less popular browser and `@wxt-dev/runner` doesn't have hard-coded paths for it. |
| 104 | + |
| 105 | +To do this, use the `browserBinaries` option and set the path to the browser's binary: |
| 106 | + |
| 107 | +```ts |
| 108 | +import { run } from '@wxt-dev/runner'; |
| 109 | + |
| 110 | +await run({ |
| 111 | + extensionDir: 'path/to/extension', |
| 112 | + browserBinaries: { |
| 113 | + chrome: '/path/to/chrome', |
| 114 | + firefox: '/path/to/firefox', |
| 115 | + }, |
| 116 | +}); |
| 117 | +``` |
| 118 | + |
| 119 | +### Arguments |
| 120 | + |
| 121 | +To pass custom arguments to the browser on startup, use the `chromiumArgs` or `firefoxArgs` options: |
| 122 | + |
| 123 | +```ts |
| 124 | +import { run } from '@wxt-dev/runner'; |
| 125 | + |
| 126 | +await run({ |
| 127 | + extensionDir: 'path/to/extension', |
| 128 | + chromiumArgs: ['--window-size=1920,1080'], |
| 129 | + firefoxArgs: ['--window-size', '1920,1080'], |
| 130 | +}); |
| 131 | +``` |
| 132 | + |
| 133 | +### Start URLs |
| 134 | + |
| 135 | +To open specific URLs in tabs by default, you also use the `chromiumArgs` or `firefoxArgs` options. |
| 136 | + |
| 137 | +Any URLs passed as a CLI argument will be opened in the browser when it starts. |
| 138 | + |
| 139 | +```ts |
| 140 | +import { run } from '@wxt-dev/runner'; |
| 141 | + |
| 142 | +await run({ |
| 143 | + extensionDir: 'path/to/extension', |
| 144 | + chromiumArgs: ['https://example.com'], |
| 145 | + firefoxArgs: ['https://example.com'], |
| 146 | +}); |
| 147 | +``` |
| 148 | + |
| 149 | +### Debugging |
| 150 | + |
| 151 | +To see debug logs, set the `DEBUG` env var to `"@wxt-dev/runner"`. This will print the resolved config, commands used to spawn the browser, any messages sent on the browser's communication protocol, and more for you to debug. |
| 152 | + |
| 153 | +<details> |
| 154 | +<summary>Example debug output</summary> |
| 155 | + |
| 156 | +``` |
| 157 | +@wxt-dev/runner:options User options: { extensionDir: 'demo-extension', target: undefined } |
| 158 | +@wxt-dev/runner:options Resolved options: { |
| 159 | + browserBinary: '/usr/bin/chromium', |
| 160 | + chromiumArgs: [ |
| 161 | + '--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4', |
| 162 | + '--disable-component-extensions-with-background-pages', |
| 163 | + '--disable-background-networking', |
| 164 | + '--disable-component-update', |
| 165 | + '--disable-client-side-phishing-detection', |
| 166 | + '--disable-sync', |
| 167 | + '--metrics-recording-only', |
| 168 | + '--disable-default-apps', |
| 169 | + '--no-default-browser-check', |
| 170 | + '--no-first-run', |
| 171 | + '--disable-background-timer-throttling', |
| 172 | + '--disable-ipc-flooding-protection', |
| 173 | + '--password-store=basic', |
| 174 | + '--use-mock-keychain', |
| 175 | + '--force-fieldtrials=*BackgroundTracing/default/', |
| 176 | + '--disable-hang-monitor', |
| 177 | + '--disable-prompt-on-repost', |
| 178 | + '--disable-domain-reliability', |
| 179 | + '--propagate-iph-for-testing', |
| 180 | + '--remote-debugging-port=0', |
| 181 | + '--remote-debugging-pipe', |
| 182 | + '--user-data-dir=/tmp/wxt-runner-pWXLO1', |
| 183 | + '--enable-unsafe-extension-debugging' |
| 184 | + ], |
| 185 | + dataDir: '/tmp/wxt-runner-pWXLO1', |
| 186 | + dataPersistence: 'none', |
| 187 | + chromiumRemoteDebuggingPort: 0, |
| 188 | + extensionDir: '/home/aklinker1/Development/github.com/wxt-dev/wxt/packages/runner/demo-extension', |
| 189 | + firefoxArgs: [ |
| 190 | + '--new-instance', |
| 191 | + '--no-remote', |
| 192 | + '--profile', |
| 193 | + '/tmp/wxt-runner-pWXLO1', |
| 194 | + '--remote-debugging-port=0', |
| 195 | + 'about:debugging#/runtime/this-firefox' |
| 196 | + ], |
| 197 | + firefoxRemoteDebuggingPort: 0, |
| 198 | + target: 'chrome' |
| 199 | +} |
| 200 | +@wxt-dev/runner:chrome:stderr DevTools listening on ws://127.0.0.1:38397/devtools/browser/93dc4de5-64cb-4e0b-a9d3-7549527015f0 |
| 201 | +@wxt-dev/runner:cdp Sending command: { |
| 202 | + id: 1, |
| 203 | + method: 'Extensions.loadUnpacked', |
| 204 | + params: { |
| 205 | + path: '/home/aklinker1/Development/github.com/wxt-dev/wxt/packages/runner/demo-extension' |
| 206 | + } |
| 207 | +} |
| 208 | +@wxt-dev/runner:cdp Received response: { id: 1, result: { id: 'hckhakegfgenefhikdcfkaaonnclljmf' } } |
| 209 | +``` |
| 210 | + |
| 211 | +</details> |
| 212 | + |
| 213 | +## Implementation Details |
| 214 | + |
| 215 | +All this package does is spawn a child process to open the browser with some default flags before using remote protocols to install the extension. |
| 216 | + |
| 217 | +### Firefox |
| 218 | + |
| 219 | +We use the new [WebDriver BiDi protocol](https://www.w3.org/TR/webdriver-bidi) to install the extension. This just involves connecting to a web socket and sending a few messages. |
| 220 | + |
| 221 | +### Chrome |
| 222 | + |
| 223 | +We use the [CDP](https://chromedevtools.github.io/devtools-protocol/) with `--remote-debugging-pipe` and `--enable-unsafe-extension-debugging` to install the extension by sending a message via IO pipes 3 and 4. |
| 224 | + |
| 225 | +We don't use Webdriver Bidi because it's not built into Chrome yet. It requires us instantiating a separate child process for `chromedriver`. This is slower and more difficult than just using the CDP built into Chrome. |
0 commit comments