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 +11 -0
- package/README.md +111 -0
- package/bin/tape6-playwright-node.js +191 -0
- package/bin/tape6-playwright.js +15 -0
- package/llms-full.txt +253 -0
- package/llms.txt +122 -0
- package/package.json +82 -0
- package/src/TestWorker.js +169 -0
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
|
+
}
|