playwright-browser-harness 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,11 +22,11 @@ their versions. `@sqlite.org/sqlite-wasm` is *not* a dependency; it is only a
22
22
  fixture/consumer dep when you test wasm-SQLite.
23
23
 
24
24
  `buffer` is an **optional peerDependency** — only needed (and only installed) when
25
- you bundle web3 / Node-dependency-heavy code with `nodePolyfills` (see
26
- [Bundling web3 / Node-dependency-heavy code](#bundling-web3--node-dependency-heavy-code)):
25
+ you ask for the `'buffer'` Node polyfill (see
26
+ [Explicit Node polyfills](#explicit-node-polyfills)):
27
27
 
28
28
  ```bash
29
- pnpm add -D buffer # only if you use nodePolyfills
29
+ pnpm add -D buffer # only if you use nodePolyfills: ['buffer', ...]
30
30
  ```
31
31
 
32
32
  ## tsconfig (required)
@@ -101,39 +101,58 @@ esbuild) **or** `prebuilt` (prebuilt dir); `coi` (default `true`), `worker`,
101
101
  `wasmDirs`, `assets`, plus the bundling escape-hatches `nodePolyfills` and
102
102
  `esbuild` (see below).
103
103
 
104
- ## Bundling web3 / Node-dependency-heavy code
104
+ ## Explicit Node polyfills
105
105
 
106
106
  Many web3 / crypto libraries (ethereumjs, tevm, and anything pulling in
107
107
  `readable-stream` / `safe-buffer`) transitively `import 'buffer'` and touch
108
- `process` / `global`. esbuild's `platform:'browser'` will **not** resolve those,
109
- so the bundle fails with `Could not resolve "buffer"`. The harness gives you two
110
- opt-in escape-hatches, both **off/empty by default** (existing consumers are
111
- unchanged):
108
+ `process` / `global` / `events` / `stream`. esbuild's `platform:'browser'` will
109
+ **not** resolve those, so the bundle fails with `Could not resolve "buffer"`.
110
+
111
+ There is **no** catch-all preset. A named "web3" preset is a leaky abstraction:
112
+ it would shim `buffer`/`process`/`global` but not `events`/`stream`/`util`, so
113
+ you'd fall back to the `esbuild` escape-hatch anyway. Instead you **declare
114
+ exactly what you need**.
115
+
116
+ ### `nodePolyfills?: ('buffer' | 'process' | 'global')[]` (default `[]`)
117
+
118
+ The three Node builtins the harness can shim **itself**, with zero opinions.
119
+ Each entry is a **self-contained** fragment — `['buffer']` shims ONLY buffer; it
120
+ does **not** touch `process` or `global`. `[]` or omitted = nothing.
121
+
122
+ | entry | what it does |
123
+ |---|---|
124
+ | `'buffer'` | alias `buffer` / `node:buffer` → the [`buffer`](https://www.npmjs.com/package/buffer) npm package, and inject a `Buffer` global (also assigned onto `globalThis`). Requires the optional `buffer` peer dep (`pnpm add -D buffer`). |
125
+ | `'process'` | inject a minimal `process` stub: `{env:{}, browser:true, version:'', nextTick}`. |
126
+ | `'global'` | set `define: { global: 'globalThis' }`. |
127
+
128
+ **Anything beyond these three** (`events`, `stream`, `util`, `crypto`, …) is the
129
+ consumer's job via the [`esbuild`](#esbuild-escape-hatch--esbuild--plugins-inject-define-alias-loader-external-tsconfig-)
130
+ pass-through below.
131
+
132
+ #### Copy-paste recipe: ethereumjs / tevm
133
+
134
+ A typical web3 EVM tree needs all three builtins **plus** `events`/`stream`
135
+ aliased through the escape hatch:
112
136
 
113
137
  ```ts
114
138
  const h = await mountHarness(page, {
115
139
  cut,
116
- coi: false, // pure-compute EVM → no SharedArrayBuffer needed
117
- nodePolyfills: 'web3', // alias buffer, inject Buffer, shim process, define global
118
- esbuild: { // pass-through merged into the internal esbuild build
119
- alias: {events: 'events', stream: 'stream-browserify'}, // any extra builtins
140
+ coi: false, // pure-compute EVM → no SharedArrayBuffer needed
141
+ nodePolyfills: ['buffer', 'process', 'global'], // the harness's three builtins
142
+ esbuild: {
143
+ // everything the harness intentionally does NOT preset:
144
+ alias: {events: 'events', stream: 'stream-browserify'},
120
145
  },
121
146
  });
122
147
  ```
123
148
 
124
- ### `nodePolyfills?: boolean | 'web3'` (default `false`)
125
-
126
- When enabled it:
127
-
128
- - aliases `buffer` / `node:buffer` → the [`buffer`](https://www.npmjs.com/package/buffer) npm package,
129
- - injects a `Buffer` global (also assigned onto `globalThis`),
130
- - provides a minimal `process` stub: `{env:{}, browser:true, version:'', nextTick}`,
131
- - sets `define: { global: 'globalThis' }`.
149
+ ```bash
150
+ pnpm add -D buffer events stream-browserify
151
+ ```
132
152
 
133
- `true` and `'web3'` are equivalent today (the string leaves room for future
134
- presets). **Install the optional `buffer` peer dep** when you opt in
135
- (`pnpm add -D buffer`). web3 / crypto libraries usually need this; plain DOM /
136
- storage code does not.
153
+ > **Migrating from 0.2.0** — `nodePolyfills` shipped in 0.2.0 as `true | 'web3'`.
154
+ > That form was **removed** (clean break; 0.2.0 had ≈no users). Replace
155
+ > `true` / `'web3'` with `['buffer', 'process', 'global']`.
137
156
 
138
157
  ### esbuild escape hatch — `esbuild?: { plugins?, inject?, define?, alias?, loader?, external?, tsconfig? }`
139
158
 
@@ -148,17 +167,17 @@ winning; arrays concatenated **after** the built-ins). The harness's own
148
167
  | key | merge behaviour |
149
168
  |---|---|
150
169
  | `plugins` | concatenated **after** the built-in `__CUT_MODULE__` resolver |
151
- | `inject` | concatenated after the `nodePolyfills` injects (if any) |
170
+ | `inject` | concatenated after the `nodePolyfills` injects (the chosen builtins, if any) |
152
171
  | `define` / `alias` | shallow-merged, **consumer key wins** |
153
172
  | `loader` | merged **over** `{'.wasm':'copy'}` (e.g. add `{'.svg':'dataurl'}`) |
154
173
  | `external` | as given |
155
174
  | `tsconfig` | path to a tsconfig esbuild should honour |
156
175
 
157
- #### Node polyfills recipe (the common case)
176
+ #### Doing it entirely by hand (no `nodePolyfills`)
158
177
 
159
- The quickest path is the `nodePolyfills` preset above. To do it by hand (or to
160
- extend it), alias `buffer` to the npm package and inject a `Buffer`/`process`
161
- shim via the escape hatch:
178
+ `nodePolyfills` is just sugar for these three fragments. If you'd rather not use
179
+ it at all (e.g. you want one custom shim module), alias `buffer` to the npm
180
+ package and inject a `Buffer`/`process` shim purely via the escape hatch:
162
181
 
163
182
  ```ts
164
183
  const h = await mountHarness(page, {
package/dist/build.mjs CHANGED
@@ -23,71 +23,128 @@ import { existsSync } from 'node:fs';
23
23
  const here = dirname(fileURLToPath(import.meta.url));
24
24
 
25
25
  /**
26
- * Build the `nodePolyfills` preset (esbuild fragments) for browser bundles of
27
- * web3/crypto code whose dependency trees reach for Node builtins.
26
+ * Explicit, composable Node-builtin polyfill fragments.
28
27
  *
29
- * Many EVM / crypto libraries (ethereumjs, tevm, anything pulling
30
- * `readable-stream`/`safe-buffer`) transitively `import 'buffer'` and touch
31
- * `process` / `global`, which esbuild's `platform:'browser'` will NOT resolve
32
- * the bundle fails with `Could not resolve "buffer"`. This opt-in preset:
33
- * - aliases `buffer` / `node:buffer` to the `buffer` npm package,
34
- * - injects a `Buffer` global (so `Buffer.from(...)` works without an import),
35
- * - shims a minimal `process` (`{env,browser,version,nextTick}`),
36
- * - defines `global` → `globalThis`.
28
+ * There is intentionally NO catch-all "web3" preset: a named preset is a leaky
29
+ * abstraction it would shim `buffer`/`process`/`global` but not
30
+ * `events`/`stream`/`util`/`crypto`, so consumers fall back to the `esbuild`
31
+ * escape-hatch anyway. Instead the harness exposes exactly the three builtins it
32
+ * can shim with zero opinions, and each maps to a self-contained fragment:
37
33
  *
38
- * `buffer` is an optional/peer dependency: the consumer installs it when they
39
- * opt in (and the harness's own ethereumjs fixture pulls it in for the test).
34
+ * - 'buffer' → alias `buffer`/`node:buffer` to the `buffer` npm package +
35
+ * inject a `Buffer` shim that also assigns `globalThis.Buffer`.
36
+ * - 'process' → inject a minimal `process` stub `{env,browser,version,nextTick}`.
37
+ * - 'global' → define `{ global: 'globalThis' }`.
40
38
  *
41
- * @param {string} outdir directory to write the generated `process` shim into
42
- * @returns {Promise<{alias: Record<string,string>, inject: string[], define: Record<string,string>}>}
39
+ * Anything else (`events`, `stream`, `util`, `crypto`, …) is the consumer's job
40
+ * via the `esbuild: { alias, inject, define, plugins }` pass-through.
41
+ *
42
+ * Each helper returns an esbuild *fragment* (`{alias?, inject?, define?}`) so a
43
+ * consumer can compose them with their own esbuild opts. `buffer` is an
44
+ * optional/peer dependency: the consumer installs it when they opt into the
45
+ * `'buffer'` polyfill.
46
+ *
47
+ * @typedef {'buffer'|'process'|'global'} NodePolyfill
43
48
  */
44
- async function nodePolyfillsPreset(outdir) {
45
- // Resolve the `buffer` npm package from the consumer's install. We resolve
46
- // the *bare specifier* so esbuild aliases `buffer`/`node:buffer` to it.
47
- // (We don't hard-require it here — esbuild will surface a clear error if the
48
- // consumer enabled nodePolyfills without installing `buffer`.)
49
- const bufferPkg = 'buffer';
50
-
51
- // A tiny `process` shim written next to the bundle and injected as a global.
52
- // Kept minimal on purpose: env bag, browser flag, empty version, and a
53
- // microtask-based nextTick — enough for readable-stream/safe-buffer & friends.
54
- const procShim = join(outdir, '__process-shim.js');
55
- await writeFile(
56
- procShim,
57
- [
58
- 'const process = {',
59
- ' env: {},',
60
- ' browser: true,',
61
- " version: '',",
62
- ' nextTick: (cb, ...args) => Promise.resolve().then(() => cb(...args)),',
63
- '};',
64
- 'export { process };',
65
- 'export default process;',
66
- ].join('\n'),
67
- );
68
49
 
69
- // A tiny module that re-exports `Buffer` from the `buffer` package so esbuild
70
- // can `inject` it as the `Buffer` global. We ALSO assign it onto `globalThis`
71
- // so libraries that reach for `globalThis.Buffer` / `window.Buffer` at runtime
72
- // (not just an unqualified `Buffer` identifier) find it too. GOTCHA: esbuild's
73
- // `inject` only provides a top-level *binding* in modules that reference the
74
- // name it does not set a real global so this explicit assignment is what
75
- // makes `globalThis.Buffer` truthy in-page.
76
- const bufShim = join(outdir, '__buffer-shim.js');
77
- await writeFile(
78
- bufShim,
79
- [
80
- "import { Buffer } from 'buffer';",
81
- 'if (typeof globalThis !== "undefined" && !globalThis.Buffer) globalThis.Buffer = Buffer;',
82
- 'export { Buffer };',
83
- ].join('\n'),
50
+ /**
51
+ * Build a single Node-polyfill fragment by name. Self-contained: passing
52
+ * `'buffer'` shims ONLY buffer, etc. Unknown names throw with a clear message.
53
+ *
54
+ * @param {NodePolyfill} name which builtin to shim
55
+ * @param {string} outdir directory to write any generated shim modules into
56
+ * @returns {Promise<{alias?: Record<string,string>, inject?: string[], define?: Record<string,string>}>}
57
+ */
58
+ export async function nodePolyfill(name, outdir) {
59
+ if (name === 'buffer') {
60
+ // Resolve the `buffer` npm package from the consumer's install via its bare
61
+ // specifier so esbuild aliases `buffer`/`node:buffer` to it. (We don't
62
+ // hard-require it here esbuild surfaces a clear error if the consumer
63
+ // asked for the 'buffer' polyfill without installing `buffer`.)
64
+ const bufferPkg = 'buffer';
65
+ // A tiny module that re-exports `Buffer` from the `buffer` package so esbuild
66
+ // can `inject` it as the `Buffer` global. We ALSO assign it onto `globalThis`
67
+ // so libraries that reach for `globalThis.Buffer` / `window.Buffer` at
68
+ // runtime (not just an unqualified `Buffer` identifier) find it too. GOTCHA:
69
+ // esbuild's `inject` only provides a top-level *binding* in modules that
70
+ // reference the name — it does not set a real global — so this explicit
71
+ // assignment is what makes `globalThis.Buffer` truthy in-page.
72
+ const bufShim = join(outdir, '__buffer-shim.js');
73
+ await writeFile(
74
+ bufShim,
75
+ [
76
+ "import { Buffer } from 'buffer';",
77
+ 'if (typeof globalThis !== "undefined" && !globalThis.Buffer) globalThis.Buffer = Buffer;',
78
+ 'export { Buffer };',
79
+ ].join('\n'),
80
+ );
81
+ return {
82
+ alias: { buffer: bufferPkg, 'node:buffer': bufferPkg },
83
+ inject: [bufShim],
84
+ };
85
+ }
86
+
87
+ if (name === 'process') {
88
+ // A tiny `process` shim written next to the bundle and injected as a global.
89
+ // Kept minimal on purpose: env bag, browser flag, empty version, and a
90
+ // microtask-based nextTick — enough for readable-stream/safe-buffer & co.
91
+ const procShim = join(outdir, '__process-shim.js');
92
+ await writeFile(
93
+ procShim,
94
+ [
95
+ 'const process = {',
96
+ ' env: {},',
97
+ ' browser: true,',
98
+ " version: '',",
99
+ ' nextTick: (cb, ...args) => Promise.resolve().then(() => cb(...args)),',
100
+ '};',
101
+ 'export { process };',
102
+ 'export default process;',
103
+ ].join('\n'),
104
+ );
105
+ return { inject: [procShim] };
106
+ }
107
+
108
+ if (name === 'global') {
109
+ return { define: { global: 'globalThis' } };
110
+ }
111
+
112
+ throw new Error(
113
+ `nodePolyfill: unknown polyfill ${JSON.stringify(name)} — valid values are 'buffer', 'process', 'global'.`,
84
114
  );
115
+ }
85
116
 
86
- return {
87
- alias: { buffer: bufferPkg, 'node:buffer': bufferPkg },
88
- inject: [bufShim, procShim],
89
- define: { global: 'globalThis' },
90
- };
117
+ /**
118
+ * The set of builtins the harness can shim itself, as a reusable/testable map of
119
+ * `name → (outdir) => fragment`. Exposed so a consumer can compose individual
120
+ * fragments with their own esbuild opts without going through `buildBundle`.
121
+ *
122
+ * @type {Record<NodePolyfill, (outdir: string) => Promise<{alias?: Record<string,string>, inject?: string[], define?: Record<string,string>}>>}
123
+ */
124
+ export const nodePolyfills = {
125
+ buffer: (outdir) => nodePolyfill('buffer', outdir),
126
+ process: (outdir) => nodePolyfill('process', outdir),
127
+ global: (outdir) => nodePolyfill('global', outdir),
128
+ };
129
+
130
+ /**
131
+ * Compose the requested Node-polyfill fragments into a single
132
+ * `{alias, inject, define}` bundle. Order-stable; later entries' alias/define
133
+ * keys win on clash (none do today since the three fragments are disjoint).
134
+ *
135
+ * @param {NodePolyfill[]} names
136
+ * @param {string} outdir
137
+ * @returns {Promise<{alias: Record<string,string>, inject: string[], define: Record<string,string>}>}
138
+ */
139
+ async function composeNodePolyfills(names, outdir) {
140
+ const out = { alias: {}, inject: [], define: {} };
141
+ for (const name of names) {
142
+ const frag = await nodePolyfill(name, outdir);
143
+ if (frag.alias) Object.assign(out.alias, frag.alias);
144
+ if (frag.inject) out.inject.push(...frag.inject);
145
+ if (frag.define) Object.assign(out.define, frag.define);
146
+ }
147
+ return out;
91
148
  }
92
149
 
93
150
  /**
@@ -99,10 +156,13 @@ async function nodePolyfillsPreset(outdir) {
99
156
  * @param {string[]} [opts.assets] explicit absolute file paths to copy verbatim
100
157
  * into outdir (e.g. sqlite3-opfs-async-proxy.js, which the OPFS VFS spawns by
101
158
  * URL relative to the worker bundle and esbuild does NOT trace).
102
- * @param {boolean|'web3'} [opts.nodePolyfills] opt-in Node-builtin polyfills for
103
- * web3/crypto code (aliases `buffer`/`node:buffer`, injects `Buffer`, shims
104
- * `process`, defines `global`). Default off. `true` and `'web3'` are
105
- * equivalent today (the string leaves room for future presets).
159
+ * @param {Array<'buffer'|'process'|'global'>} [opts.nodePolyfills] explicit,
160
+ * composable list of Node builtins the harness shims itself. `['buffer']`
161
+ * shims ONLY buffer; `[]` or omitted = nothing. Each entry is self-contained:
162
+ * 'buffer' aliases `buffer`/`node:buffer` + injects a `Buffer` global,
163
+ * 'process' injects a minimal `process` stub, 'global' defines
164
+ * `{global:'globalThis'}`. There is NO catch-all preset; everything beyond
165
+ * these three (events, stream, util, crypto, …) goes through `opts.esbuild`.
106
166
  * @param {object} [opts.esbuild] esbuild pass-through merged into the internal
107
167
  * build. Consumer values take precedence; arrays are concatenated AFTER the
108
168
  * built-ins so the `__CUT_MODULE__` resolver, the page-entry entry point, and
@@ -121,9 +181,16 @@ export async function buildBundle({
121
181
  worker,
122
182
  wasmDirs = [],
123
183
  assets = [],
124
- nodePolyfills = false,
184
+ nodePolyfills = [],
125
185
  esbuild: esbuildOpts = {},
126
186
  }) {
187
+ if (!Array.isArray(nodePolyfills)) {
188
+ throw new TypeError(
189
+ `buildBundle: \`nodePolyfills\` must be an array of 'buffer' | 'process' | 'global' ` +
190
+ `(got ${JSON.stringify(nodePolyfills)}). The 0.2.0 \`true\`/'web3' form was removed; ` +
191
+ `migrate to e.g. ['buffer','process','global'].`,
192
+ );
193
+ }
127
194
  await rm(outdir, { recursive: true, force: true });
128
195
  await mkdir(outdir, { recursive: true });
129
196
 
@@ -139,10 +206,13 @@ export async function buildBundle({
139
206
  },
140
207
  };
141
208
 
142
- // Opt-in Node-builtin polyfill preset (default off). Produces alias/inject/
143
- // define fragments that are merged UNDER the consumer's explicit esbuild opts
144
- // (so the consumer can still override any specific key).
145
- const preset = nodePolyfills ? await nodePolyfillsPreset(outdir) : null;
209
+ // Compose the explicitly-requested Node-builtin polyfill fragments (default
210
+ // none). Produces alias/inject/define merged UNDER the consumer's explicit
211
+ // esbuild opts (so the consumer can still override any specific key).
212
+ const preset =
213
+ nodePolyfills.length > 0
214
+ ? await composeNodePolyfills(nodePolyfills, outdir)
215
+ : null;
146
216
 
147
217
  const {
148
218
  plugins: userPlugins = [],
@@ -162,7 +232,7 @@ export async function buildBundle({
162
232
  target: 'es2022',
163
233
  platform: 'browser',
164
234
  sourcemap: true,
165
- // Merge order: built-ins → preset → consumer. Consumer wins on key clashes
235
+ // Merge order: built-ins → polyfills → consumer. Consumer wins on key clashes
166
236
  // (define/alias/loader) and runs last (plugins). Arrays are concatenated.
167
237
  plugins: [cutResolver, ...userPlugins],
168
238
  inject: [...(preset?.inject ?? []), ...userInject],
package/dist/driver.d.ts CHANGED
@@ -55,7 +55,7 @@ export interface MountOptions {
55
55
  entry?: string;
56
56
  /**
57
57
  * Skip esbuild entirely and treat `cut`/`entry` as a prebuilt `.js` module
58
- * (see `entry`). Default `false` (the esbuild path). `worker`/`nodePolyfills`/
58
+ * (see `entry`). Default `false` (the esbuild path). `worker` / `nodePolyfills` /
59
59
  * `esbuild` options are ignored in this mode.
60
60
  */
61
61
  noBundle?: boolean;
@@ -69,23 +69,40 @@ export interface MountOptions {
69
69
  * async proxy js). */
70
70
  assets?: string[];
71
71
  /**
72
- * Opt-in Node-builtin polyfills for browser bundles of web3 / crypto code
73
- * (ethereumjs, tevm, anything pulling `readable-stream` / `safe-buffer`).
74
- * Default **off** existing consumers are unaffected.
72
+ * Explicit, composable Node-builtin polyfills. Declare exactly what your
73
+ * code-under-test needs there is **no** catch-all preset. Default `[]`
74
+ * (nothing); omitted is the same as `[]`.
75
75
  *
76
- * When enabled it aliases `buffer` / `node:buffer` to the `buffer` npm
77
- * package, injects a `Buffer` global, shims a minimal `process`
78
- * (`{ env: {}, browser: true, version: '', nextTick }`), and sets
79
- * `define: { global: 'globalThis' }`. Requires the optional `buffer`
80
- * dependency to be installed. `true` and `'web3'` are equivalent today.
76
+ * The three builtins the harness can shim itself (with zero opinions):
77
+ * - `'buffer'` — alias `buffer` / `node:buffer` to the [`buffer`] npm
78
+ * package and inject a `Buffer` global (also assigned onto `globalThis`).
79
+ * Requires the optional `buffer` peer dependency to be installed.
80
+ * - `'process'` inject a minimal `process` stub
81
+ * (`{ env: {}, browser: true, version: '', nextTick }`).
82
+ * - `'global'` — set `define: { global: 'globalThis' }`.
83
+ *
84
+ * Each entry is self-contained: `['buffer']` shims ONLY buffer and does NOT
85
+ * touch `process` or `global`. Anything beyond these three (`events`,
86
+ * `stream`, `util`, `crypto`, …) is your job via `esbuild.{alias,inject,
87
+ * define,plugins}` below.
88
+ *
89
+ * web3 / crypto trees (ethereumjs, tevm, anything pulling `readable-stream` /
90
+ * `safe-buffer`) typically want `['buffer','process','global']` plus an
91
+ * `esbuild.alias` for `events`/`stream`. See README "Explicit Node polyfills".
92
+ *
93
+ * **Migration from 0.2.0:** the old `true` / `'web3'` form was removed (clean
94
+ * break — 0.2.0 had ~no users). Replace `true`/`'web3'` with
95
+ * `['buffer','process','global']`.
96
+ *
97
+ * [`buffer`]: https://www.npmjs.com/package/buffer
81
98
  */
82
- nodePolyfills?: boolean | 'web3';
99
+ nodePolyfills?: Array<'buffer' | 'process' | 'global'>;
83
100
  /**
84
101
  * esbuild pass-through merged into the harness's internal browser build.
85
102
  * Consumer values take precedence; arrays are concatenated AFTER the
86
103
  * built-ins, so the `__CUT_MODULE__` resolver and the `.wasm` copy loader
87
104
  * always remain. Combine with `nodePolyfills` for web3 code that still needs
88
- * an extra alias/define.
105
+ * extra builtins (e.g. `alias: { events: 'events', stream: 'stream-browserify' }`).
89
106
  */
90
107
  esbuild?: {
91
108
  plugins?: import('esbuild').Plugin[];
package/dist/driver.js CHANGED
@@ -47,7 +47,7 @@ export async function mountHarness(page, opts) {
47
47
  worker: opts.worker,
48
48
  wasmDirs: opts.wasmDirs ?? [],
49
49
  assets: opts.assets ?? [],
50
- nodePolyfills: opts.nodePolyfills ?? false,
50
+ nodePolyfills: opts.nodePolyfills ?? [],
51
51
  esbuild: opts.esbuild ?? {},
52
52
  });
53
53
  const srv = await startServer({ root: outdir, coi: opts.coi ?? true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-browser-harness",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Playwright harness that bundles your code, serves it under a COOP/COEP-toggleable server, drives it in a real browser, and returns structured results/timings — for testing real browser-storage persistence (IndexedDB/OPFS), cross-origin isolation, Workers, and perf that Node can't fake. Importable machinery + a tiny `init` CLI for the per-project files.",
6
6
  "license": "MIT",