Skip to content

Commit b99da14

Browse files
authored
feat(runner): Create new @wxt-dev/runner package (#1566)
1 parent f02400a commit b99da14

25 files changed

+2000
-366
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ on:
1414
- module-solid
1515
- module-svelte
1616
- module-vue
17+
- runner
1718
- storage
1819
- unocss
1920
- webextension-polyfill

.github/workflows/sync-releases.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ on:
1414
- module-solid
1515
- module-svelte
1616
- module-vue
17+
- runner
1718
- storage
1819
- webextension-polyfill
1920
- wxt

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
.output
66
.webextrc
77
.wxt
8+
.wxt-runner
89
*.log
910
/docs/.vitepress/cache
1011
docs/.vitepress/.temp

docs/.vitepress/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { version as autoIconsVersion } from '../../packages/auto-icons/package.j
1515
import { version as unocssVersion } from '../../packages/unocss/package.json';
1616
import { version as storageVersion } from '../../packages/storage/package.json';
1717
import { version as analyticsVersion } from '../../packages/analytics/package.json';
18+
import { version as runnerVersion } from '../../packages/runner/package.json';
1819
import addKnowledge from 'vitepress-knowledge';
1920
import {
2021
groupIconMdPlugin,
@@ -41,6 +42,7 @@ const otherPackages = {
4142
i18n: i18nVersion,
4243
storage: storageVersion,
4344
unocss: unocssVersion,
45+
runner: runnerVersion,
4446
};
4547

4648
const knowledge = addKnowledge<DefaultTheme.Config>({

docs/runner.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!--@include: ../packages/runner/README.md-->

packages/runner/README.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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 | &ge; 22.4.0 |
43+
| Bun | &ge; 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 | &ge; 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.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('Hello background!');
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "Test",
3+
"version": "1.0.0",
4+
"manifest_version": 3,
5+
"background": {
6+
"service_worker": "background.js",
7+
"scripts": ["background.js"]
8+
}
9+
}

packages/runner/dev.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// USAGE:
3+
// pnpm dev
4+
// pnpm dev firefox-nightly
5+
// pnpm dev <target>
6+
//
7+
8+
import { run } from './src';
9+
10+
// Uncomment to enable debug logs
11+
process.env.DEBUG = '@wxt-dev/runner';
12+
13+
await run({
14+
extensionDir: 'demo-extension',
15+
target: process.argv[2],
16+
});

packages/runner/package.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "@wxt-dev/runner",
3+
"description": "Launch Chrome and Firefox with a web extension installed",
4+
"version": "0.1.0",
5+
"type": "module",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/wxt-dev/wxt.git",
9+
"directory": "packages/runner"
10+
},
11+
"homepage": "https://github.com/wxt-dev/wxt/tree/main/packages/runner#readme",
12+
"keywords": [
13+
"web-extension",
14+
"chrome-extension",
15+
"wxt"
16+
],
17+
"author": {
18+
"name": "Aaron Klinker",
19+
"email": "aaronklinker1+wxt@gmail.com"
20+
},
21+
"license": "MIT",
22+
"funding": "https://github.com/sponsors/wxt-dev",
23+
"scripts": {
24+
"check": "pnpm build && check",
25+
"test": "buildc --deps-only -- vitest",
26+
"dev": "tsx --trace-warnings dev.ts",
27+
"build": "buildc -- unbuild",
28+
"prepublishOnly": "pnpm build"
29+
},
30+
"dependencies": {},
31+
"devDependencies": {
32+
"@aklinker1/check": "2.0.0",
33+
"oxlint": "^0.16.8",
34+
"publint": "^0.3.12",
35+
"tsx": "4.19.4",
36+
"typescript": "^5.8.3",
37+
"unbuild": "^3.5.0",
38+
"vitest": "^3.1.2"
39+
},
40+
"main": "dist/index.mjs",
41+
"types": "dist/index.d.ts",
42+
"exports": {
43+
".": {
44+
"types": "./dist/index.d.mts",
45+
"default": "./dist/index.mjs"
46+
}
47+
},
48+
"files": [
49+
"dist"
50+
]
51+
}

0 commit comments

Comments
 (0)