tape-six-playwright 1.0.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/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2005-2026 Eugene Lazutkin
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # tape-six-playwright [![NPM version][npm-img]][npm-url]
2
+
3
+ [npm-img]: https://img.shields.io/npm/v/tape-six-playwright.svg
4
+ [npm-url]: https://npmjs.org/package/tape-six-playwright
5
+
6
+ `tape-six-playwright` is a helper for [tape-six](https://www.npmjs.com/package/tape-six)
7
+ to run tests in a headless browser via Playwright. Each test file runs in its own
8
+ iframe inside headless Chromium, providing full browser isolation.
9
+
10
+ ## Why?
11
+
12
+ The standard `tape6` runner uses worker threads. `tape6-playwright` launches headless
13
+ Chromium and runs each test file in a separate iframe, giving tests access to real DOM,
14
+ browser APIs, and the full web platform. Tests can be `.js`/`.mjs` modules or `.html` files.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm i -D tape-six-playwright
20
+ ```
21
+
22
+ Playwright's bundled Chromium is installed automatically via `postinstall`.
23
+
24
+ ## Quick start
25
+
26
+ 1. Write tests using [tape-six](https://www.npmjs.com/package/tape-six) that use browser APIs:
27
+
28
+ ```js
29
+ import test from 'tape-six';
30
+
31
+ test('DOM works', t => {
32
+ const el = document.createElement('div');
33
+ el.textContent = 'hello';
34
+ document.body.appendChild(el);
35
+ t.equal(document.body.lastChild.textContent, 'hello', 'element created');
36
+ });
37
+ ```
38
+
39
+ 2. Configure tests in `package.json`:
40
+
41
+ ```json
42
+ {
43
+ "scripts": {
44
+ "test": "tape6-playwright --start-server --flags FO"
45
+ },
46
+ "tape6": {
47
+ "browser": ["/tests/test-*.html"],
48
+ "tests": ["/tests/test-*.*js"],
49
+ "importmap": {
50
+ "imports": {
51
+ "tape-six": "/node_modules/tape-six/index.js",
52
+ "tape-six/": "/node_modules/tape-six/src/"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ 3. Run:
60
+
61
+ ```bash
62
+ npm test
63
+ ```
64
+
65
+ ## Server
66
+
67
+ `tape6-playwright` requires `tape6-server` (from `tape-six`) to serve test files to the browser.
68
+
69
+ - **Auto-start:** use `--start-server` to launch it automatically.
70
+ - **Manual:** run `npx tape6-server` in a separate terminal, then run tests without `--start-server`.
71
+ - **Custom URL:** use `--server-url URL` (`-u`), or set `TAPE6_SERVER_URL` or `HOST`/`PORT` environment variables.
72
+
73
+ ## Cross-runtime usage
74
+
75
+ ```json
76
+ {
77
+ "scripts": {
78
+ "test": "tape6-playwright --start-server --flags FO",
79
+ "test:bun": "bun run `tape6-playwright --self` --start-server --flags FO",
80
+ "test:deno": "deno run -A `tape6-playwright --self` --start-server --flags FO"
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## Docs
86
+
87
+ See the [wiki](https://github.com/uhop/tape-six-playwright/wiki) for full documentation.
88
+ `tape-six` has its own [wiki](https://github.com/uhop/tape-six/wiki).
89
+
90
+ `tape-six-playwright` uses the same test configuration and CLI conventions as `tape-six`.
91
+
92
+ ### Command-line utilities
93
+
94
+ - [tape6-playwright](https://github.com/uhop/tape-six-playwright/wiki/Utility-‐-tape6‐playwright) — the main utility of this package to run browser tests.
95
+
96
+ ## AI agents
97
+
98
+ If you are an AI coding agent, see [AGENTS.md](./AGENTS.md) for project conventions, commands, and architecture.
99
+
100
+ LLM-friendly documentation is available:
101
+
102
+ - [llms.txt](./llms.txt) — concise reference.
103
+ - [llms-full.txt](./llms-full.txt) — full reference with architecture details.
104
+
105
+ ## Release notes
106
+
107
+ The most recent releases:
108
+
109
+ - 1.0.0 _The first official release._
110
+
111
+ See the full [release notes](https://github.com/uhop/tape-six-playwright/wiki/Release-notes) for details.
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'node:process';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {spawn} from 'node:child_process';
6
+
7
+ import {getOptions, initReporter, showInfo} from 'tape-six/utils/config.js';
8
+
9
+ import {getReporter, setReporter} from 'tape-six/test.js';
10
+ import {selectTimer} from 'tape-six/utils/timer.js';
11
+
12
+ import TestWorker from '../src/TestWorker.js';
13
+
14
+ const rootFolder = process.cwd();
15
+
16
+ const showSelf = () => {
17
+ const self = new URL(import.meta.url);
18
+ if (self.protocol === 'file:') {
19
+ console.log(fileURLToPath(self));
20
+ } else {
21
+ console.log(self);
22
+ }
23
+ process.exit(0);
24
+ };
25
+
26
+ const getServerUrl = () => {
27
+ if (process.env.TAPE6_SERVER_URL) return process.env.TAPE6_SERVER_URL;
28
+ const host = process.env.HOST || 'localhost',
29
+ port = process.env.PORT || '3000';
30
+ return `http://${host}:${port}`;
31
+ };
32
+
33
+ const ensureServer = async (serverUrl, startServer) => {
34
+ try {
35
+ const response = await fetch(serverUrl + '/--tests');
36
+ if (response.ok) return null;
37
+ } catch (error) {
38
+ void error;
39
+ }
40
+
41
+ if (!startServer) {
42
+ console.error(
43
+ `Error: tape6-server is not reachable at ${serverUrl}\n\n` +
44
+ 'Start it manually:\n' +
45
+ ' npx tape6-server\n\n' +
46
+ 'Or re-run with --start-server:\n' +
47
+ ' tape6-playwright --start-server --flags FO\n'
48
+ );
49
+ process.exit(1);
50
+ }
51
+
52
+ // start the server
53
+ const serverBin = fileURLToPath(
54
+ new URL('../node_modules/tape-six/bin/tape6-server.js', import.meta.url)
55
+ );
56
+ const host = new URL(serverUrl).hostname,
57
+ port = new URL(serverUrl).port || '3000';
58
+ const child = spawn(process.execPath, [serverBin], {
59
+ cwd: rootFolder,
60
+ stdio: ['ignore', 'ignore', 'pipe'],
61
+ detached: false,
62
+ env: {...process.env, HOST: host, PORT: port}
63
+ });
64
+
65
+ let exited = false,
66
+ exitCode = null,
67
+ stderrData = '';
68
+ child.stderr.on('data', chunk => (stderrData += chunk));
69
+ child.on('exit', code => {
70
+ exited = true;
71
+ exitCode = code;
72
+ });
73
+ child.unref();
74
+
75
+ // wait for server to become available
76
+ for (let i = 0; i < 30; ++i) {
77
+ await new Promise(resolve => setTimeout(resolve, 500));
78
+ if (exited) {
79
+ console.error(
80
+ `Error: tape6-server exited with code ${exitCode} while starting on ${host}:${port}` +
81
+ (stderrData ? '\n' + stderrData.trim() : '')
82
+ );
83
+ process.exit(1);
84
+ }
85
+ try {
86
+ const response = await fetch(serverUrl + '/--tests');
87
+ if (response.ok) return child;
88
+ } catch (error) {
89
+ void error;
90
+ }
91
+ }
92
+
93
+ child.kill();
94
+ console.error(
95
+ `Error: tape6-server failed to start on ${host}:${port} (timed out after 15s)` +
96
+ (stderrData ? '\n' + stderrData.trim() : '')
97
+ );
98
+ process.exit(1);
99
+ };
100
+
101
+ const main = async () => {
102
+ const options = getOptions({
103
+ '--self': showSelf,
104
+ '--start-server': {isValueRequired: false},
105
+ '--info': {isValueRequired: false},
106
+ '--server-url': {aliases: ['-u'], initialValue: getServerUrl(), isValueRequired: true}
107
+ });
108
+ options.flags.serverUrl = options.optionFlags['--server-url'];
109
+
110
+ await Promise.all([initReporter(getReporter, setReporter, options.flags), selectTimer()]);
111
+
112
+ if (options.optionFlags['--info'] === '') {
113
+ showInfo(options, []);
114
+ process.exit(0);
115
+ }
116
+
117
+ const startServer = options.optionFlags['--start-server'] === '';
118
+
119
+ const serverUrl = options.optionFlags['--server-url'].replace(/\/+$/, '');
120
+ const serverChild = await ensureServer(serverUrl, startServer);
121
+
122
+ console.log(`Connected to ${serverUrl} (${serverChild ? 'self-launched' : 'external'})`);
123
+
124
+ const shutdown = code => {
125
+ serverChild?.kill();
126
+ process.exit(code);
127
+ };
128
+
129
+ process.on('uncaughtException', (error, origin) => {
130
+ console.error('UNHANDLED ERROR:', origin, error);
131
+ shutdown(1);
132
+ });
133
+
134
+ // fetch test files from server
135
+ let files = [];
136
+ try {
137
+ if (options.files.length) {
138
+ const query = options.files.map(p => 'q=' + encodeURIComponent(p)).join('&');
139
+ const response = await fetch(serverUrl + '/--patterns?' + query);
140
+ if (response.ok) files = await response.json();
141
+ }
142
+ if (!files.length) {
143
+ const response = await fetch(serverUrl + '/--tests');
144
+ if (response.ok) files = await response.json();
145
+ }
146
+ } catch (error) {
147
+ void error;
148
+ }
149
+
150
+ if (!files.length) {
151
+ console.log('No test files found on the server.');
152
+ shutdown(1);
153
+ }
154
+
155
+ // fetch importmap from server
156
+ let importmap = null;
157
+ try {
158
+ const response = await fetch(serverUrl + '/--importmap');
159
+ if (response.ok) importmap = await response.json();
160
+ } catch (error) {
161
+ void error;
162
+ }
163
+
164
+ const reporter = getReporter(),
165
+ worker = new TestWorker(reporter, options.parallel, {
166
+ ...options.flags,
167
+ serverUrl,
168
+ importmap
169
+ });
170
+
171
+ reporter.report({type: 'test', test: 0});
172
+
173
+ await new Promise(resolve => {
174
+ worker.done = () => resolve();
175
+ worker.execute(files);
176
+ });
177
+
178
+ const hasFailed = reporter.state && reporter.state.failed > 0;
179
+
180
+ reporter.report({
181
+ type: 'end',
182
+ test: 0,
183
+ fail: hasFailed
184
+ });
185
+
186
+ await worker.cleanup();
187
+
188
+ shutdown(hasFailed ? 1 : 0);
189
+ };
190
+
191
+ main().catch(error => console.error('ERROR:', error));
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'node:process';
4
+ import {fileURLToPath} from 'node:url';
5
+
6
+ if (process.argv.includes('--self')) {
7
+ const self = new URL(import.meta.url);
8
+ if (self.protocol === 'file:') {
9
+ console.log(fileURLToPath(self));
10
+ } else {
11
+ console.log(self);
12
+ }
13
+ } else {
14
+ await import('./tape6-playwright-node.js');
15
+ }
package/llms-full.txt ADDED
@@ -0,0 +1,253 @@
1
+ # tape-six-playwright
2
+
3
+ > A helper for [tape-six](https://github.com/uhop/tape-six) that runs test files in a headless browser via Playwright. Each test file runs in its own iframe inside headless Chromium. The npm package name is `tape-six-playwright` and the CLI command is `tape6-playwright`.
4
+
5
+ - Real browser environment: tests run in headless Chromium with full DOM and browser API access
6
+ - Iframe isolation: each test file runs in its own iframe
7
+ - Cross-runtime: Node, Deno, and Bun with the same test files
8
+ - Drop-in companion for `tape6` (worker-thread runner) and `tape6-proc` (subprocess runner)
9
+ - Same configuration format as `tape-six`
10
+ - Parallel execution with configurable concurrency
11
+ - TAP, TTY (colored), JSONL, and minimal output formats
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm i -D tape-six-playwright
17
+ ```
18
+
19
+ Playwright's bundled Chromium is installed automatically via `postinstall`.
20
+
21
+ ## Quick start
22
+
23
+ Write a test (`tests/test-dom.js`):
24
+
25
+ ```js
26
+ import test from 'tape-six'
27
+
28
+ test('DOM works', t => {
29
+ const el = document.createElement('div')
30
+ el.textContent = 'hello'
31
+ document.body.appendChild(el)
32
+ t.equal(document.body.lastChild.textContent, 'hello', 'element created')
33
+ })
34
+ ```
35
+
36
+ Run all configured tests via Playwright:
37
+
38
+ ```bash
39
+ tape6-playwright --start-server --flags FO
40
+ ```
41
+
42
+ ## CLI: tape6-playwright
43
+
44
+ Runs test files in parallel, each in its own iframe inside headless Chromium.
45
+
46
+ ```bash
47
+ tape6-playwright [--flags FLAGS] [--par N] [--start-server] [--server-url URL] [--info] [tests...]
48
+ ```
49
+
50
+ ### Options
51
+
52
+ - `--flags FLAGS` (`-f`) — output control flags (see Supported flags below).
53
+ - `--par N` (`-p`) — number of parallel iframes. Default: all CPU cores (via `os.availableParallelism()` or `navigator.hardwareConcurrency`).
54
+ - `--start-server` — auto-start `tape6-server` if not already running.
55
+ - `--server-url URL` (`-u`) — server URL. Overrides `TAPE6_SERVER_URL` and `HOST`/`PORT`.
56
+ - `--self` — prints the absolute path to `tape6-playwright.js` and exits. Used in cross-runtime scripts.
57
+ - `--info` — prints runtime, reporter, flags, parallelism, and resolved test files, then exits. Does not require a running server.
58
+ - Positional arguments: test file glob patterns. If none given, resolved from configuration.
59
+ - Options accept `--flags FO` or `--flags=FO`. The `=` form does not support quoting.
60
+
61
+ ### Examples
62
+
63
+ ```bash
64
+ # Run all configured tests
65
+ tape6-playwright --start-server --flags FO
66
+
67
+ # Run specific test files
68
+ tape6-playwright --start-server tests/test-dom.js
69
+
70
+ # Limit parallelism
71
+ tape6-playwright --start-server --par 2 --flags FO
72
+
73
+ # Use with already-running server
74
+ tape6-playwright --flags FO
75
+ ```
76
+
77
+ ## Cross-runtime usage
78
+
79
+ `tape6-playwright` is a Node CLI by default. For Bun and Deno, use the `--self` flag to get the script path:
80
+
81
+ ```json
82
+ {
83
+ "scripts": {
84
+ "test": "tape6-playwright --start-server --flags FO",
85
+ "test:bun": "bun run `tape6-playwright --self` --start-server --flags FO",
86
+ "test:deno": "deno run -A `tape6-playwright --self` --start-server --flags FO"
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## Supported flags
92
+
93
+ Flags are a string of characters. Uppercase = enabled, lowercase = disabled.
94
+
95
+ - `F` — Failures only: show only failed tests.
96
+ - `T` — show Time for each test.
97
+ - `B` — show Banner with summary.
98
+ - `D` — show Data of failed tests.
99
+ - `O` — fail Once: stop at first failure.
100
+ - `N` — show assert Number.
101
+ - `M` — Monochrome: no colors.
102
+ - `C` — don't Capture console output.
103
+ - `H` — Hide streams and console output.
104
+
105
+ Usage:
106
+
107
+ ```bash
108
+ tape6-playwright --start-server --flags FO
109
+ TAPE6_FLAGS=FO tape6-playwright --start-server
110
+ ```
111
+
112
+ ## Server
113
+
114
+ `tape6-playwright` requires `tape6-server` (from `tape-six`) to serve test files to the browser.
115
+
116
+ - `--start-server`: auto-starts the server before running tests.
117
+ - Without it: the server must already be running. The runner prints instructions if it's unreachable.
118
+ - Server URL: `TAPE6_SERVER_URL` env var, or `HOST`/`PORT`, or default `http://localhost:3000`.
119
+ - Server endpoints used: `GET /--tests` (test file list), `GET /--patterns?q=...` (filtered file list), `GET /--importmap` (import map).
120
+
121
+ ## Configuration
122
+
123
+ Configuration is read from `tape6.json` or the `"tape6"` section of `package.json` (same format as `tape-six`):
124
+
125
+ ```json
126
+ {
127
+ "tape6": {
128
+ "browser": ["/tests/test-*.html"],
129
+ "tests": ["/tests/test-*.*js"],
130
+ "importmap": {
131
+ "imports": {
132
+ "tape-six": "/node_modules/tape-six/index.js",
133
+ "tape-six/": "/node_modules/tape-six/src/"
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ The `importmap` section is served by `tape6-server` at `/--importmap` and injected into each iframe for module resolution.
141
+
142
+ ## Environment variables
143
+
144
+ - `TAPE6_FLAGS` — flags string (combined with `--flags` CLI argument).
145
+ - `TAPE6_PAR` — number of parallel iframes (overridden by `--par`).
146
+ - `TAPE6_TAP` — force TAP reporter (any non-empty value).
147
+ - `TAPE6_JSONL` — force JSONL reporter (any non-empty value).
148
+ - `TAPE6_MIN` — force minimal reporter (any non-empty value).
149
+ - `TAPE6_SERVER_URL` — full server URL override (e.g. `http://localhost:4000`). Overridden by `--server-url`.
150
+ - `HOST` — server hostname (default: `localhost`).
151
+ - `PORT` — server port (default: `3000`).
152
+
153
+ ## Architecture
154
+
155
+ ### Entry point
156
+
157
+ `bin/tape6-playwright.js` is the CLI entry point:
158
+
159
+ - With `--self`: prints its own absolute path and exits.
160
+ - Otherwise: delegates to `bin/tape6-playwright-node.js`.
161
+
162
+ ### Main CLI (`bin/tape6-playwright-node.js`)
163
+
164
+ 1. Parses CLI arguments via `getOptions()` from `tape-six` (`--flags`, `--par`, `--start-server`, `--server-url`, `--info`, positional test patterns).
165
+ 2. Initializes the reporter via `initReporter()` from `tape-six`.
166
+ 3. Ensures `tape6-server` is reachable (auto-starts if `--start-server`).
167
+ 4. Fetches test files from server via `GET /--patterns?q=...` (if patterns given) or `GET /--tests`.
168
+ 5. Fetches importmap from `GET /--importmap`.
169
+ 6. Creates a `TestWorker` instance and executes all test files.
170
+ 7. Reports final results and exits with code 0 (success) or 1 (failures).
171
+ 8. Kills auto-started server on exit.
172
+
173
+ ### TestWorker (`src/TestWorker.js`)
174
+
175
+ Extends `EventServer` from `tape-six`. Manages Playwright browser and iframe execution:
176
+
177
+ - **`constructor(reporter, numberOfTasks, options)`** — launches headless Chromium, creates a page, exposes `__tape6_reporter` and `__tape6_error` as page globals.
178
+ - **`makeTask(fileName)`** — creates an iframe for the test file. Returns a task ID.
179
+ - **`destroyTask(id)`** — removes the iframe from the page.
180
+ - **`cleanup()`** — closes the browser.
181
+
182
+ ### Iframe lifecycle
183
+
184
+ For `.html` files:
185
+ 1. Set iframe `src` to the file URL with query parameters (`id`, `test-file-name`, `flags`).
186
+ 2. The HTML file loads `tape-six` which auto-detects `window.parent.__tape6_reporter`.
187
+
188
+ For `.js`/`.mjs` files:
189
+ 1. Create iframe, write an HTML document with `importmap` and a dynamic `<script type="module">`.
190
+ 2. The script sets `window.__tape6_id`, `window.__tape6_testFileName`, `window.__tape6_flags`.
191
+ 3. Dynamically appends a `<script>` element pointing to the test file URL on the server.
192
+ 4. `tape-six` initializes, detects `window.parent.__tape6_reporter`, and uses `ProxyReporter`.
193
+
194
+ ### Event flow
195
+
196
+ ```
197
+ iframe tape-six → ProxyReporter → window.parent.__tape6_reporter(id, event)
198
+ → page.exposeFunction → Node.js TestWorker.report(id, event) → reporter
199
+ ```
200
+
201
+ ### Unsupported files
202
+
203
+ `.cjs`, `.ts`, `.cts`, `.mts` files are skipped with a warning.
204
+
205
+ ## Dependencies
206
+
207
+ - **`tape-six`** — the core test library. Imports: `State.js`, `utils/EventServer.js`, `utils/config.js` (`getOptions`, `initReporter`, `showInfo`), `test.js`, `utils/timer.js`.
208
+ - **`playwright`** — headless browser automation. Bundled Chromium installed via `postinstall`.
209
+
210
+ ## Writing tests
211
+
212
+ Tests are standard `tape-six` tests. See the [tape-six documentation](https://github.com/uhop/tape-six/wiki) for the full API.
213
+
214
+ ```js
215
+ import test from 'tape-six'
216
+
217
+ test('browser APIs', t => {
218
+ t.ok(typeof window === 'object', 'window exists')
219
+ t.ok(typeof document === 'object', 'document exists')
220
+ t.equal(typeof fetch, 'function', 'fetch available')
221
+ })
222
+
223
+ test('DOM manipulation', t => {
224
+ const el = document.createElement('div')
225
+ el.classList.add('test')
226
+ t.ok(el.classList.contains('test'), 'classList works')
227
+ })
228
+ ```
229
+
230
+ `.html` test files can include their own importmap and inline scripts:
231
+
232
+ ```html
233
+ <!doctype html>
234
+ <html>
235
+ <head>
236
+ <script type="importmap">
237
+ { "imports": { "tape-six": "/node_modules/tape-six/index.js" } }
238
+ </script>
239
+ <script type="module">
240
+ import test from 'tape-six'
241
+ test('html test', t => { t.ok(true, 'works') })
242
+ </script>
243
+ </head>
244
+ <body></body>
245
+ </html>
246
+ ```
247
+
248
+ ## Links
249
+
250
+ - Docs: https://github.com/uhop/tape-six-playwright/wiki
251
+ - npm: https://www.npmjs.com/package/tape-six-playwright
252
+ - tape-six: https://github.com/uhop/tape-six
253
+ - tape-six LLM reference: https://github.com/uhop/tape-six/blob/master/llms.txt
package/llms.txt ADDED
@@ -0,0 +1,122 @@
1
+ # tape-six-playwright
2
+
3
+ > Helper for [tape-six](https://github.com/uhop/tape-six) that runs test files in a headless browser via Playwright. Each test file runs in its own iframe inside headless Chromium. Works with Node, Deno, and Bun.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -D tape-six-playwright
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ 1. Write tests using `tape-six` with browser APIs:
14
+
15
+ ```js
16
+ import test from 'tape-six'
17
+
18
+ test('DOM test', t => {
19
+ const el = document.createElement('div')
20
+ el.textContent = 'hello'
21
+ t.equal(el.textContent, 'hello', 'element works')
22
+ })
23
+ ```
24
+
25
+ 2. Configure tests in `package.json`:
26
+
27
+ ```json
28
+ {
29
+ "scripts": {
30
+ "test": "tape6-playwright --start-server --flags FO"
31
+ },
32
+ "tape6": {
33
+ "browser": ["/tests/test-*.html"],
34
+ "tests": ["/tests/test-*.*js"],
35
+ "importmap": {
36
+ "imports": {
37
+ "tape-six": "/node_modules/tape-six/index.js",
38
+ "tape-six/": "/node_modules/tape-six/src/"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ 3. Run: `npm test`
46
+
47
+ ## Why use tape-six-playwright?
48
+
49
+ - **Real browser environment** — tests run in headless Chromium with full DOM and browser API access.
50
+ - **Iframe isolation** — each test file runs in its own iframe.
51
+ - **Cross-runtime** — works with Node, Deno, and Bun using the same test files.
52
+ - **Drop-in companion** — uses the same configuration and test format as `tape6` and `tape6-proc`.
53
+
54
+ ## CLI: tape6-playwright
55
+
56
+ ```bash
57
+ tape6-playwright [--flags FLAGS] [--par N] [--start-server] [--server-url URL] [--info] [tests...]
58
+ ```
59
+
60
+ ### Options
61
+
62
+ - `--flags FLAGS` (`-f`) — output control flags (same as tape6: F=failures only, T=time, B=banner, D=data, O=fail once, N=assert number, M=monochrome, C=don't capture console, H=hide streams). Can be specified multiple times.
63
+ - `--par N` (`-p`) — number of parallel iframes (default: all CPU cores).
64
+ - `--start-server` — auto-start `tape6-server` if not already running.
65
+ - `--server-url URL` (`-u`) — server URL (overrides `TAPE6_SERVER_URL`).
66
+ - `--self` — prints the path to `tape6-playwright.js` (for cross-runtime scripts).
67
+ - `--info` — prints runtime, reporter, flags, parallelism, and resolved test files, then exits.
68
+ - No arguments: runs tests from configuration.
69
+ - Options accept `--flags FO` or `--flags=FO`. The `=` form does not support quoting.
70
+
71
+ ### Cross-runtime usage
72
+
73
+ ```json
74
+ {
75
+ "scripts": {
76
+ "test": "tape6-playwright --start-server --flags FO",
77
+ "test:bun": "bun run `tape6-playwright --self` --start-server --flags FO",
78
+ "test:deno": "deno run -A `tape6-playwright --self` --start-server --flags FO"
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Server
84
+
85
+ Requires `tape6-server` (from `tape-six`) to serve test files. Use `--start-server` to auto-start it, or run `npx tape6-server` manually.
86
+
87
+ Server URL: `TAPE6_SERVER_URL` env var, or `HOST`/`PORT`, default `http://localhost:3000`.
88
+
89
+ ## Configuration
90
+
91
+ Same as `tape-six`. Reads from `tape6.json` or the `"tape6"` section of `package.json`:
92
+
93
+ ```json
94
+ {
95
+ "tape6": {
96
+ "browser": ["/tests/test-*.html"],
97
+ "tests": ["/tests/test-*.*js"],
98
+ "importmap": {
99
+ "imports": {
100
+ "tape-six": "/node_modules/tape-six/index.js",
101
+ "tape-six/": "/node_modules/tape-six/src/"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## Environment variables
109
+
110
+ - `TAPE6_FLAGS` — flags string.
111
+ - `TAPE6_PAR` — number of parallel iframes.
112
+ - `TAPE6_TAP` — force TAP reporter.
113
+ - `TAPE6_JSONL` — force JSONL reporter.
114
+ - `TAPE6_MIN` — force minimal reporter.
115
+ - `TAPE6_SERVER_URL` — full server URL override.
116
+
117
+ ## Links
118
+
119
+ - Docs: https://github.com/uhop/tape-six-playwright/wiki
120
+ - npm: https://www.npmjs.com/package/tape-six-playwright
121
+ - tape-six: https://github.com/uhop/tape-six
122
+ - Full LLM reference: https://github.com/uhop/tape-six-playwright/blob/master/llms-full.txt
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "tape-six-playwright",
3
+ "version": "1.0.0",
4
+ "description": "Playwright-based browser test runner for tape-six. Runs each test file in its own iframe inside headless Chromium. Works with Node, Deno, and Bun.",
5
+ "type": "module",
6
+ "bin": {
7
+ "tape6-playwright": "bin/tape6-playwright.js",
8
+ "tape6-playwright-node": "bin/tape6-playwright-node.js"
9
+ },
10
+ "scripts": {
11
+ "lint": "prettier --check .",
12
+ "lint:fix": "prettier --write .",
13
+ "browser": "playwright install chromium",
14
+ "postinstall": "playwright install chromium",
15
+ "test": "node ./bin/tape6-playwright.js --start-server --flags FO",
16
+ "test:bun": "bun run ./bin/tape6-playwright.js --start-server --flags FO",
17
+ "test:deno": "deno run -A ./bin/tape6-playwright.js --start-server --flags FO"
18
+ },
19
+ "keywords": [
20
+ "tap",
21
+ "tape6",
22
+ "tape-six",
23
+ "test",
24
+ "test-runner",
25
+ "unit-test",
26
+ "testing",
27
+ "harness",
28
+ "browser",
29
+ "playwright",
30
+ "headless",
31
+ "chromium",
32
+ "iframe",
33
+ "parallel",
34
+ "cross-runtime",
35
+ "nodejs",
36
+ "deno",
37
+ "bun",
38
+ "esm",
39
+ "es-modules"
40
+ ],
41
+ "author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com)",
42
+ "funding": "https://github.com/sponsors/uhop",
43
+ "license": "BSD-3-Clause",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/uhop/tape-six-playwright.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/uhop/tape-six-playwright/issues"
50
+ },
51
+ "homepage": "https://github.com/uhop/tape-six-playwright#readme",
52
+ "github": "http://github.com/uhop/tape-six-playwright",
53
+ "llms": "https://raw.githubusercontent.com/uhop/tape-six-playwright/master/llms.txt",
54
+ "llmsFull": "https://raw.githubusercontent.com/uhop/tape-six-playwright/master/llms-full.txt",
55
+ "files": [
56
+ "bin",
57
+ "src",
58
+ "llms.txt",
59
+ "llms-full.txt"
60
+ ],
61
+ "dependencies": {
62
+ "playwright": "^1.52.0",
63
+ "tape-six": "^1.7.8"
64
+ },
65
+ "tape6": {
66
+ "browser": [
67
+ "/tests/test-*.html"
68
+ ],
69
+ "tests": [
70
+ "/tests/test-*.*js"
71
+ ],
72
+ "importmap": {
73
+ "imports": {
74
+ "tape-six": "/node_modules/tape-six/index.js",
75
+ "tape-six/": "/node_modules/tape-six/src/"
76
+ }
77
+ }
78
+ },
79
+ "devDependencies": {
80
+ "prettier": "^3.8.1"
81
+ }
82
+ }
@@ -0,0 +1,169 @@
1
+ import {chromium} from 'playwright';
2
+
3
+ import {isStopTest} from 'tape-six/State.js';
4
+ import EventServer from 'tape-six/utils/EventServer.js';
5
+
6
+ const supportedExtRe = /\.(?:js|mjs|htm|html)$/i;
7
+
8
+ export default class TestWorker extends EventServer {
9
+ #ready;
10
+ constructor(reporter, numberOfTasks, options) {
11
+ super(reporter, numberOfTasks, options);
12
+ this.counter = 0;
13
+ this.browser = null;
14
+ this.page = null;
15
+ this.#ready = this.#init();
16
+ }
17
+ async #init() {
18
+ this.browser = await chromium.launch({headless: true, args: ['--no-sandbox']});
19
+ this.page = await this.browser.newPage();
20
+
21
+ this.page.on('pageerror', e => console.error(e));
22
+
23
+ // navigate to server so iframes inherit the correct origin
24
+ await this.page.goto(this.options.serverUrl + '/--tests', {waitUntil: 'load'});
25
+ await this.page.evaluate(() => {
26
+ document.documentElement.innerHTML = '<head></head><body></body>';
27
+ });
28
+
29
+ // forward console messages only after the page is set up
30
+ this.page.on('console', msg =>
31
+ console[typeof console[msg.type()] == 'function' ? msg.type() : 'log'](msg.text())
32
+ );
33
+
34
+ await this.page.exposeFunction('__tape6_reporter', (id, event) => {
35
+ try {
36
+ this.report(id, event);
37
+ if ((event.type === 'end' && event.test === 0) || event.type === 'terminated') {
38
+ this.close(id);
39
+ }
40
+ } catch (error) {
41
+ if (!isStopTest(error)) throw error;
42
+ }
43
+ });
44
+
45
+ await this.page.exposeFunction('__tape6_error', (id, error) => {
46
+ if (error) {
47
+ this.report(id, {
48
+ type: 'comment',
49
+ name: 'fail to load: ' + (error.message || 'Worker error'),
50
+ test: 0
51
+ });
52
+ try {
53
+ this.report(id, {
54
+ name: String(error),
55
+ test: 0,
56
+ marker: new Error(),
57
+ operator: 'error',
58
+ fail: true,
59
+ data: {actual: error}
60
+ });
61
+ } catch (error) {
62
+ if (!isStopTest(error)) throw error;
63
+ }
64
+ }
65
+ this.close(id);
66
+ });
67
+ }
68
+ makeTask(fileName) {
69
+ const id = String(++this.counter);
70
+ if (!supportedExtRe.test(fileName)) {
71
+ this.report(id, {
72
+ name: 'unsupported file type: ' + fileName,
73
+ test: 0,
74
+ marker: new Error(),
75
+ operator: 'error',
76
+ fail: true
77
+ });
78
+ this.report(id, {type: 'terminated', test: 0, name: 'FILE: /' + fileName});
79
+ this.close(id);
80
+ return id;
81
+ }
82
+ this.#ready
83
+ .then(() => this.#runInIframe(id, fileName))
84
+ .catch(error => {
85
+ console.error('Failed to run test:', fileName, error);
86
+ this.close(id);
87
+ });
88
+ return id;
89
+ }
90
+ async #runInIframe(id, fileName) {
91
+ const importmap = this.options.importmap,
92
+ flags = this.options.flags || '';
93
+
94
+ try {
95
+ if (/\.html?$/i.test(fileName)) {
96
+ const search = new URLSearchParams({id, 'test-file-name': fileName});
97
+ if (flags) search.set('flags', flags);
98
+ const url = '/' + fileName + '?' + search.toString();
99
+ await this.page.evaluate(
100
+ ({url, frameId}) => {
101
+ const iframe = document.createElement('iframe');
102
+ iframe.id = 'test-iframe-' + frameId;
103
+ iframe.src = url;
104
+ iframe.onerror = error => window.__tape6_error(frameId, error);
105
+ document.body.append(iframe);
106
+ },
107
+ {url, frameId: id}
108
+ );
109
+ } else {
110
+ const html =
111
+ '<!doctype html>' +
112
+ '<html lang="en"><head>' +
113
+ '<meta charset="utf-8" />' +
114
+ (importmap
115
+ ? '<script type="importmap">' + JSON.stringify(importmap) + '<\/script>'
116
+ : '') +
117
+ '<script type="module">' +
118
+ 'window.__tape6_id = ' +
119
+ JSON.stringify(id) +
120
+ ';' +
121
+ 'window.__tape6_testFileName = ' +
122
+ JSON.stringify(fileName) +
123
+ ';' +
124
+ 'window.__tape6_flags = ' +
125
+ JSON.stringify(flags) +
126
+ ';' +
127
+ 'const s = document.createElement("script");' +
128
+ 's.setAttribute("type", "module");' +
129
+ 's.src = "/' +
130
+ fileName +
131
+ '";' +
132
+ 's.onerror = error => window.parent.__tape6_error(' +
133
+ JSON.stringify(id) +
134
+ ', error && error.message || "Script load error");' +
135
+ 'document.documentElement.appendChild(s);' +
136
+ '<\/script>' +
137
+ '</head><body></body></html>';
138
+ await this.page.evaluate(
139
+ ({frameId, srcdoc}) => {
140
+ const iframe = document.createElement('iframe');
141
+ iframe.id = 'test-iframe-' + frameId;
142
+ iframe.srcdoc = srcdoc;
143
+ document.body.append(iframe);
144
+ },
145
+ {frameId: id, srcdoc: html}
146
+ );
147
+ }
148
+ } catch (error) {
149
+ console.error('Failed to create iframe for:', fileName, error);
150
+ this.close(id);
151
+ }
152
+ }
153
+ destroyTask(id) {
154
+ if (!this.page) return;
155
+ this.page
156
+ .evaluate(frameId => {
157
+ const iframe = document.getElementById('test-iframe-' + frameId);
158
+ if (iframe) iframe.parentElement.removeChild(iframe);
159
+ }, id)
160
+ .catch(() => {});
161
+ }
162
+ async cleanup() {
163
+ if (this.browser) {
164
+ await this.browser.close();
165
+ this.browser = null;
166
+ this.page = null;
167
+ }
168
+ }
169
+ }