qunitx-cli 0.22.3 → 0.23.1
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 +104 -31
- package/dist/cli.js +1621 -793
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ output to the terminal.
|
|
|
29
29
|
- `--port` defaults to 1234 and auto-increments if taken; fails fast if an explicit port is unavailable
|
|
30
30
|
- `--browser` flag to run tests in Chromium, Firefox, or WebKit
|
|
31
31
|
- `--version` / `-v` prints the installed version
|
|
32
|
+
- Optional daemon mode (`qunitx daemon start`) keeps Chrome and the esbuild context warm across runs — roughly halves the wall-clock time of repeated invocations
|
|
32
33
|
- Docker image for zero-install CI usage
|
|
33
34
|
|
|
34
35
|
## Installation
|
|
@@ -106,6 +107,56 @@ qunitx test/**/*.js --browser=webkit
|
|
|
106
107
|
> npx playwright install webkit
|
|
107
108
|
> ```
|
|
108
109
|
|
|
110
|
+
## Daemon mode
|
|
111
|
+
|
|
112
|
+
**`qunitx daemon start` is optional.** Set `QUNITX_DAEMON=1` once (in your shell, `.env`, or a CI step) and plain `qunitx <file>` invocations auto-spawn the daemon on first use, then transparently route through it on every run after — no extra commands, no flags, nothing to remember between invocations. The explicit `daemon start` / `stop` subcommands exist only for when you want to control the lifecycle yourself.
|
|
113
|
+
|
|
114
|
+
What it does: cold-start cost dominates a single `qunitx` run — launching Chrome, loading playwright-core, and creating an esbuild incremental context together account for most of the wall-clock time on a small suite. The daemon keeps all three resources alive across runs so subsequent invocations skip them entirely — roughly **2-3× faster** on repeated runs of the same suite.
|
|
115
|
+
|
|
116
|
+
A single one-off run won't get faster from spinning the daemon up; it's an opt-in optimization aimed at two situations:
|
|
117
|
+
|
|
118
|
+
- **Local TDD loops.** Export `QUNITX_DAEMON=1` in your shell profile (or run `qunitx daemon start` once at the top of your session) and forget about it — subsequent runs reuse the daemon automatically until you `daemon stop` or 30 idle minutes pass.
|
|
119
|
+
- **Monorepo CI** where each package shells out to `qunitx` separately. Set `QUNITX_DAEMON=1` for the job and a single daemon is auto-spawned and reused across every package's invocation.
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
# Start a background daemon for this project
|
|
123
|
+
qunitx daemon start
|
|
124
|
+
|
|
125
|
+
# Run tests as usual — the cli auto-detects the daemon and routes through it
|
|
126
|
+
qunitx test/**/*.ts
|
|
127
|
+
|
|
128
|
+
# Stop it when you're done
|
|
129
|
+
qunitx daemon stop
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`--watch` and `--open` manage their own browser lifecycle and bypass the daemon automatically. Single-invocation CI jobs (where `CI=1` is set) also bypass it by default — `QUNITX_DAEMON=1` overrides if you want it on anyway.
|
|
133
|
+
|
|
134
|
+
### Daemon subcommands
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
qunitx daemon start # Launch a detached daemon for this cwd (idempotent)
|
|
138
|
+
qunitx daemon stop # Ask the running daemon to exit and wait until it has
|
|
139
|
+
qunitx daemon status # Print the live daemon's pid, socket, and uptime
|
|
140
|
+
qunitx daemon restart # Stop + start in one step
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### How it works
|
|
144
|
+
|
|
145
|
+
`qunitx daemon start` spawns a detached Node process listening on a per-project Unix socket (named pipe on Windows). Subsequent `qunitx` invocations detect the live socket and forward `argv` + `cwd` + `env` to the daemon; the daemon executes the run in-process and streams TAP back to your terminal. Ctrl+C is forwarded — the daemon abandons the in-flight run cleanly and stays up for the next one. A single daemon serves one run at a time; concurrent invocations queue in arrival order.
|
|
146
|
+
|
|
147
|
+
Running `qunitx daemon start` upfront is optional. With `QUNITX_DAEMON=1` set in your environment, a plain `qunitx <file>` invocation will spawn the daemon on its own when it doesn't find one already running — so the very first run pays the spawn cost and every run after that is warm. Without `QUNITX_DAEMON=1`, the cli skips auto-spawn and just runs locally; `qunitx daemon start` then becomes the explicit way to opt in.
|
|
148
|
+
|
|
149
|
+
### Debugging the daemon
|
|
150
|
+
|
|
151
|
+
The daemon process is detached with `stdio: 'ignore'`, so its idle/startup/shutdown output never reaches a terminal. Set `QUNITX_DAEMON_LOG=<path>` before `daemon start` to redirect the daemon's stdout + stderr to a file:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
QUNITX_DAEMON_LOG=/tmp/qunitx-daemon.log qunitx daemon start
|
|
155
|
+
tail -f /tmp/qunitx-daemon.log
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The log captures startup banners, browser-crash recovery, idle-timeout shutdown, package.json-mutation restarts, and any unhandled rejection. During an active run the per-run interceptor still forwards stdout to the client; the log catches everything else.
|
|
159
|
+
|
|
109
160
|
## Writing Tests
|
|
110
161
|
|
|
111
162
|
qunitx-cli runs [QUnitX](https://github.com/izelnakri/qunitx) tests — a superset of QUnit with async
|
|
@@ -177,17 +228,17 @@ All CLI flags can also be set in `package.json` under the `qunitx` key, so you d
|
|
|
177
228
|
}
|
|
178
229
|
```
|
|
179
230
|
|
|
180
|
-
| Key | Default
|
|
181
|
-
| ------------ |
|
|
182
|
-
| `inputs` | `[]`
|
|
183
|
-
| `htmlPaths` | `[]`
|
|
184
|
-
| `extensions` | `["js", "ts", "jsx", "tsx"]`
|
|
185
|
-
| `output` | `"tmp"`
|
|
186
|
-
| `timeout` | `20000`
|
|
187
|
-
| `failFast` | `false`
|
|
188
|
-
| `port` | `1234`
|
|
189
|
-
| `browser` | `"chromium"`
|
|
190
|
-
| `plugins` | `[]`
|
|
231
|
+
| Key | Default | Description |
|
|
232
|
+
| ------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
233
|
+
| `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
|
|
234
|
+
| `htmlPaths` | `[]` | Optional HTML templates to run tests inside. Any listed `.html` file that contains `{{qunitxScript}}` or other handlebars-style tokens is treated as a test runner template. |
|
|
235
|
+
| `extensions` | `["js", "ts", "jsx", "tsx"]` | File extensions tracked for test discovery (directory scans) and watch-mode rebuild triggers. Add `"mjs"`, `"cjs"`, or any other extension your project uses. |
|
|
236
|
+
| `output` | `"tmp"` | Directory where compiled test bundles are written. |
|
|
237
|
+
| `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
|
|
238
|
+
| `failFast` | `false` | Stop the run after the first failing test. |
|
|
239
|
+
| `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
|
|
240
|
+
| `browser` | `"chromium"` | Browser engine to use: `"chromium"`, `"firefox"`, or `"webkit"`. Overridden by `--browser` on the CLI. |
|
|
241
|
+
| `plugins` | `[]` | esbuild plugin specifiers loaded from your `node_modules` and applied to the test bundle. See [esbuild plugins](#esbuild-plugins). |
|
|
191
242
|
|
|
192
243
|
CLI flags always override `package.json` values when both are present.
|
|
193
244
|
|
|
@@ -245,20 +296,20 @@ For file formats esbuild does not handle natively (e.g. `.vue` SFCs, `.svelte`),
|
|
|
245
296
|
|
|
246
297
|
Each entry is one of:
|
|
247
298
|
|
|
248
|
-
| Form
|
|
249
|
-
|
|
|
250
|
-
| `"<package-name>"`
|
|
299
|
+
| Form | Behavior |
|
|
300
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| `"<package-name>"` | Imports the package. If the default export is a function, it's called with no arguments to produce the plugin; otherwise the export is used as the plugin. |
|
|
251
302
|
| `["<package-name>", <options>]` | Same, but the factory is called with `<options>` as its only argument. Use this form to pass plugin-specific configuration. |
|
|
252
|
-
| `"./relative/plugin.js"`
|
|
303
|
+
| `"./relative/plugin.js"` | Loads a plugin you wrote yourself. Resolved against the project root (where your `package.json` lives). |
|
|
253
304
|
|
|
254
305
|
Don't forget to add the plugin's file extension(s) to `qunitx.extensions` so directory scans and watch-mode rebuilds pick them up.
|
|
255
306
|
|
|
256
307
|
### Environment variables
|
|
257
308
|
|
|
258
|
-
| Variable | Description
|
|
259
|
-
|
|
309
|
+
| Variable | Description |
|
|
310
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
260
311
|
| `CHROME_BIN` | Path to the Chrome/Chromium executable. Required on systems where Chrome is not on `PATH` (e.g. many CI environments). Set automatically when using `browser-actions/setup-chrome` in GitHub Actions. |
|
|
261
|
-
| `QUNITX_BROWSER` | Browser engine to use (`chromium`, `firefox`, `webkit`). Equivalent to `--browser` on the CLI. Useful in CI matrix jobs.
|
|
312
|
+
| `QUNITX_BROWSER` | Browser engine to use (`chromium`, `firefox`, `webkit`). Equivalent to `--browser` on the CLI. Useful in CI matrix jobs. |
|
|
262
313
|
|
|
263
314
|
If you do not provide any HTML template, qunitx falls back to its built-in `test/tests.html` boilerplate internally, so `qunitx init` is optional.
|
|
264
315
|
|
|
@@ -290,6 +341,12 @@ Options:
|
|
|
290
341
|
--open=<binary> Open output in a specific browser binary (e.g. brave, google-chrome-lts)
|
|
291
342
|
--port=<n> HTTP server port (auto-selects a free port if taken)
|
|
292
343
|
--browser=<name> Browser engine: chromium (default), firefox, or webkit
|
|
344
|
+
--no-daemon Don't use the daemon for this run — skips a running daemon and prevents QUNITX_DAEMON auto-spawn
|
|
345
|
+
|
|
346
|
+
Subcommands:
|
|
347
|
+
qunitx daemon start | stop | status Manage the optional persistent daemon
|
|
348
|
+
qunitx init Bootstrap qunitx config + base HTML in this project
|
|
349
|
+
qunitx new <testFileName> Create a new qunitx test file
|
|
293
350
|
```
|
|
294
351
|
|
|
295
352
|
## Timezone
|
|
@@ -298,11 +355,11 @@ The browser inherits the **OS system timezone** automatically — no Playwright
|
|
|
298
355
|
|
|
299
356
|
### Setting a timezone for tests
|
|
300
357
|
|
|
301
|
-
| Platform
|
|
302
|
-
|
|
303
|
-
| **Linux**
|
|
304
|
-
| **macOS**
|
|
305
|
-
| **Windows** | Reads the registry timezone (ignores `TZ`)
|
|
358
|
+
| Platform | How Chrome resolves the timezone | Override |
|
|
359
|
+
| ----------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
360
|
+
| **Linux** | glibc reads `TZ` env var first, then `/etc/localtime` | `TZ=America/New_York npx qunitx …` works |
|
|
361
|
+
| **macOS** | CoreFoundation reads the system timezone (ignores `TZ`) | Must set the system timezone: `sudo systemsetup -settimezone America/New_York` |
|
|
362
|
+
| **Windows** | Reads the registry timezone (ignores `TZ`) | Must set the system timezone: `tzutil /s "Eastern Standard Time"` |
|
|
306
363
|
|
|
307
364
|
On Linux, the `TZ` env var is the simplest way to run tests in a specific timezone:
|
|
308
365
|
|
|
@@ -356,12 +413,18 @@ module('Invoice formatting', (hooks) => {
|
|
|
356
413
|
// Pin "now" to a fixed instant for the whole module
|
|
357
414
|
const FIXED = new realDate('2024-06-01T12:00:00Z');
|
|
358
415
|
globalThis.Date = class extends realDate {
|
|
359
|
-
constructor(...args) {
|
|
360
|
-
|
|
416
|
+
constructor(...args) {
|
|
417
|
+
super(args.length ? args : [FIXED]);
|
|
418
|
+
}
|
|
419
|
+
static now() {
|
|
420
|
+
return FIXED.getTime();
|
|
421
|
+
}
|
|
361
422
|
};
|
|
362
423
|
});
|
|
363
424
|
|
|
364
|
-
hooks.after(() => {
|
|
425
|
+
hooks.after(() => {
|
|
426
|
+
globalThis.Date = realDate;
|
|
427
|
+
});
|
|
365
428
|
|
|
366
429
|
test('formats the current date correctly', (assert) => {
|
|
367
430
|
assert.equal(new Date().toISOString().slice(0, 10), '2024-06-01');
|
|
@@ -377,8 +440,12 @@ import sinon from 'sinon';
|
|
|
377
440
|
module('Debounce logic', (hooks) => {
|
|
378
441
|
let clock;
|
|
379
442
|
|
|
380
|
-
hooks.before(() => {
|
|
381
|
-
|
|
443
|
+
hooks.before(() => {
|
|
444
|
+
clock = sinon.useFakeTimers({ now: new Date('2024-06-01T00:00:00Z') });
|
|
445
|
+
});
|
|
446
|
+
hooks.after(() => {
|
|
447
|
+
clock.restore();
|
|
448
|
+
});
|
|
382
449
|
|
|
383
450
|
test('fires after 300 ms', (assert) => {
|
|
384
451
|
// clock.tick(300) advances fake time without waiting in real time
|
|
@@ -393,11 +460,15 @@ If you need the mock active across the entire test run rather than inside a sing
|
|
|
393
460
|
```js
|
|
394
461
|
// scripts/mock-date.js (passed as: qunitx … --before=scripts/mock-date.js)
|
|
395
462
|
const realDate = globalThis.Date;
|
|
396
|
-
const FIXED
|
|
463
|
+
const FIXED = new realDate('2024-06-01T12:00:00Z');
|
|
397
464
|
|
|
398
465
|
globalThis.Date = class extends realDate {
|
|
399
|
-
constructor(...args) {
|
|
400
|
-
|
|
466
|
+
constructor(...args) {
|
|
467
|
+
super(args.length ? args : [FIXED]);
|
|
468
|
+
}
|
|
469
|
+
static now() {
|
|
470
|
+
return FIXED.getTime();
|
|
471
|
+
}
|
|
401
472
|
};
|
|
402
473
|
```
|
|
403
474
|
|
|
@@ -416,6 +487,8 @@ make demo # regenerate docs/demo.gif
|
|
|
416
487
|
make release LEVEL=patch # bump version, update changelog, tag, push
|
|
417
488
|
```
|
|
418
489
|
|
|
490
|
+
For a tight TDD loop on this repo (or any consuming project), run `qunitx daemon start` once at the top of your session — every subsequent `qunitx` invocation reuses the warm Chrome and esbuild context, roughly halving the wait-per-iteration. AI/LLM coding agents benefit even more, since their inner loop is dozens of `qunitx <file>` invocations per feature. Caveat: agents running inside containers or CI-style environments (GitHub Actions Copilot, sandboxed coding agents) often have `CI=1` set, which bypasses the daemon by default — set `QUNITX_DAEMON=1` in those environments to opt back in.
|
|
491
|
+
|
|
419
492
|
Use `--trace-perf` to print internal timing to stderr — useful when investigating startup or e2e regressions:
|
|
420
493
|
|
|
421
494
|
```sh
|