playwright-browser-harness 0.0.1 → 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 +191 -2
- package/dist/build.mjs +197 -11
- package/dist/driver.d.ts +89 -4
- package/dist/driver.js +75 -3
- package/dist/page-bootstrap.js +45 -0
- package/package.json +26 -5
package/README.md
CHANGED
|
@@ -21,6 +21,14 @@ pnpm exec playwright install chromium
|
|
|
21
21
|
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
|
+
`buffer` is an **optional peerDependency** — only needed (and only installed) when
|
|
25
|
+
you ask for the `'buffer'` Node polyfill (see
|
|
26
|
+
[Explicit Node polyfills](#explicit-node-polyfills)):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add -D buffer # only if you use nodePolyfills: ['buffer', ...]
|
|
30
|
+
```
|
|
31
|
+
|
|
24
32
|
## tsconfig (required)
|
|
25
33
|
|
|
26
34
|
```jsonc
|
|
@@ -41,6 +49,28 @@ pnpm exec playwright test
|
|
|
41
49
|
npx playwright-browser-harness update tests # report drift of scaffolded files vs current templates
|
|
42
50
|
```
|
|
43
51
|
|
|
52
|
+
## Named exports
|
|
53
|
+
|
|
54
|
+
The package's `.` entry exports the high-level driver plus the lower-level
|
|
55
|
+
building blocks, all as documented named exports:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import {mountHarness, buildBundle, startServer} from 'playwright-browser-harness';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `mountHarness(page, opts)` — the usual entry point (bundle + serve + drive).
|
|
62
|
+
- `buildBundle(opts)` — esbuild-bundle a `cut` (+ optional worker) to an `outdir`.
|
|
63
|
+
- `startServer({root, coi})` — the tiny COOP/COEP-toggleable static server.
|
|
64
|
+
|
|
65
|
+
Use `buildBundle` / `startServer` directly when you want to drive bundling and
|
|
66
|
+
serving yourself (e.g. a custom Playwright global-setup) instead of `prebuilt`.
|
|
67
|
+
|
|
68
|
+
The in-page glue is importable too, so your **own** bundler can include it:
|
|
69
|
+
|
|
70
|
+
- `playwright-browser-harness/contract` — raw `.ts` contract (`captureEnv`, `timed`, types).
|
|
71
|
+
- `playwright-browser-harness/page-entry` — raw `.ts` page glue (for esbuild/bundler consumers).
|
|
72
|
+
- `playwright-browser-harness/page-bootstrap` — plain `.js` page glue (loadable directly in the browser).
|
|
73
|
+
|
|
44
74
|
## Use
|
|
45
75
|
|
|
46
76
|
```ts
|
|
@@ -66,7 +96,166 @@ test('persists', async ({page}) => {
|
|
|
66
96
|
});
|
|
67
97
|
```
|
|
68
98
|
|
|
69
|
-
`MountOptions`: `cut` (
|
|
70
|
-
|
|
99
|
+
`MountOptions`: `cut` (esbuild path) **or** `entry`/`noBundle` (prebuilt JS, no
|
|
100
|
+
esbuild) **or** `prebuilt` (prebuilt dir); `coi` (default `true`), `worker`,
|
|
101
|
+
`wasmDirs`, `assets`, plus the bundling escape-hatches `nodePolyfills` and
|
|
102
|
+
`esbuild` (see below).
|
|
103
|
+
|
|
104
|
+
## Explicit Node polyfills
|
|
105
|
+
|
|
106
|
+
Many web3 / crypto libraries (ethereumjs, tevm, and anything pulling in
|
|
107
|
+
`readable-stream` / `safe-buffer`) transitively `import 'buffer'` and touch
|
|
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:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const h = await mountHarness(page, {
|
|
139
|
+
cut,
|
|
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'},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pnpm add -D buffer events stream-browserify
|
|
151
|
+
```
|
|
152
|
+
|
|
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']`.
|
|
156
|
+
|
|
157
|
+
### esbuild escape hatch — `esbuild?: { plugins?, inject?, define?, alias?, loader?, external?, tsconfig? }`
|
|
158
|
+
|
|
159
|
+
A pass-through merged into the harness's internal esbuild build, so a consumer
|
|
160
|
+
whose code-under-test needs a plugin/alias/define/loader no longer has to **fork
|
|
161
|
+
the bundler** (replicating the page-entry glue and mounting via `prebuilt`).
|
|
162
|
+
**Consumer values take precedence** (objects shallow-merged with the consumer
|
|
163
|
+
winning; arrays concatenated **after** the built-ins). The harness's own
|
|
164
|
+
`__CUT_MODULE__` resolve plugin, the page-entry entry point, and the
|
|
165
|
+
`.wasm`→`copy` loader **always remain**:
|
|
166
|
+
|
|
167
|
+
| key | merge behaviour |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `plugins` | concatenated **after** the built-in `__CUT_MODULE__` resolver |
|
|
170
|
+
| `inject` | concatenated after the `nodePolyfills` injects (the chosen builtins, if any) |
|
|
171
|
+
| `define` / `alias` | shallow-merged, **consumer key wins** |
|
|
172
|
+
| `loader` | merged **over** `{'.wasm':'copy'}` (e.g. add `{'.svg':'dataurl'}`) |
|
|
173
|
+
| `external` | as given |
|
|
174
|
+
| `tsconfig` | path to a tsconfig esbuild should honour |
|
|
175
|
+
|
|
176
|
+
#### Doing it entirely by hand (no `nodePolyfills`)
|
|
177
|
+
|
|
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:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const h = await mountHarness(page, {
|
|
184
|
+
cut,
|
|
185
|
+
coi: false,
|
|
186
|
+
esbuild: {
|
|
187
|
+
alias: {buffer: 'buffer'}, // the npm `buffer` package
|
|
188
|
+
inject: [resolve(__dirname, 'node-shim.js')], // exports Buffer; sets globalThis.Buffer/process
|
|
189
|
+
define: {global: 'globalThis'},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// node-shim.js
|
|
196
|
+
import {Buffer} from 'buffer';
|
|
197
|
+
if (!globalThis.Buffer) globalThis.Buffer = Buffer;
|
|
198
|
+
if (!globalThis.process) globalThis.process = {env: {}, browser: true, version: '', nextTick: (cb, ...a) => Promise.resolve().then(() => cb(...a))};
|
|
199
|
+
export {Buffer};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Testing a prebuilt artifact (no esbuild)
|
|
203
|
+
|
|
204
|
+
The default path re-bundles your **source** with the harness's esbuild. To
|
|
205
|
+
exercise the **exact bytes your own build emitted** (tsc / tsup / rollup →
|
|
206
|
+
`dist/`), skip esbuild entirely:
|
|
207
|
+
|
|
208
|
+
### `entry` / `noBundle` — wrap one prebuilt JS module
|
|
209
|
+
|
|
210
|
+
Point the harness at an already-built `.js` module that default-exports a
|
|
211
|
+
`CodeUnderTest`. The harness serves it **verbatim** (with its sibling `.map`,
|
|
212
|
+
if present) behind a tiny plain-JS loader — no esbuild, no re-bundle, so source
|
|
213
|
+
maps point at your `dist/`, not a harness re-bundle:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import {mountHarness} from 'playwright-browser-harness';
|
|
217
|
+
|
|
218
|
+
// your own build already produced dist/cut.js (+ dist/cut.js.map)
|
|
219
|
+
const h = await mountHarness(page, {entry: resolve('dist/cut.js'), coi: false});
|
|
220
|
+
// equivalently: { cut: resolve('dist/cut.js'), noBundle: true }
|
|
221
|
+
const w = await h.run({phase: 'write', params: {n: 100}});
|
|
222
|
+
await h.reload();
|
|
223
|
+
const r = await h.run({phase: 'read', params: {n: 100}});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The served directory contains `__cut.js` (your module, verbatim), `contract.js`
|
|
227
|
+
(the harness's compiled glue), `bootstrap.js` (the no-esbuild page glue), and
|
|
228
|
+
`index.html`.
|
|
229
|
+
|
|
230
|
+
### `prebuilt` — serve a fully-built directory
|
|
231
|
+
|
|
232
|
+
If your own bundler already produced a complete directory (its own `index.html`
|
|
233
|
+
that boots the harness and sets `window.__harness`), serve it as-is. The glue is
|
|
234
|
+
importable so your bundler can include it:
|
|
235
|
+
`playwright-browser-harness/page-entry` (raw `.ts`, for bundler consumers) or
|
|
236
|
+
`playwright-browser-harness/page-bootstrap` (plain `.js`, loadable directly).
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
// harness starts its own COOP/COEP server for the dir:
|
|
240
|
+
const h = await mountHarness(page, {prebuilt: {outdir: resolve('dist/site')}, coi: false});
|
|
241
|
+
// or reuse an already-running server (harness won't start/stop one):
|
|
242
|
+
const h2 = await mountHarness(page, {prebuilt: {outdir, serverUrl: 'http://127.0.0.1:5173'}});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## COOP/COEP (`coi`) vs your wasm executor
|
|
246
|
+
|
|
247
|
+
`coi` toggles cross-origin isolation (COOP/COEP response headers →
|
|
248
|
+
`crossOriginIsolated === true`, the precondition for `SharedArrayBuffer`).
|
|
249
|
+
Match it to what your wasm needs:
|
|
250
|
+
|
|
251
|
+
| Workload | `coi` | Why |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| EVM / pure-compute wasm (ethereumjs, tevm, single-threaded wasm) | `false` | No `SharedArrayBuffer`; isolation is pure overhead and can complicate loading. |
|
|
254
|
+
| Threaded wasm needing `SharedArrayBuffer` (wasm threads, OPFS sync VFS, sqlite-wasm `opfs`) | `true` | `SharedArrayBuffer` / the sync OPFS VFS require `crossOriginIsolated`. |
|
|
255
|
+
| Plain IndexedDB / `opfs-sahpool` | `false` | Works without isolation; flip `true` only to test the isolated path. |
|
|
256
|
+
|
|
257
|
+
Default is `coi:true`; **EVM / pure-compute executors want `coi:false`**.
|
|
258
|
+
|
|
259
|
+
See the
|
|
71
260
|
[`playwright-browser-test-harness` research topic](../README.md) for the full
|
|
72
261
|
architecture, gotchas, and per-browser OPFS matrix.
|
package/dist/build.mjs
CHANGED
|
@@ -22,6 +22,131 @@ import { existsSync } from 'node:fs';
|
|
|
22
22
|
|
|
23
23
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Explicit, composable Node-builtin polyfill fragments.
|
|
27
|
+
*
|
|
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:
|
|
33
|
+
*
|
|
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' }`.
|
|
38
|
+
*
|
|
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
|
|
48
|
+
*/
|
|
49
|
+
|
|
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'.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
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;
|
|
148
|
+
}
|
|
149
|
+
|
|
25
150
|
/**
|
|
26
151
|
* @param {object} opts
|
|
27
152
|
* @param {string} opts.cut absolute path to the code-under-test module
|
|
@@ -31,14 +156,74 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|
|
31
156
|
* @param {string[]} [opts.assets] explicit absolute file paths to copy verbatim
|
|
32
157
|
* into outdir (e.g. sqlite3-opfs-async-proxy.js, which the OPFS VFS spawns by
|
|
33
158
|
* URL relative to the worker bundle and esbuild does NOT trace).
|
|
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`.
|
|
166
|
+
* @param {object} [opts.esbuild] esbuild pass-through merged into the internal
|
|
167
|
+
* build. Consumer values take precedence; arrays are concatenated AFTER the
|
|
168
|
+
* built-ins so the `__CUT_MODULE__` resolver, the page-entry entry point, and
|
|
169
|
+
* the `.wasm` copy loader always remain.
|
|
170
|
+
* @param {import('esbuild').Plugin[]} [opts.esbuild.plugins]
|
|
171
|
+
* @param {string[]} [opts.esbuild.inject]
|
|
172
|
+
* @param {Record<string,string>} [opts.esbuild.define]
|
|
173
|
+
* @param {Record<string,string>} [opts.esbuild.alias]
|
|
174
|
+
* @param {Record<string,string>} [opts.esbuild.loader] merged OVER `{'.wasm':'copy'}`
|
|
175
|
+
* @param {string[]} [opts.esbuild.external]
|
|
176
|
+
* @param {string} [opts.esbuild.tsconfig] path to a tsconfig esbuild should honour
|
|
34
177
|
*/
|
|
35
|
-
export async function buildBundle({
|
|
178
|
+
export async function buildBundle({
|
|
179
|
+
cut,
|
|
180
|
+
outdir,
|
|
181
|
+
worker,
|
|
182
|
+
wasmDirs = [],
|
|
183
|
+
assets = [],
|
|
184
|
+
nodePolyfills = [],
|
|
185
|
+
esbuild: esbuildOpts = {},
|
|
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
|
+
}
|
|
36
194
|
await rm(outdir, { recursive: true, force: true });
|
|
37
195
|
await mkdir(outdir, { recursive: true });
|
|
38
196
|
|
|
39
197
|
const entryPoints = [{ in: join(here, 'page-entry.ts'), out: 'bundle' }];
|
|
40
198
|
if (worker) entryPoints.push({ in: worker, out: 'worker' });
|
|
41
199
|
|
|
200
|
+
// The built-in __CUT_MODULE__ resolver plugin must ALWAYS run, even when the
|
|
201
|
+
// consumer supplies their own plugins (concat, never clobber).
|
|
202
|
+
const cutResolver = {
|
|
203
|
+
name: 'resolve-cut',
|
|
204
|
+
setup(b) {
|
|
205
|
+
b.onResolve({ filter: /^__CUT_MODULE__$/ }, () => ({ path: cut }));
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
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;
|
|
216
|
+
|
|
217
|
+
const {
|
|
218
|
+
plugins: userPlugins = [],
|
|
219
|
+
inject: userInject = [],
|
|
220
|
+
define: userDefine = {},
|
|
221
|
+
alias: userAlias = {},
|
|
222
|
+
loader: userLoader = {},
|
|
223
|
+
external: userExternal = [],
|
|
224
|
+
tsconfig: userTsconfig,
|
|
225
|
+
} = esbuildOpts;
|
|
226
|
+
|
|
42
227
|
await esbuild.build({
|
|
43
228
|
entryPoints,
|
|
44
229
|
outdir,
|
|
@@ -47,18 +232,19 @@ export async function buildBundle({ cut, outdir, worker, wasmDirs = [], assets =
|
|
|
47
232
|
target: 'es2022',
|
|
48
233
|
platform: 'browser',
|
|
49
234
|
sourcemap: true,
|
|
50
|
-
//
|
|
51
|
-
plugins
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
],
|
|
235
|
+
// Merge order: built-ins → polyfills → consumer. Consumer wins on key clashes
|
|
236
|
+
// (define/alias/loader) and runs last (plugins). Arrays are concatenated.
|
|
237
|
+
plugins: [cutResolver, ...userPlugins],
|
|
238
|
+
inject: [...(preset?.inject ?? []), ...userInject],
|
|
239
|
+
define: { ...(preset?.define ?? {}), ...userDefine },
|
|
240
|
+
alias: { ...(preset?.alias ?? {}), ...userAlias },
|
|
241
|
+
external: [...userExternal],
|
|
242
|
+
...(userTsconfig ? { tsconfig: userTsconfig } : {}),
|
|
59
243
|
// sqlite-wasm ships an mjs that references the wasm by URL; keep it external
|
|
60
244
|
// to the worker if needed — here we let esbuild bundle JS and copy wasm.
|
|
61
|
-
|
|
245
|
+
// Consumer loaders merge OVER this (e.g. add `.svg`), but `.wasm` stays copy
|
|
246
|
+
// unless the consumer deliberately overrides that exact key.
|
|
247
|
+
loader: { '.wasm': 'copy', ...userLoader },
|
|
62
248
|
});
|
|
63
249
|
|
|
64
250
|
// Copy wasm assets (e.g. @sqlite.org/sqlite-wasm's sqlite3.wasm) next to JS.
|
package/dist/driver.d.ts
CHANGED
|
@@ -27,11 +27,38 @@
|
|
|
27
27
|
* env assertions, and ferrying `{results,timings,errors,env}` across the bridge.
|
|
28
28
|
*/
|
|
29
29
|
import type { Page } from '@playwright/test';
|
|
30
|
+
import { buildBundle } from './build.mjs';
|
|
31
|
+
import { startServer } from './server.mjs';
|
|
30
32
|
import type { EnvInfo, RunContext, RunResult } from './contract.ts';
|
|
31
33
|
declare const here: string;
|
|
32
34
|
export interface MountOptions {
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Absolute path to the code-under-test module (default-exports CodeUnderTest).
|
|
37
|
+
*
|
|
38
|
+
* - **Default (esbuild) path:** point at your SOURCE module; the harness
|
|
39
|
+
* esbuild-bundles it with the page-entry glue.
|
|
40
|
+
* - **Prebuilt-entry path** (`noBundle: true` or use `entry`): point at an
|
|
41
|
+
* ALREADY-BUILT `.js` module (your own tsc/tsup/rollup output). The harness
|
|
42
|
+
* serves it verbatim behind a tiny no-esbuild loader — you exercise the
|
|
43
|
+
* exact bytes your build emitted.
|
|
44
|
+
*
|
|
45
|
+
* Optional when `entry` or `prebuilt` is supplied.
|
|
46
|
+
*/
|
|
47
|
+
cut?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Absolute path to an ALREADY-BUILT `.js` CodeUnderTest module to load with
|
|
50
|
+
* **no esbuild**. Equivalent to `{ cut, noBundle: true }`; provided as a
|
|
51
|
+
* clearer name for the prebuilt-artifact path. The file is copied verbatim
|
|
52
|
+
* (with its sibling `.map` if present) and wrapped by the harness's plain-JS
|
|
53
|
+
* page-bootstrap glue. Mutually exclusive with the esbuild path.
|
|
54
|
+
*/
|
|
55
|
+
entry?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Skip esbuild entirely and treat `cut`/`entry` as a prebuilt `.js` module
|
|
58
|
+
* (see `entry`). Default `false` (the esbuild path). `worker` / `nodePolyfills` /
|
|
59
|
+
* `esbuild` options are ignored in this mode.
|
|
60
|
+
*/
|
|
61
|
+
noBundle?: boolean;
|
|
35
62
|
/** Serve with COOP/COEP (cross-origin isolation). Default true. */
|
|
36
63
|
coi?: boolean;
|
|
37
64
|
/** Optional absolute path to a Web Worker entry to bundle alongside. */
|
|
@@ -41,10 +68,67 @@ export interface MountOptions {
|
|
|
41
68
|
/** Explicit asset files to copy verbatim next to the bundle (e.g. the OPFS
|
|
42
69
|
* async proxy js). */
|
|
43
70
|
assets?: string[];
|
|
44
|
-
/**
|
|
71
|
+
/**
|
|
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
|
+
*
|
|
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
|
|
98
|
+
*/
|
|
99
|
+
nodePolyfills?: Array<'buffer' | 'process' | 'global'>;
|
|
100
|
+
/**
|
|
101
|
+
* esbuild pass-through merged into the harness's internal browser build.
|
|
102
|
+
* Consumer values take precedence; arrays are concatenated AFTER the
|
|
103
|
+
* built-ins, so the `__CUT_MODULE__` resolver and the `.wasm` copy loader
|
|
104
|
+
* always remain. Combine with `nodePolyfills` for web3 code that still needs
|
|
105
|
+
* extra builtins (e.g. `alias: { events: 'events', stream: 'stream-browserify' }`).
|
|
106
|
+
*/
|
|
107
|
+
esbuild?: {
|
|
108
|
+
plugins?: import('esbuild').Plugin[];
|
|
109
|
+
inject?: string[];
|
|
110
|
+
define?: Record<string, string>;
|
|
111
|
+
alias?: Record<string, string>;
|
|
112
|
+
/** Merged OVER the built-in `{'.wasm':'copy'}` loader. */
|
|
113
|
+
loader?: Record<string, import('esbuild').Loader>;
|
|
114
|
+
external?: string[];
|
|
115
|
+
/** Path to a tsconfig esbuild should honour while bundling. */
|
|
116
|
+
tsconfig?: string;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Serve a FULLY-prebuilt directory as-is (no esbuild, no glue injection). The
|
|
120
|
+
* directory must already contain an `index.html` that boots the harness and
|
|
121
|
+
* sets `window.__harness` (e.g. produced by a consumer's own bundler that
|
|
122
|
+
* imported `playwright-browser-harness/page-entry`).
|
|
123
|
+
*
|
|
124
|
+
* - `serverUrl` omitted → the harness starts its own COOP/COEP server for
|
|
125
|
+
* `outdir` (honouring `coi`/`headers`/`mime`).
|
|
126
|
+
* - `serverUrl` provided → the harness reuses that already-running server and
|
|
127
|
+
* does not start or stop one.
|
|
128
|
+
*/
|
|
45
129
|
prebuilt?: {
|
|
46
130
|
outdir: string;
|
|
47
|
-
serverUrl
|
|
131
|
+
serverUrl?: string;
|
|
48
132
|
};
|
|
49
133
|
}
|
|
50
134
|
export interface MountedHarness {
|
|
@@ -58,3 +142,4 @@ export interface MountedHarness {
|
|
|
58
142
|
}
|
|
59
143
|
export declare function mountHarness(page: Page, opts: MountOptions): Promise<MountedHarness>;
|
|
60
144
|
export { here as harnessDir };
|
|
145
|
+
export { buildBundle, startServer };
|
package/dist/driver.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { mkdtemp } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
import { mkdtemp, mkdir, copyFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
4
5
|
import { tmpdir } from 'node:os';
|
|
5
6
|
// build.mjs / server.mjs are JS ESM siblings; imported at runtime by Playwright.
|
|
6
7
|
// @ts-ignore - .mjs has no types
|
|
@@ -12,11 +13,33 @@ export async function mountHarness(page, opts) {
|
|
|
12
13
|
let outdir;
|
|
13
14
|
let serverUrl;
|
|
14
15
|
let close;
|
|
16
|
+
const entry = opts.entry ?? (opts.noBundle ? opts.cut : undefined);
|
|
15
17
|
if (opts.prebuilt) {
|
|
18
|
+
// (b) Serve a fully-prebuilt directory as-is. No esbuild, no glue.
|
|
16
19
|
outdir = opts.prebuilt.outdir;
|
|
17
|
-
|
|
20
|
+
if (opts.prebuilt.serverUrl) {
|
|
21
|
+
serverUrl = opts.prebuilt.serverUrl;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const srv = await startServer({ root: outdir, coi: opts.coi ?? true });
|
|
25
|
+
serverUrl = srv.url;
|
|
26
|
+
close = srv.close;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (entry) {
|
|
30
|
+
// (a) Prebuilt-entry path: wrap an ALREADY-BUILT .js module with the plain-JS
|
|
31
|
+
// page-bootstrap glue — NO esbuild. Exercises the exact built bytes.
|
|
32
|
+
outdir = await mkdtemp(join(tmpdir(), 'harness-'));
|
|
33
|
+
await wrapPrebuiltEntry({ entry, outdir, assets: opts.assets ?? [] });
|
|
34
|
+
const srv = await startServer({ root: outdir, coi: opts.coi ?? true });
|
|
35
|
+
serverUrl = srv.url;
|
|
36
|
+
close = srv.close;
|
|
18
37
|
}
|
|
19
38
|
else {
|
|
39
|
+
// Default esbuild path.
|
|
40
|
+
if (!opts.cut) {
|
|
41
|
+
throw new Error('mountHarness: provide `cut` (esbuild path), `entry`/`noBundle` (prebuilt JS), or `prebuilt` (prebuilt dir).');
|
|
42
|
+
}
|
|
20
43
|
outdir = await mkdtemp(join(tmpdir(), 'harness-'));
|
|
21
44
|
await buildBundle({
|
|
22
45
|
cut: opts.cut,
|
|
@@ -24,6 +47,8 @@ export async function mountHarness(page, opts) {
|
|
|
24
47
|
worker: opts.worker,
|
|
25
48
|
wasmDirs: opts.wasmDirs ?? [],
|
|
26
49
|
assets: opts.assets ?? [],
|
|
50
|
+
nodePolyfills: opts.nodePolyfills ?? [],
|
|
51
|
+
esbuild: opts.esbuild ?? {},
|
|
27
52
|
});
|
|
28
53
|
const srv = await startServer({ root: outdir, coi: opts.coi ?? true });
|
|
29
54
|
serverUrl = srv.url;
|
|
@@ -50,4 +75,51 @@ async function loadAndWait(page, url) {
|
|
|
50
75
|
await page.goto(url, { waitUntil: 'load' });
|
|
51
76
|
await page.waitForFunction(() => window.__harness?.ready === true);
|
|
52
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Prebuilt-entry path: serve an ALREADY-BUILT `.js` CodeUnderTest module behind
|
|
80
|
+
* the harness's plain-JS page-bootstrap glue, with **no esbuild**.
|
|
81
|
+
*
|
|
82
|
+
* It writes a fresh `outdir` containing:
|
|
83
|
+
* - `__cut.js` the consumer's built module, copied verbatim (+ `.map`),
|
|
84
|
+
* - `contract.js` the harness's compiled contract glue (`captureEnv`),
|
|
85
|
+
* - `bootstrap.js` the page-bootstrap glue (imports the two above, sets
|
|
86
|
+
* `window.__harness`),
|
|
87
|
+
* - `index.html` a `type=module` shell loading `./bootstrap.js`.
|
|
88
|
+
*
|
|
89
|
+
* Because nothing is re-bundled, the served `__cut.js` is byte-for-byte the file
|
|
90
|
+
* your build produced — sourcemaps/line numbers point at YOUR dist, not a
|
|
91
|
+
* harness re-bundle.
|
|
92
|
+
*/
|
|
93
|
+
async function wrapPrebuiltEntry({ entry, outdir, assets, }) {
|
|
94
|
+
await mkdir(outdir, { recursive: true });
|
|
95
|
+
// Copy the consumer's built module verbatim as `__cut.js` (+ sibling sourcemap
|
|
96
|
+
// if present, so source maps keep resolving).
|
|
97
|
+
await copyFile(entry, join(outdir, '__cut.js'));
|
|
98
|
+
const map = entry + '.map';
|
|
99
|
+
if (existsSync(map))
|
|
100
|
+
await copyFile(map, join(outdir, '__cut.js.map'));
|
|
101
|
+
// Copy the harness's compiled glue siblings (live next to this driver.js in
|
|
102
|
+
// dist/). These are plain JS the browser loads directly — no bundling.
|
|
103
|
+
await copyFile(join(here, 'contract.js'), join(outdir, 'contract.js'));
|
|
104
|
+
await copyFile(join(here, 'page-bootstrap.js'), join(outdir, 'bootstrap.js'));
|
|
105
|
+
// Copy any explicit assets verbatim (same semantics as the esbuild path).
|
|
106
|
+
for (const a of assets) {
|
|
107
|
+
if (!existsSync(a))
|
|
108
|
+
continue;
|
|
109
|
+
await copyFile(a, join(outdir, basename(a)));
|
|
110
|
+
}
|
|
111
|
+
const html = `<!doctype html>
|
|
112
|
+
<html>
|
|
113
|
+
<head><meta charset="utf-8"><title>harness (prebuilt)</title></head>
|
|
114
|
+
<body>
|
|
115
|
+
<script type="module" src="./bootstrap.js"></script>
|
|
116
|
+
</body>
|
|
117
|
+
</html>`;
|
|
118
|
+
await writeFile(join(outdir, 'index.html'), html);
|
|
119
|
+
}
|
|
53
120
|
export { here as harnessDir };
|
|
121
|
+
// Re-export the lower-level building blocks as documented named exports so a
|
|
122
|
+
// consumer can drive bundling/serving directly (e.g. a custom global-setup, or
|
|
123
|
+
// re-implementing the mount flow with extra steps) without reaching into the
|
|
124
|
+
// package's internal `.mjs` siblings.
|
|
125
|
+
export { buildBundle, startServer };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* page-bootstrap.js — the NO-ESBUILD in-page glue.
|
|
3
|
+
*
|
|
4
|
+
* This is the plain-JS sibling of `page-entry.ts`. Where `page-entry.ts` is
|
|
5
|
+
* BUNDLED by esbuild together with the code-under-test (the default path), this
|
|
6
|
+
* file is served VERBATIM to the browser and loaded as a native ES module. It
|
|
7
|
+
* imports two things by relative URL at runtime:
|
|
8
|
+
*
|
|
9
|
+
* - `./__cut.js` the consumer's ALREADY-BUILT CodeUnderTest module
|
|
10
|
+
* (their own tsc/tsup/rollup output, copied in as-is),
|
|
11
|
+
* - `./contract.js` the harness's compiled contract glue (`captureEnv`).
|
|
12
|
+
*
|
|
13
|
+
* It then exposes `window.__harness` exactly like `page-entry.ts` does, so the
|
|
14
|
+
* Node-side driver (`run`/`reset`/`env`/`reload`) is identical across both
|
|
15
|
+
* paths. The point: you exercise the EXACT bytes your build emitted — no
|
|
16
|
+
* re-bundling, no second esbuild pass, source maps point at your dist file.
|
|
17
|
+
*
|
|
18
|
+
* The two relative specifiers are rewritten by the harness when it writes the
|
|
19
|
+
* served directory (see driver.ts `wrapPrebuiltEntry`).
|
|
20
|
+
*/
|
|
21
|
+
import { captureEnv } from './contract.js';
|
|
22
|
+
// The consumer's prebuilt CodeUnderTest module, copied into the served dir.
|
|
23
|
+
import cut from './__cut.js';
|
|
24
|
+
|
|
25
|
+
const mod = cut;
|
|
26
|
+
|
|
27
|
+
window.__harness = {
|
|
28
|
+
env: () => captureEnv(),
|
|
29
|
+
async run(ctx) {
|
|
30
|
+
try {
|
|
31
|
+
return await mod.run(ctx);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return {
|
|
34
|
+
results: {},
|
|
35
|
+
timings: [],
|
|
36
|
+
errors: [String(e instanceof Error ? (e.stack ?? e.message) : e)],
|
|
37
|
+
env: captureEnv(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
async reset() {
|
|
42
|
+
await mod.reset?.();
|
|
43
|
+
},
|
|
44
|
+
ready: true,
|
|
45
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-browser-harness",
|
|
3
|
-
"version": "0.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",
|
|
@@ -25,11 +25,19 @@
|
|
|
25
25
|
"types": "./dist/contract.ts",
|
|
26
26
|
"default": "./dist/contract.ts"
|
|
27
27
|
},
|
|
28
|
+
"./page-entry": {
|
|
29
|
+
"types": "./dist/page-entry.ts",
|
|
30
|
+
"default": "./dist/page-entry.ts"
|
|
31
|
+
},
|
|
32
|
+
"./page-bootstrap": {
|
|
33
|
+
"default": "./dist/page-bootstrap.js"
|
|
34
|
+
},
|
|
28
35
|
"./package.json": "./package.json"
|
|
29
36
|
},
|
|
30
37
|
"scripts": {
|
|
31
38
|
"build": "node build.mjs",
|
|
32
|
-
"prepack": "node build.mjs"
|
|
39
|
+
"prepack": "node build.mjs",
|
|
40
|
+
"test": "playwright test"
|
|
33
41
|
},
|
|
34
42
|
"files": [
|
|
35
43
|
"dist",
|
|
@@ -39,13 +47,26 @@
|
|
|
39
47
|
],
|
|
40
48
|
"peerDependencies": {
|
|
41
49
|
"@playwright/test": ">=1.40.0",
|
|
50
|
+
"buffer": ">=6.0.0",
|
|
42
51
|
"esbuild": ">=0.20.0"
|
|
43
52
|
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"buffer": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
44
58
|
"devDependencies": {
|
|
45
|
-
"
|
|
46
|
-
"@
|
|
59
|
+
"@ethereumjs/common": "^4.4.0",
|
|
60
|
+
"@ethereumjs/evm": "^3.1.1",
|
|
61
|
+
"@ethereumjs/statemanager": "^2.4.0",
|
|
62
|
+
"@ethereumjs/util": "^9.1.0",
|
|
47
63
|
"@playwright/test": "^1.60.0",
|
|
48
|
-
"
|
|
64
|
+
"@types/node": "^25.9.1",
|
|
65
|
+
"buffer": "^6.0.3",
|
|
66
|
+
"esbuild": "^0.28.0",
|
|
67
|
+
"events": "^3.3.0",
|
|
68
|
+
"stream-browserify": "^3.0.0",
|
|
69
|
+
"typescript": "^6.0.3"
|
|
49
70
|
},
|
|
50
71
|
"keywords": [
|
|
51
72
|
"playwright",
|