tape-six 1.7.2 → 1.7.4

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/TESTING.md ADDED
@@ -0,0 +1,756 @@
1
+ # Testing with tape-six
2
+
3
+ > This guide is for AI agents and developers working on projects that use `tape-six` for testing.
4
+ > It covers how to write tests, run them, and configure test discovery.
5
+
6
+ `tape-six` supports ES modules and CommonJS. TypeScript is supported natively — no transpilation needed (Node 22+, Deno, Bun all run `.ts` files directly).
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm i -D tape-six
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ Create a test file (e.g., `tests/test-example.js`):
17
+
18
+ ```js
19
+ import test from 'tape-six';
20
+
21
+ test('arithmetic', t => {
22
+ t.equal(2 + 2, 4, 'addition');
23
+ t.ok(10 > 5, 'comparison');
24
+ t.deepEqual({a: 1}, {a: 1}, 'deep equality');
25
+ });
26
+ ```
27
+
28
+ Run it directly:
29
+
30
+ ```bash
31
+ node tests/test-example.js
32
+ ```
33
+
34
+ Run all configured tests:
35
+
36
+ ```bash
37
+ npx tape6 --flags FO
38
+ ```
39
+
40
+ ## Writing tests
41
+
42
+ ### Importing
43
+
44
+ ES modules (`.js`, `.mjs`, `.ts`, `.mts`):
45
+
46
+ ```js
47
+ import test from 'tape-six';
48
+ // or named imports:
49
+ import {test, describe, it, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six';
50
+ ```
51
+
52
+ CommonJS (`.cjs`, `.cts`):
53
+
54
+ ```js
55
+ const {test} = require('tape-six');
56
+ ```
57
+
58
+ ### Registering tests
59
+
60
+ `test(name, options, testFn)` — all arguments are optional, recognized by type.
61
+
62
+ ```js
63
+ test('name', t => {
64
+ t.pass('unconditional pass');
65
+ });
66
+
67
+ test('async test', async t => {
68
+ const result = await fetchData();
69
+ t.equal(result.status, 200, 'status OK');
70
+ });
71
+ ```
72
+
73
+ Aliases: `suite()`, `describe()`, `it()` — all identical to `test()`.
74
+
75
+ When called inside a test body, top-level functions (`test`, `it`, `describe`, and all hooks) automatically delegate to the current tester. Using `t.test()`, `t.before()`, etc. is still preferred because it makes the delegation explicit. The top-level form is convenient when porting tests from frameworks like Mocha or Jest:
76
+
77
+ ```js
78
+ import {describe, it, before, beforeEach} from 'tape-six';
79
+
80
+ describe('module', () => {
81
+ before(() => {
82
+ /* setup — same as t.before() */
83
+ });
84
+ beforeEach(() => {
85
+ /* per-test setup */
86
+ });
87
+
88
+ it('works', t => {
89
+ t.ok(true);
90
+ });
91
+
92
+ it('also works', t => {
93
+ t.equal(1 + 1, 2);
94
+ });
95
+ });
96
+ ```
97
+
98
+ ### Skip and TODO
99
+
100
+ ```js
101
+ test.skip('not ready', t => {
102
+ t.fail();
103
+ }); // skipped entirely
104
+ test.todo('in progress', t => {
105
+ t.equal(1, 2);
106
+ }); // runs, failures not counted
107
+ ```
108
+
109
+ ### Assertions
110
+
111
+ All `msg` arguments are optional. If omitted, a generic message is used.
112
+
113
+ | Method | Checks | Common aliases |
114
+ | ------------------------------------ | ------------------------------- | -------------------------------- |
115
+ | `t.pass(msg)` | Unconditional pass | |
116
+ | `t.fail(msg)` | Unconditional fail | |
117
+ | `t.ok(val, msg)` | `val` is truthy | `true`, `assert` |
118
+ | `t.notOk(val, msg)` | `val` is falsy | `false`, `notok` |
119
+ | `t.error(err, msg)` | `err` is falsy | `ifError`, `ifErr` |
120
+ | `t.equal(a, b, msg)` | `a === b` | `is`, `strictEqual`, `isEqual` |
121
+ | `t.notEqual(a, b, msg)` | `a !== b` | `not`, `notStrictEqual`, `isNot` |
122
+ | `t.deepEqual(a, b, msg)` | Deep strict equality | `same`, `isEquivalent` |
123
+ | `t.notDeepEqual(a, b, msg)` | Not deeply equal | `notSame`, `notEquivalent` |
124
+ | `t.looseEqual(a, b, msg)` | `a == b` | |
125
+ | `t.notLooseEqual(a, b, msg)` | `a != b` | |
126
+ | `t.deepLooseEqual(a, b, msg)` | Deep loose equality | |
127
+ | `t.notDeepLooseEqual(a, b, msg)` | Not deeply loosely equal | |
128
+ | `t.throws(fn, msg)` | `fn()` throws | |
129
+ | `t.doesNotThrow(fn, msg)` | `fn()` does not throw | |
130
+ | `t.matchString(str, re, msg)` | `str` matches `re` | |
131
+ | `t.doesNotMatchString(str, re, msg)` | `str` doesn't match `re` | |
132
+ | `t.match(a, b, msg)` | Structural pattern match | |
133
+ | `t.doesNotMatch(a, b, msg)` | No structural match | |
134
+ | `t.rejects(promise, msg)` | Promise rejects (**await it**) | `doesNotResolve` |
135
+ | `t.resolves(promise, msg)` | Promise resolves (**await it**) | `doesNotReject` |
136
+
137
+ ### Async assertions
138
+
139
+ `rejects` and `resolves` are async — always `await` them:
140
+
141
+ ```js
142
+ test('async asserts', async t => {
143
+ await t.rejects(Promise.reject(new Error('fail')), 'should reject');
144
+ await t.resolves(Promise.resolve(42), 'should resolve');
145
+ });
146
+ ```
147
+
148
+ ### Nested (embedded) tests
149
+
150
+ Tests can be nested. The preferred way is `t.test()` because it makes the delegation explicit. Top-level `test()`/`it()` are equivalent inside a test body — they auto-delegate to the current tester:
151
+
152
+ ```js
153
+ import {test, it} from 'tape-six';
154
+
155
+ test('suite', async t => {
156
+ // these two forms are equivalent:
157
+ await t.test('using t.test', t => {
158
+ t.pass();
159
+ });
160
+
161
+ await it('using top-level it()', t => {
162
+ t.ok(true);
163
+ });
164
+ });
165
+ ```
166
+
167
+ Embedded tests must be `await`ed to preserve execution order.
168
+
169
+ ### Hooks (setup/teardown)
170
+
171
+ Hooks are scoped — they only affect tests at their registration level.
172
+
173
+ `before` is an alias for `beforeAll`, `after` is an alias for `afterAll`. These aliases work everywhere: as named exports, on `test.before`/`test.after`, on `t.before`/`t.after`, and in options objects.
174
+
175
+ **Top-level hooks** (affect all top-level tests in the file):
176
+
177
+ ```js
178
+ import {test, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six';
179
+ // or with aliases:
180
+ import {test, before, after, beforeEach, afterEach} from 'tape-six';
181
+
182
+ beforeAll(() => {
183
+ /* once before first test */
184
+ });
185
+ afterAll(() => {
186
+ /* once after last test */
187
+ });
188
+ beforeEach(() => {
189
+ /* before each test */
190
+ });
191
+ afterEach(() => {
192
+ /* after each test */
193
+ });
194
+ ```
195
+
196
+ **Nested hooks** (affect embedded tests within a suite):
197
+
198
+ The preferred way is `t.before()`/`t.after()` because it makes the scope explicit. Top-level `before()`/`after()` are equivalent inside a test body — they auto-delegate to the current tester:
199
+
200
+ ```js
201
+ import {test, before, after, beforeEach} from 'tape-six';
202
+
203
+ test('database tests', async t => {
204
+ let db;
205
+ // these use top-level functions — they auto-delegate to t:
206
+ before(async () => {
207
+ db = await connect();
208
+ });
209
+ after(async () => {
210
+ await db.close();
211
+ });
212
+ beforeEach(() => {
213
+ /* reset state */
214
+ });
215
+
216
+ // equivalent using t. methods:
217
+ // t.before(async () => { db = await connect(); });
218
+ // t.after(async () => { await db.close(); });
219
+ // t.beforeEach(() => { /* reset state */ });
220
+
221
+ await t.test('insert', async t => {
222
+ const result = await db.insert({name: 'Alice'});
223
+ t.ok(result.id, 'got an id');
224
+ });
225
+
226
+ await t.test('query', async t => {
227
+ const rows = await db.query('SELECT * FROM users');
228
+ t.ok(rows.length > 0, 'has rows');
229
+ });
230
+ });
231
+ ```
232
+
233
+ **Hooks via options** (reusable across tests):
234
+
235
+ ```js
236
+ const dbOpts = {
237
+ beforeEach: () => resetFixtures(),
238
+ afterEach: () => cleanupDb()
239
+ };
240
+
241
+ test('suite A', dbOpts, async t => {
242
+ /* ... */
243
+ });
244
+ test('suite B', dbOpts, async t => {
245
+ /* ... */
246
+ });
247
+ ```
248
+
249
+ ## Migrating from other test frameworks
250
+
251
+ `tape-six` supports `describe`/`it` and `before`/`after` aliases. When called inside a test body, all top-level functions automatically delegate to the current tester. This means migration from Mocha, Jest, or `node:test` is nearly mechanical — change the import and swap assertions.
252
+
253
+ ### Mocha / Jest → tape-six
254
+
255
+ ```js
256
+ // Mocha / Jest
257
+ describe('module', () => {
258
+ before(() => {
259
+ /* setup */
260
+ });
261
+ after(() => {
262
+ /* teardown */
263
+ });
264
+ beforeEach(() => {
265
+ /* per-test setup */
266
+ });
267
+
268
+ it('works', () => {
269
+ expect(1).toBe(1);
270
+ });
271
+ });
272
+ ```
273
+
274
+ ```js
275
+ // tape-six — just change the import and swap assertions
276
+ import {describe, it, before, after, beforeEach} from 'tape-six';
277
+
278
+ describe('module', () => {
279
+ before(() => {
280
+ /* setup */
281
+ });
282
+ after(() => {
283
+ /* teardown */
284
+ });
285
+ beforeEach(() => {
286
+ /* per-test setup */
287
+ });
288
+
289
+ it('works', t => {
290
+ t.equal(1, 1);
291
+ });
292
+ });
293
+ ```
294
+
295
+ Key differences from Mocha/Jest:
296
+
297
+ - **Assertions** use `t.equal`, `t.deepEqual`, etc. instead of `expect`.
298
+ - The test function receives a `t` argument for assertions.
299
+ - No magic globals — everything is imported explicitly.
300
+ - `it()` inside `describe()` is auto-delegated, no need to use `t.test()`.
301
+ - `before()`/`after()` inside `describe()` are auto-delegated, no need to use `t.before()`/`t.after()`.
302
+
303
+ ### Chai → tape-six
304
+
305
+ Chai is commonly used with Mocha and other test frameworks. Its `expect`/`should`/`assert` styles all throw `AssertionError`, which `tape-six` catches automatically. You can use Chai assertions directly inside `tape-six` tests, or replace them with `t.*` equivalents:
306
+
307
+ ```js
308
+ // Chai expect style
309
+ import {expect} from 'chai';
310
+
311
+ describe('module', () => {
312
+ it('works', () => {
313
+ expect(1 + 1).to.equal(2);
314
+ expect([1, 2]).to.deep.equal([1, 2]);
315
+ expect(true).to.be.ok;
316
+ expect(() => badFn()).to.throw();
317
+ });
318
+ });
319
+ ```
320
+
321
+ ```js
322
+ // tape-six — Chai assertions work as-is, or replace with t.*
323
+ import {describe, it} from 'tape-six';
324
+
325
+ describe('module', () => {
326
+ it('works', t => {
327
+ t.equal(1 + 1, 2);
328
+ t.deepEqual([1, 2], [1, 2]);
329
+ t.ok(true);
330
+ t.throws(() => badFn());
331
+ });
332
+ });
333
+ ```
334
+
335
+ You can also keep Chai alongside `tape-six` — failures are reported correctly either way:
336
+
337
+ ```js
338
+ import {describe, it} from 'tape-six';
339
+ import {expect} from 'chai';
340
+
341
+ describe('mixed assertions', () => {
342
+ it('uses both', t => {
343
+ expect(1).to.be.lessThan(2); // Chai — caught automatically
344
+ t.equal(1 + 1, 2); // tape-six native
345
+ });
346
+ });
347
+ ```
348
+
349
+ ### node:test → tape-six
350
+
351
+ ```js
352
+ // node:test
353
+ import {describe, it, before, after} from 'node:test';
354
+ import assert from 'node:assert/strict';
355
+
356
+ describe('module', () => {
357
+ before(() => {
358
+ /* setup */
359
+ });
360
+ it('works', () => {
361
+ assert.equal(1, 1);
362
+ });
363
+ });
364
+ ```
365
+
366
+ ```js
367
+ // tape-six — change import, optionally swap assert for t.*
368
+ import {describe, it, before} from 'tape-six';
369
+
370
+ describe('module', () => {
371
+ before(() => {
372
+ /* setup */
373
+ });
374
+ it('works', t => {
375
+ t.equal(1, 1);
376
+ });
377
+ });
378
+ ```
379
+
380
+ Note: you can also keep using `node:assert` inside tape-six tests — `AssertionError` is caught automatically (see [3rd-party assertion libraries](#3rd-party-assertion-libraries)).
381
+
382
+ ### Quick reference
383
+
384
+ | Mocha / Jest / node:test | tape-six equivalent |
385
+ | ----------------------------------- | ------------------------------------------------- |
386
+ | `describe(name, fn)` | `describe(name, fn)` — same |
387
+ | `it(name, fn)` | `it(name, t => { ... })` — same, but receives `t` |
388
+ | `before(fn)` | `before(fn)` — same (alias for `beforeAll`) |
389
+ | `after(fn)` | `after(fn)` — same (alias for `afterAll`) |
390
+ | `beforeEach(fn)` | `beforeEach(fn)` — same |
391
+ | `afterEach(fn)` | `afterEach(fn)` — same |
392
+ | `expect(a).toBe(b)` | `t.equal(a, b)` |
393
+ | `expect(a).toEqual(b)` | `t.deepEqual(a, b)` |
394
+ | `expect(a).toBeTruthy()` | `t.ok(a)` |
395
+ | `expect(fn).toThrow()` | `t.throws(fn)` |
396
+ | `assert.equal(a, b)` | `t.equal(a, b)` or keep `assert.equal` |
397
+ | `assert.deepEqual(a, b)` | `t.deepEqual(a, b)` or keep `assert.deepEqual` |
398
+ | `expect(a).to.equal(b)` (Chai) | `t.equal(a, b)` or keep `expect` |
399
+ | `expect(a).to.deep.equal(b)` (Chai) | `t.deepEqual(a, b)` or keep `expect` |
400
+ | `expect(a).to.be.ok` (Chai) | `t.ok(a)` or keep `expect` |
401
+ | `expect(fn).to.throw()` (Chai) | `t.throws(fn)` or keep `expect` |
402
+
403
+ ### Wildcard matching with `t.any`
404
+
405
+ Use `t.any` (or `t._`) in deep equality checks to match any value:
406
+
407
+ ```js
408
+ test('partial match', t => {
409
+ const result = {id: 123, name: 'Alice', createdAt: new Date()};
410
+ t.deepEqual(result, {id: 123, name: 'Alice', createdAt: t.any});
411
+ });
412
+ ```
413
+
414
+ ### Testing exceptions
415
+
416
+ ```js
417
+ test('errors', async t => {
418
+ t.throws(() => {
419
+ throw new Error('boom');
420
+ }, 'should throw');
421
+ t.doesNotThrow(() => 42, 'should not throw');
422
+ await t.rejects(Promise.reject(new Error('fail')), 'should reject');
423
+ await t.resolves(Promise.resolve(42), 'should resolve');
424
+ });
425
+ ```
426
+
427
+ ### 3rd-party assertion libraries
428
+
429
+ `tape-six` catches `AssertionError` automatically. You can use `chai` or `node:assert`:
430
+
431
+ ```js
432
+ import test from 'tape-six';
433
+ import {expect} from 'chai';
434
+
435
+ test('with chai', t => {
436
+ expect(1).to.be.lessThan(2);
437
+ expect([1, 2]).to.deep.equal([1, 2]);
438
+ });
439
+ ```
440
+
441
+ ```js
442
+ import test from 'tape-six';
443
+ import assert from 'node:assert/strict';
444
+
445
+ test('with node:assert', t => {
446
+ assert.equal(1 + 1, 2);
447
+ assert.deepEqual({a: 1}, {a: 1});
448
+ });
449
+ ```
450
+
451
+ ## Running tests
452
+
453
+ ### Single file
454
+
455
+ ```bash
456
+ node tests/test-example.js # Node.js
457
+ bun run tests/test-example.js # Bun
458
+ deno run -A tests/test-example.js # Deno
459
+ ```
460
+
461
+ ### All configured tests
462
+
463
+ ```bash
464
+ npx tape6 --flags FO # parallel (worker threads)
465
+ npx tape6-seq --flags FO # sequential (in-process, no workers)
466
+ npx tape6 --par 4 --flags FO # limit to 4 workers
467
+ ```
468
+
469
+ **`tape6` vs `tape6-seq`**: The default `tape6` runner spawns worker threads to run test files in parallel — faster, but each file runs in its own isolated context. `tape6-seq` runs all test files sequentially in a single process — slower, but useful for debugging, for tests that share state, or when worker threads are unavailable.
470
+
471
+ ### Selected test files
472
+
473
+ ```bash
474
+ npx tape6 --flags FO tests/test-foo.js tests/test-bar.js
475
+ npx tape6-seq --flags FO tests/test-foo.js tests/test-bar.js
476
+ ```
477
+
478
+ ### Typical package.json scripts
479
+
480
+ ```json
481
+ {
482
+ "scripts": {
483
+ "test": "tape6 --flags FO",
484
+ "test:bun": "tape6-bun --flags FO",
485
+ "test:deno": "tape6-deno --flags FO",
486
+ "test:seq": "tape6-seq --flags FO",
487
+ "test:seq:bun": "bun run `tape6-seq --self` --flags FO",
488
+ "test:seq:deno": "deno run -A `tape6-seq --self` --flags FO"
489
+ }
490
+ }
491
+ ```
492
+
493
+ ### Flags
494
+
495
+ Flags control test output. Uppercase = enabled, lowercase = disabled.
496
+
497
+ | Flag | Meaning |
498
+ | ---- | -------------------------------------- |
499
+ | `F` | **F**ailures only — hide passing tests |
500
+ | `O` | Fail **o**nce — stop at first failure |
501
+ | `T` | Show **t**ime for each test |
502
+ | `D` | Show **d**ata of failed tests |
503
+ | `B` | Show **b**anner with summary |
504
+ | `N` | Show assert **n**umber |
505
+ | `M` | **M**onochrome — no colors |
506
+ | `C` | Don't **c**apture console output |
507
+ | `H` | **H**ide streams and console output |
508
+
509
+ Common combinations: `FO` (failures only + stop at first), `FOT` (+ show time).
510
+
511
+ ### Environment variables
512
+
513
+ - `TAPE6_FLAGS` — flags string (alternative to `--flags`).
514
+ - `TAPE6_PAR` — number of parallel workers.
515
+ - `TAPE6_TAP` — force TAP output format.
516
+ - `TAPE6_JSONL` — force JSONL output format.
517
+
518
+ ## Configuring test discovery
519
+
520
+ Add to `package.json`:
521
+
522
+ ```json
523
+ {
524
+ "tape6": {
525
+ "tests": ["/tests/test-*.*js"],
526
+ "importmap": {
527
+ "imports": {
528
+ "tape-six": "/node_modules/tape-six/index.js",
529
+ "tape-six/": "/node_modules/tape-six/src/",
530
+ "my-package": "/src/index.js",
531
+ "my-package/": "/src/"
532
+ }
533
+ }
534
+ }
535
+ }
536
+ ```
537
+
538
+ - `tests` — glob patterns for test files (relative to project root with leading `/`). Common for all environments.
539
+ - `cli` — additional patterns for CLI-only environments (Node, Bun, Deno). Typically used for `.cjs` files.
540
+ - `node`, `deno`, `bun`, `browser` — additional patterns specific to a given environment. These are **not overrides** — they are added to `tests` (and `cli` for non-browser).
541
+ - `importmap` — import map for browser testing (standard [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) format).
542
+
543
+ Example with environment-specific tests:
544
+
545
+ ```json
546
+ {
547
+ "tape6": {
548
+ "node": ["/tests/node/test-*.js"],
549
+ "deno": ["/tests/deno/test-*.js"],
550
+ "browser": ["/tests/web/test-*.html"],
551
+ "tests": ["/tests/test-*.*js"],
552
+ "importmap": {
553
+ "imports": {
554
+ "tape-six": "/node_modules/tape-six/index.js",
555
+ "tape-six/": "/node_modules/tape-six/src/"
556
+ }
557
+ }
558
+ }
559
+ }
560
+ ```
561
+
562
+ In this example, running `tape6` on Node will execute tests matching `/tests/node/test-*.js` + `/tests/test-*.*js`. Running in a browser will execute `/tests/web/test-*.html` + `/tests/test-*.*js`.
563
+
564
+ ## Browser testing
565
+
566
+ Browser tests use `tape6-server`, a static file server bundled with `tape-six` that provides a web UI for running tests.
567
+
568
+ ### Setup
569
+
570
+ 1. Configure `importmap` in `package.json` so the browser can resolve bare imports:
571
+
572
+ ```json
573
+ {
574
+ "tape6": {
575
+ "tests": ["/tests/test-*.*js"],
576
+ "browser": ["/tests/web/test-*.html"],
577
+ "importmap": {
578
+ "imports": {
579
+ "tape-six": "/node_modules/tape-six/index.js",
580
+ "tape-six/": "/node_modules/tape-six/src/",
581
+ "my-package": "/src/index.js",
582
+ "my-package/": "/src/"
583
+ }
584
+ }
585
+ }
586
+ }
587
+ ```
588
+
589
+ 2. Start the server:
590
+
591
+ ```bash
592
+ npx tape6-server --trace
593
+ ```
594
+
595
+ 3. Open `http://localhost:3000` in a browser to see the web UI and run all configured tests.
596
+
597
+ ### Running all configured browser tests
598
+
599
+ Navigate to:
600
+
601
+ ```
602
+ http://localhost:3000/
603
+ ```
604
+
605
+ The web app fetches the configured test list from the server and runs them.
606
+
607
+ ### Running specific test files by name
608
+
609
+ Use the `?q=` query parameter (supports multiple values):
610
+
611
+ ```
612
+ http://localhost:3000/?q=/tests/test-foo.js&q=/tests/test-bar.js
613
+ ```
614
+
615
+ These are glob patterns resolved by the server, so wildcards work:
616
+
617
+ ```
618
+ http://localhost:3000/?q=/tests/test-sample.*js
619
+ ```
620
+
621
+ ### Running a single test file (HTML shim)
622
+
623
+ Create an HTML file that loads test scripts directly with an inline import map:
624
+
625
+ ```html
626
+ <!DOCTYPE html>
627
+ <html lang="en">
628
+ <head>
629
+ <meta charset="utf-8" />
630
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
631
+ <title>My tests</title>
632
+ <script type="importmap">
633
+ {
634
+ "imports": {
635
+ "tape-six": "/node_modules/tape-six/index.js",
636
+ "tape-six/": "/node_modules/tape-six/src/"
637
+ }
638
+ }
639
+ </script>
640
+ <script type="module" src="../test-sample.js"></script>
641
+ </head>
642
+ <body>
643
+ <h1>My tests</h1>
644
+ <p>See the console.</p>
645
+ </body>
646
+ </html>
647
+ ```
648
+
649
+ Navigate to the HTML file directly (e.g., `http://localhost:3000/tests/web/test-simple.html`). Results appear in the browser console. This approach does not use the web UI.
650
+
651
+ ### Flags and parallel execution in the browser
652
+
653
+ Append query parameters:
654
+
655
+ ```
656
+ http://localhost:3000/?flags=FO&par=3
657
+ ```
658
+
659
+ ### Browser automation
660
+
661
+ Use Puppeteer or Playwright to run browser tests from the command line:
662
+
663
+ ```js
664
+ import puppeteer from 'puppeteer';
665
+ const browser = await puppeteer.launch({headless: true});
666
+ const page = await browser.newPage();
667
+ page.on('console', msg => console.log(msg.text()));
668
+ await page.exposeFunction('__tape6_reportResults', async text => {
669
+ await browser.close();
670
+ process.exit(text === 'success' ? 0 : 1);
671
+ });
672
+ await page.goto('http://localhost:3000/?flags=M');
673
+ ```
674
+
675
+ ### Browser limitations
676
+
677
+ - Browsers cannot run TypeScript files directly — only `.js` and `.mjs`.
678
+ - Browsers cannot run CommonJS (`.cjs`) files.
679
+ - Browsers can run HTML shim files in addition to JS files.
680
+
681
+ ## Test file conventions
682
+
683
+ - **Naming**: `test-*.js`, `test-*.mjs`, `test-*.cjs`, `test-*.ts`, `test-*.mts`, `test-*.cts`.
684
+ - **Location**: typically `tests/` directory.
685
+ - **Self-contained**: each test file should be directly executable with `node`.
686
+ - **One concern per file**: group related tests in a single file, use embedded tests for sub-grouping.
687
+
688
+ ## Patterns for AI agents writing tests
689
+
690
+ ### Testing a new function
691
+
692
+ ```js
693
+ import test from 'tape-six';
694
+ import {myFunction} from 'my-package/my-module.js';
695
+
696
+ test('myFunction', async t => {
697
+ await t.test('returns correct result for basic input', t => {
698
+ t.deepEqual(myFunction(1, 2), {sum: 3, product: 2});
699
+ });
700
+
701
+ await t.test('handles edge cases', t => {
702
+ t.deepEqual(myFunction(0, 0), {sum: 0, product: 0});
703
+ t.throws(() => myFunction(null), 'throws on null input');
704
+ });
705
+
706
+ await t.test('async variant', async t => {
707
+ const result = await myFunction.async(1, 2);
708
+ t.equal(result.sum, 3);
709
+ });
710
+ });
711
+ ```
712
+
713
+ ### Testing a class
714
+
715
+ ```js
716
+ import test from 'tape-six';
717
+ import {MyClass} from 'my-package/my-class.js';
718
+
719
+ test('MyClass', async t => {
720
+ let instance;
721
+ t.beforeEach(() => {
722
+ instance = new MyClass();
723
+ });
724
+
725
+ await t.test('constructor', t => {
726
+ t.ok(instance, 'creates instance');
727
+ t.equal(instance.size, 0, 'starts empty');
728
+ });
729
+
730
+ await t.test('add', t => {
731
+ instance.add('item');
732
+ t.equal(instance.size, 1, 'size increases');
733
+ });
734
+
735
+ await t.test('remove', t => {
736
+ instance.add('item');
737
+ instance.remove('item');
738
+ t.equal(instance.size, 0, 'size decreases');
739
+ });
740
+ });
741
+ ```
742
+
743
+ ### Verifying after writing tests
744
+
745
+ ```bash
746
+ node tests/test-<name>.js # run your new test file directly
747
+ npm test # run full suite to check for regressions
748
+ ```
749
+
750
+ ## Links
751
+
752
+ - Full API reference: https://github.com/uhop/tape-six/wiki/Tester
753
+ - Hooks documentation: https://github.com/uhop/tape-six/wiki/Before-and-after-hooks
754
+ - Configuration: https://github.com/uhop/tape-six/wiki/Set-up-tests
755
+ - Supported flags: https://github.com/uhop/tape-six/wiki/Supported-flags
756
+ - npm: https://www.npmjs.com/package/tape-six