@jterrazz/test 9.1.0 → 9.2.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 +77 -24
- package/dist/index.d.ts +314 -3
- package/dist/index.js +429 -8
- package/dist/oxlint.cjs +73 -7
- package/dist/oxlint.d.cts +5 -0
- package/dist/oxlint.d.ts +5 -0
- package/dist/oxlint.js +73 -7
- package/dist/playwright.adapter.js +135 -0
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jterrazz/test
|
|
2
2
|
|
|
3
|
-
Declarative testing framework for APIs, jobs, and
|
|
3
|
+
Declarative testing framework for APIs, jobs, CLIs, and websites. Four constructors — `specification.api()`, `specification.jobs()`, `specification.cli()`, `specification.website()` — and specs that read as sentences: given → action → assertions. The vitest test name is the spec's description; all assertions go through `expect()` with auto-registered, subject-typed matchers.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -D @jterrazz/test vitest
|
|
@@ -76,9 +76,38 @@ test('builds the project', async () => {
|
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
### Website testing (browser)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
```typescript
|
|
82
|
+
// specs/website/website.specification.ts
|
|
83
|
+
import { specification } from '@jterrazz/test';
|
|
84
|
+
import { afterAll } from 'vitest';
|
|
85
|
+
|
|
86
|
+
export const { cleanup, website } = await specification.website({
|
|
87
|
+
server: { command: 'node specs/fixtures/website-app/server.mjs', ready: '/' },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
afterAll(cleanup);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// specs/website/visit/head.test.ts
|
|
95
|
+
import { expect, test } from 'vitest';
|
|
96
|
+
import { website } from '../website.specification.js';
|
|
97
|
+
|
|
98
|
+
test('captures the full head surface of a rendered page', async () => {
|
|
99
|
+
// Given - the fixture homepage
|
|
100
|
+
const result = await website.visit('/');
|
|
101
|
+
|
|
102
|
+
// Then - one golden covers title, canonical, alternates, and metas
|
|
103
|
+
expect(result.status).toBe(200);
|
|
104
|
+
expect(result.head).toMatch('home.head.json');
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Actions are **terminal**: `.request()`, `.get()`, `.trigger()`, `.exec()`, `.fetch()`, `.visit()` execute the spec and resolve to a precisely typed result. There is no `.run()`, no label, and no `.spawn()`.
|
|
109
|
+
|
|
110
|
+
## The four constructors
|
|
82
111
|
|
|
83
112
|
One constructor per tested interface, each returning a record destructured with its canonical name:
|
|
84
113
|
|
|
@@ -87,6 +116,7 @@ One constructor per tested interface, each returning a record destructured with
|
|
|
87
116
|
| `specification.api(options)` | `{ api, cleanup, docker, orchestrator }` | `.request(file)`, `.get()`, `.post()`, `.put()`, `.delete()` |
|
|
88
117
|
| `specification.jobs(options)` | `{ jobs, cleanup, orchestrator }` | `.trigger(name)` |
|
|
89
118
|
| `specification.cli(bin, options)` | `{ cli, cleanup, docker, orchestrator }` | `.exec(args, { waitFor?, timeout? }?)` |
|
|
119
|
+
| `specification.website(options)` | `{ website, cleanup, url }` | `.fetch(path)`, `.visit(path, scenario?)` |
|
|
90
120
|
|
|
91
121
|
### `specification.api({ services, server, mode?, root? })`
|
|
92
122
|
|
|
@@ -146,35 +176,57 @@ export const { cli, cleanup } = await specification.cli('my-migrate-tool', {
|
|
|
146
176
|
const result = await cli.seed('legacy-schema.sql').exec('up');
|
|
147
177
|
```
|
|
148
178
|
|
|
179
|
+
### `specification.website({ server?, url?, external?, root? })`
|
|
180
|
+
|
|
181
|
+
Tests a rendered website: `.fetch(path)` for a raw HTTP exchange (redirects never followed), `.visit(path, scenario?)` for a page rendered in a real chromium. Exactly one of `server` (start the site locally — a free port injected as `PORT`, polled on `ready`) or `url` (target a running site) is required.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
export const { website, cleanup } = await specification.website({
|
|
185
|
+
server: { command: 'node specs/fixtures/website-app/server.mjs', ready: '/' },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Raw exchange — status + headers, redirects surface as 3xx
|
|
189
|
+
const redirect = await website.fetch('/old');
|
|
190
|
+
|
|
191
|
+
// Rendered page, optionally driven by a scenario (the When)
|
|
192
|
+
const page = await website.visit('/', async (visitor) => {
|
|
193
|
+
await visitor.click(link('Articles'));
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The handle destructures to `{ website, cleanup, url }` — no `docker`, no `orchestrator`. `.visit()` needs playwright (`npm install -D playwright && npx playwright install chromium`) — an optional peer dependency, only loaded when a spec actually renders a page. Full reference: [docs/11-website.md](docs/11-website.md).
|
|
198
|
+
|
|
149
199
|
### Root auto-discovery
|
|
150
200
|
|
|
151
|
-
When `root` is absent, the framework walks up from the specification file to the first directory containing `docker/compose.test.yaml`, else the first containing `package.json`. Pass `root` only when the convention does not fit. `root` is strictly the **project root** (compose detection + local-bin resolution) — it is not a fixtures root; `.fixture()` resolves its own paths.
|
|
201
|
+
When `root` is absent, the framework walks up from the specification file to the first directory containing `docker/compose.test.yaml`, else the first containing `package.json`. Pass `root` only when the convention does not fit. `root` is strictly the **project root** (compose detection + local-bin resolution, or the cwd of a `specification.website()` server command) — it is not a fixtures root; `.fixture()` resolves its own paths.
|
|
152
202
|
|
|
153
203
|
## Builder API
|
|
154
204
|
|
|
155
205
|
### Setup (chainable)
|
|
156
206
|
|
|
157
|
-
| Method | Facets
|
|
158
|
-
| --------------------------------------- |
|
|
159
|
-
| `.seed("file.sql", { database? })` | all
|
|
160
|
-
| `.fixture("file")` | cli
|
|
161
|
-
| `.fixture("$FIXTURES/name/")` | cli
|
|
162
|
-
| `.env({ KEY: "value" })` | cli
|
|
163
|
-
| `.headers({ "Accept-Language": "fr" })` | api
|
|
164
|
-
| `.intercept(contract)` | api, jobs
|
|
165
|
-
| `.intercept(trigger, response)` | api, jobs
|
|
207
|
+
| Method | Facets | Description |
|
|
208
|
+
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------ |
|
|
209
|
+
| `.seed("file.sql", { database? })` | all | Load SQL from `seeds/` — `database` is the record key (mandatory with ≥ 2 databases, forbidden with 1) |
|
|
210
|
+
| `.fixture("file")` | cli | Copy the feature-local `fixtures/file` into the working directory |
|
|
211
|
+
| `.fixture("$FIXTURES/name/")` | cli | Spread the shared `specs/fixtures/name/` project into the cwd (trailing `/` = contents; layers) |
|
|
212
|
+
| `.env({ KEY: "value" })` | cli | Set env vars on the child (`null` unsets, `$WORKDIR` expands, calls merge) |
|
|
213
|
+
| `.headers({ "Accept-Language": "fr" })` | api, website | Set HTTP request headers (merge on top of `.http` file headers, or on the browser context) |
|
|
214
|
+
| `.intercept(contract)` | api, jobs | Intercept an outgoing HTTP call with a declared contract |
|
|
215
|
+
| `.intercept(trigger, response)` | api, jobs | Inline intercept for one-off cases |
|
|
166
216
|
|
|
167
217
|
### Actions (terminal)
|
|
168
218
|
|
|
169
|
-
| Method | Facet
|
|
170
|
-
| ------------------------------------------ |
|
|
171
|
-
| `.request("create-user.http")` | api
|
|
172
|
-
| `.get(path)` / `.delete(path)` | api
|
|
173
|
-
| `.post(path, body?)` / `.put(path, body?)` | api
|
|
174
|
-
| `.trigger("name")` | jobs
|
|
175
|
-
| `.exec("args")` | cli
|
|
176
|
-
| `.exec(["build", "start"])` | cli
|
|
177
|
-
| `.exec("dev", { waitFor, timeout? })` | cli
|
|
219
|
+
| Method | Facet | Resolves to | Description |
|
|
220
|
+
| ------------------------------------------ | ------- | ------------- | ------------------------------------------------------------------------------------- |
|
|
221
|
+
| `.request("create-user.http")` | api | `HttpResult` | Send the COMPLETE request from `requests/<file>` (method, path, headers, raw body) |
|
|
222
|
+
| `.get(path)` / `.delete(path)` | api | `HttpResult` | Inline requests for simple cases |
|
|
223
|
+
| `.post(path, body?)` / `.put(path, body?)` | api | `HttpResult` | Inline body: plain object, JSON-serialized |
|
|
224
|
+
| `.trigger("name")` | jobs | `BaseResult` | Execute a registered job |
|
|
225
|
+
| `.exec("args")` | cli | `CliResult` | Run the command |
|
|
226
|
+
| `.exec(["build", "start"])` | cli | `CliResult` | Sequence in the same cwd; stops on first non-zero exit |
|
|
227
|
+
| `.exec("dev", { waitFor, timeout? })` | cli | `CliResult` | Long-running: resolves at the pattern, killed at `timeout` (default 10 s) |
|
|
228
|
+
| `.fetch(path)` | website | `FetchResult` | One raw HTTP exchange — redirects surface as 3xx, never followed |
|
|
229
|
+
| `.visit(path, scenario?)` | website | `PageResult` | Render the page in a shared chromium; with a scenario, the capture is the final state |
|
|
178
230
|
|
|
179
231
|
One chain = one terminal action; databases reset at the start of every chain. Every cli spec runs in a fresh, empty temp directory.
|
|
180
232
|
|
|
@@ -322,13 +374,14 @@ These conventions are not just prose: the package ships an oxlint plugin (`@jter
|
|
|
322
374
|
|
|
323
375
|
## Requirements
|
|
324
376
|
|
|
325
|
-
- **Docker** - testcontainers for node mode, docker compose for compose mode; not needed for `sqlite()
|
|
377
|
+
- **Docker** - testcontainers for node mode, docker compose for compose mode; not needed for `sqlite()`, plain cli specs, or website specs
|
|
326
378
|
- **vitest** - peer dependency
|
|
379
|
+
- **playwright** - optional peer dependency, only needed for `.visit()`: `npm install -D playwright && npx playwright install chromium`
|
|
327
380
|
- **msw** - bundled as a direct dependency (powers `.intercept()`); no separate install
|
|
328
381
|
- **hono** (or any web framework) - supplied by your project for in-process apps; the adapter only needs an object with a `request()` method, so it is not a peer
|
|
329
382
|
|
|
330
383
|
## Docs
|
|
331
384
|
|
|
332
|
-
- Guide (chapters): [docs/README.md](docs/README.md) — getting started, API/jobs/CLI specs, assertions, tokens, contracts, services, conventions, linting
|
|
385
|
+
- Guide (chapters): [docs/README.md](docs/README.md) — getting started, API/jobs/CLI/website specs, assertions, tokens, contracts, services, conventions, linting
|
|
333
386
|
- API reference: committed under [docs/reference/](docs/reference/) — compiled from source by `npm run docs`
|
|
334
387
|
- Agent skill: [skills/jterrazz-test/](skills/jterrazz-test/) — mental model, per-facet references, generated rule reference
|
package/dist/index.d.ts
CHANGED
|
@@ -496,6 +496,116 @@ interface InterceptContract {
|
|
|
496
496
|
*/
|
|
497
497
|
declare function defineContract(contract: InterceptContract): InterceptContract;
|
|
498
498
|
//#endregion
|
|
499
|
+
//#region src/core/ports/browser.port.d.ts
|
|
500
|
+
/**
|
|
501
|
+
* A user-facing element descriptor — pure data, built by the element
|
|
502
|
+
* vocabulary (`button()`, `link()`, `field()`, …) and translated into
|
|
503
|
+
* concrete locators by the browser integration. CSS/XPath selectors are
|
|
504
|
+
* deliberately not expressible: user-facing elements are the only surface.
|
|
505
|
+
*/
|
|
506
|
+
interface ElementRef {
|
|
507
|
+
kind: 'button' | 'field' | 'heading' | 'link' | 'testId' | 'text';
|
|
508
|
+
name: string;
|
|
509
|
+
}
|
|
510
|
+
/** A `<link>` element captured from the rendered document's head. */
|
|
511
|
+
interface BrowserLinkElement {
|
|
512
|
+
href: string;
|
|
513
|
+
hreflang?: string;
|
|
514
|
+
rel: string;
|
|
515
|
+
type?: string;
|
|
516
|
+
}
|
|
517
|
+
/** A `<meta>` element captured from the rendered document's head. */
|
|
518
|
+
interface BrowserMetaElement {
|
|
519
|
+
content: string;
|
|
520
|
+
name?: string;
|
|
521
|
+
property?: string;
|
|
522
|
+
}
|
|
523
|
+
/** A console message emitted while the page loaded or the scenario ran. */
|
|
524
|
+
interface BrowserConsoleMessage {
|
|
525
|
+
text: string;
|
|
526
|
+
type: string;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* The visitor — the interaction vocabulary handed to a visit scenario.
|
|
530
|
+
* Every action auto-waits (playwright actionability); `see()` is the single
|
|
531
|
+
* synchronization primitive: it retries until the element is visible and
|
|
532
|
+
* fails at the timeout. There is no sleep and no conditional helper.
|
|
533
|
+
*/
|
|
534
|
+
interface Visitor {
|
|
535
|
+
/** Check a checkbox or radio. */
|
|
536
|
+
check: (element: ElementRef) => Promise<void>;
|
|
537
|
+
/** Click the element. */
|
|
538
|
+
click: (element: ElementRef) => Promise<void>;
|
|
539
|
+
/** Fill a form field with a value. */
|
|
540
|
+
fill: (element: ElementRef, value: string) => Promise<void>;
|
|
541
|
+
/** Navigate to a path of the site under test. */
|
|
542
|
+
goto: (path: string) => Promise<void>;
|
|
543
|
+
/** Hover the element. */
|
|
544
|
+
hover: (element: ElementRef) => Promise<void>;
|
|
545
|
+
/** Press a key (e.g. `Enter`). */
|
|
546
|
+
press: (key: string) => Promise<void>;
|
|
547
|
+
/** Wait until the element is visible — the only synchronization primitive. */
|
|
548
|
+
see: (element: ElementRef) => Promise<void>;
|
|
549
|
+
/** Select an option in a select field. */
|
|
550
|
+
select: (element: ElementRef, option: string) => Promise<void>;
|
|
551
|
+
}
|
|
552
|
+
/** The behavior of a visit — the When of the spec; assertions stay in the Then. */
|
|
553
|
+
type VisitScenario = (visitor: Visitor) => Promise<void>;
|
|
554
|
+
/** Per-visit options forwarded to the browser context. */
|
|
555
|
+
interface BrowserOpenOptions {
|
|
556
|
+
/**
|
|
557
|
+
* Base URL of the site under test — the origin `goto()` resolves against
|
|
558
|
+
* and the boundary of the `external` policy.
|
|
559
|
+
*/
|
|
560
|
+
baseUrl: string;
|
|
561
|
+
/**
|
|
562
|
+
* Cross-origin request policy. `'block'` aborts every request leaving
|
|
563
|
+
* the site under test (analytics, CDNs) — the browser-side analog of
|
|
564
|
+
* strict intercepts. `'allow'` lets them through (deployed-site mode).
|
|
565
|
+
*/
|
|
566
|
+
external: 'allow' | 'block';
|
|
567
|
+
/** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
|
|
568
|
+
headers?: Record<string, string>;
|
|
569
|
+
/** The interaction scenario to run after load; the capture reflects the final state. */
|
|
570
|
+
scenario?: VisitScenario;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* The rendered page captured by a browser visit — the FINAL state when a
|
|
574
|
+
* scenario ran. Extraction happens in-page (the browser IS the HTML
|
|
575
|
+
* parser); interpretation of the raw elements belongs to core.
|
|
576
|
+
*/
|
|
577
|
+
interface BrowserPage {
|
|
578
|
+
/** Console messages emitted while loading and interacting, in order. */
|
|
579
|
+
consoleMessages: BrowserConsoleMessage[];
|
|
580
|
+
/** Serialized DOM after rendering (`document.documentElement.outerHTML`). */
|
|
581
|
+
html: string;
|
|
582
|
+
/** Raw text content of every `application/ld+json` script, in DOM order. */
|
|
583
|
+
jsonLdBlocks: string[];
|
|
584
|
+
/** `<link>` elements of the head, in DOM order. */
|
|
585
|
+
links: BrowserLinkElement[];
|
|
586
|
+
/** `<meta>` elements of the head, in DOM order. */
|
|
587
|
+
metas: BrowserMetaElement[];
|
|
588
|
+
/** HTTP status of the main document response (0 when unavailable). */
|
|
589
|
+
status: number;
|
|
590
|
+
/** Rendered `document.body.innerText`. */
|
|
591
|
+
text: string;
|
|
592
|
+
/** `document.title` after rendering. */
|
|
593
|
+
title: string;
|
|
594
|
+
/** Final URL after redirects and scenario navigation. */
|
|
595
|
+
url: string;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Abstract browser interface for the website specification runner.
|
|
599
|
+
* One implementation lives in `integrations/playwright/` — a single shared
|
|
600
|
+
* browser instance per runner; each `open()` gets a fresh, isolated context.
|
|
601
|
+
*/
|
|
602
|
+
interface BrowserPort {
|
|
603
|
+
/** Close the shared browser instance (idempotent). */
|
|
604
|
+
close: () => Promise<void>;
|
|
605
|
+
/** Load `url` in a fresh context, run the scenario, capture the final page. */
|
|
606
|
+
open: (url: string, options: BrowserOpenOptions) => Promise<BrowserPage>;
|
|
607
|
+
}
|
|
608
|
+
//#endregion
|
|
499
609
|
//#region src/core/ports/isolation.port.d.ts
|
|
500
610
|
/**
|
|
501
611
|
* Strategy for isolating service state across parallel test workers.
|
|
@@ -623,6 +733,83 @@ declare class HttpResult extends BaseResult {
|
|
|
623
733
|
get status(): number;
|
|
624
734
|
}
|
|
625
735
|
//#endregion
|
|
736
|
+
//#region src/core/specification/website/result.d.ts
|
|
737
|
+
/** A raw HTTP exchange captured by `.fetch()` — redirects are NOT followed. */
|
|
738
|
+
interface FetchExchange {
|
|
739
|
+
body: string;
|
|
740
|
+
headers: Record<string, string>;
|
|
741
|
+
status: number;
|
|
742
|
+
}
|
|
743
|
+
/** Result from a raw `.fetch()` action (robots.txt, sitemaps, redirects). */
|
|
744
|
+
declare class FetchResult extends BaseResult {
|
|
745
|
+
private readonly exchange;
|
|
746
|
+
constructor(options: {
|
|
747
|
+
config: SpecificationConfig;
|
|
748
|
+
exchange: FetchExchange;
|
|
749
|
+
testDir: string;
|
|
750
|
+
});
|
|
751
|
+
/** The response body as a text accessor (`toMatch('robots.txt')`, `.grep()`). */
|
|
752
|
+
get body(): TextAccessor;
|
|
753
|
+
/** Response headers as a flat, lower-cased key-value map. */
|
|
754
|
+
get headers(): Record<string, string>;
|
|
755
|
+
/** The response body parsed as JSON. */
|
|
756
|
+
get json(): JsonAccessor;
|
|
757
|
+
/** The `location` header of a redirect response, or undefined. */
|
|
758
|
+
get location(): string | undefined;
|
|
759
|
+
/** The HTTP response status code — redirects surface as 3xx, never followed. */
|
|
760
|
+
get status(): number;
|
|
761
|
+
}
|
|
762
|
+
/** Result from a rendered `.visit()` action — the page as a browser saw it. */
|
|
763
|
+
declare class PageResult extends BaseResult {
|
|
764
|
+
private readonly page;
|
|
765
|
+
constructor(options: {
|
|
766
|
+
config: SpecificationConfig;
|
|
767
|
+
page: BrowserPage;
|
|
768
|
+
testDir: string;
|
|
769
|
+
});
|
|
770
|
+
/** Hreflang alternates declared in the head, keyed by language code. */
|
|
771
|
+
get alternates(): Record<string, string>;
|
|
772
|
+
/** The canonical URL declared in the head, or null. */
|
|
773
|
+
get canonical(): null | string;
|
|
774
|
+
/**
|
|
775
|
+
* The browser console as a stream — one message per line, prefixed by
|
|
776
|
+
* its type — same shape as `stdout` on a cli result. Assert emptiness
|
|
777
|
+
* with `toBeEmpty()`, or snapshot with `toMatch('home.console.txt')`.
|
|
778
|
+
*/
|
|
779
|
+
get console(): TextAccessor;
|
|
780
|
+
/**
|
|
781
|
+
* Console messages of type `error` only — the stream you usually want
|
|
782
|
+
* empty: `expect(result.errors).toBeEmpty()`. Same shape as `stderr` on
|
|
783
|
+
* a cli result.
|
|
784
|
+
*/
|
|
785
|
+
get errors(): TextAccessor;
|
|
786
|
+
/**
|
|
787
|
+
* The document-head projection as a JSON accessor — title, canonical,
|
|
788
|
+
* hreflang alternates, and named metas — for one golden per page:
|
|
789
|
+
* `expect(result.head).toMatch('home.head.json')`.
|
|
790
|
+
*/
|
|
791
|
+
get head(): JsonAccessor;
|
|
792
|
+
/** The rendered DOM serialization as a text accessor. */
|
|
793
|
+
get html(): TextAccessor;
|
|
794
|
+
/**
|
|
795
|
+
* Every `application/ld+json` block of the page, parsed, as one JSON
|
|
796
|
+
* array — `expect(result.jsonLd).toMatch('article.jsonld.json')`.
|
|
797
|
+
*/
|
|
798
|
+
get jsonLd(): JsonAccessor;
|
|
799
|
+
/** `<link>` elements of the head, in DOM order. */
|
|
800
|
+
get links(): BrowserLinkElement[];
|
|
801
|
+
/** The content of a named meta — `meta('description')`, `meta('og:image')`. */
|
|
802
|
+
meta(name: string): string | undefined;
|
|
803
|
+
/** HTTP status of the main document response. */
|
|
804
|
+
get status(): number;
|
|
805
|
+
/** Rendered body text as a text accessor. */
|
|
806
|
+
get content(): TextAccessor;
|
|
807
|
+
/** The document title as a text accessor. */
|
|
808
|
+
get title(): TextAccessor;
|
|
809
|
+
/** Final URL after redirects. */
|
|
810
|
+
get url(): string;
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
626
813
|
//#region src/core/specification/shared/builder.d.ts
|
|
627
814
|
/** A named job that can be triggered via jobs.trigger(). */
|
|
628
815
|
interface JobHandle {
|
|
@@ -642,6 +829,20 @@ interface DockerSpecConfig {
|
|
|
642
829
|
}
|
|
643
830
|
/** Adapter configuration passed to the specification facets at setup time. */
|
|
644
831
|
interface SpecificationConfig {
|
|
832
|
+
/** Base URL of the website under test (website facet only). */
|
|
833
|
+
baseUrl?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Lazy browser accessor (website facet only). The first `.visit()`
|
|
836
|
+
* launches the shared browser instance; `.fetch()`-only spec files never
|
|
837
|
+
* pay the browser cost.
|
|
838
|
+
*/
|
|
839
|
+
browser?: () => Promise<BrowserPort>;
|
|
840
|
+
/**
|
|
841
|
+
* Cross-origin request policy for visits (website facet only): `'block'`
|
|
842
|
+
* aborts requests leaving the site under test — the browser-side analog
|
|
843
|
+
* of strict intercepts.
|
|
844
|
+
*/
|
|
845
|
+
external?: 'allow' | 'block';
|
|
645
846
|
command?: CliPort;
|
|
646
847
|
database?: DatabasePort;
|
|
647
848
|
/**
|
|
@@ -750,6 +951,24 @@ interface CliSpecification<DatabaseKey extends string = string> {
|
|
|
750
951
|
*/
|
|
751
952
|
exec: (args?: string | string[], options?: ExecOptions) => Promise<CliResult>;
|
|
752
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* The `website` facet — page chain entry handed out by
|
|
956
|
+
* `specification.website()`. Setup methods chain; action methods are
|
|
957
|
+
* terminal. `.visit()` renders the page in the shared browser; `.fetch()`
|
|
958
|
+
* performs one raw HTTP exchange and never follows redirects.
|
|
959
|
+
*/
|
|
960
|
+
interface WebsiteSpecification {
|
|
961
|
+
/** Set HTTP headers for the exchange (incl. User-Agent overrides). Multiple calls merge. */
|
|
962
|
+
headers: (headers: Record<string, string>) => WebsiteSpecification;
|
|
963
|
+
/** Perform one raw HTTP GET — redirects surface as 3xx results, never followed. */
|
|
964
|
+
fetch: (path: string) => Promise<FetchResult>;
|
|
965
|
+
/**
|
|
966
|
+
* Render the page in the shared browser and resolve with the captured
|
|
967
|
+
* document. With a scenario, the visitor interacts first (the When) and
|
|
968
|
+
* the capture reflects the FINAL page state.
|
|
969
|
+
*/
|
|
970
|
+
visit: (path: string, scenario?: VisitScenario) => Promise<PageResult>;
|
|
971
|
+
}
|
|
753
972
|
//#endregion
|
|
754
973
|
//#region src/core/specification/cli/result.d.ts
|
|
755
974
|
/**
|
|
@@ -1101,6 +1320,65 @@ interface JobsHandle<DatabaseKey extends string = string> {
|
|
|
1101
1320
|
}
|
|
1102
1321
|
declare function startJobs<Services extends ServiceRecord>(options: JobsSpecificationOptions<Services>): Promise<JobsHandle<DatabaseKeys<Services>>>;
|
|
1103
1322
|
//#endregion
|
|
1323
|
+
//#region src/core/specification/website/serve.adapter.d.ts
|
|
1324
|
+
/** Options for the local server started by `specification.website()`. */
|
|
1325
|
+
interface ServeOptions {
|
|
1326
|
+
/** Shell command that starts the site. Receives the chosen port as `PORT`. */
|
|
1327
|
+
command: string;
|
|
1328
|
+
/** Fixed port. Default: an OS-assigned free port, injected as `PORT`. */
|
|
1329
|
+
port?: number;
|
|
1330
|
+
/** Path polled until it answers with any HTTP status. Default `/`. */
|
|
1331
|
+
ready?: string;
|
|
1332
|
+
/** Readiness budget in milliseconds. Default 30 000. */
|
|
1333
|
+
timeout?: number;
|
|
1334
|
+
}
|
|
1335
|
+
//#endregion
|
|
1336
|
+
//#region src/core/specification/website/start-website.d.ts
|
|
1337
|
+
/**
|
|
1338
|
+
* Options for {@link startWebsite | specification.website}. `server` (start
|
|
1339
|
+
* the site locally) and `url` (target a running site) are mutually
|
|
1340
|
+
* exclusive BY TYPE — the union makes the invalid combinations
|
|
1341
|
+
* inexpressible rather than runtime-checked.
|
|
1342
|
+
*/
|
|
1343
|
+
type WebsiteSpecificationOptions = {
|
|
1344
|
+
/**
|
|
1345
|
+
* Cross-origin request policy for visits. Default: `'block'` with a
|
|
1346
|
+
* local `server` (deterministic — analytics and CDNs never leave the
|
|
1347
|
+
* machine), `'allow'` with a deployed `url`.
|
|
1348
|
+
*/
|
|
1349
|
+
external?: 'allow' | 'block';
|
|
1350
|
+
/**
|
|
1351
|
+
* Project-root override (CONVENTIONS A9): the working directory of the
|
|
1352
|
+
* `server` command. Auto-discovered from the calling file when absent.
|
|
1353
|
+
*/
|
|
1354
|
+
root?: string;
|
|
1355
|
+
} & ({
|
|
1356
|
+
/**
|
|
1357
|
+
* Start the site locally: a shell command receiving a free port
|
|
1358
|
+
* as `PORT`, polled on `ready` (default `/`) until it answers.
|
|
1359
|
+
*/
|
|
1360
|
+
server: ServeOptions;
|
|
1361
|
+
url?: never;
|
|
1362
|
+
} | {
|
|
1363
|
+
server?: never;
|
|
1364
|
+
/** Target an already-running site (a deployed or preview URL). */
|
|
1365
|
+
url: string;
|
|
1366
|
+
});
|
|
1367
|
+
/**
|
|
1368
|
+
* The record returned by {@link startWebsite | specification.website}.
|
|
1369
|
+
* Destructure with the canonical names (CONVENTIONS A3):
|
|
1370
|
+
*
|
|
1371
|
+
* const { website, cleanup } = await specification.website(…);
|
|
1372
|
+
*/
|
|
1373
|
+
interface WebsiteHandle {
|
|
1374
|
+
/** Stop the server process and the shared browser instance. */
|
|
1375
|
+
cleanup: () => Promise<void>;
|
|
1376
|
+
/** The base URL the specs run against. */
|
|
1377
|
+
url: string;
|
|
1378
|
+
website: WebsiteSpecification;
|
|
1379
|
+
}
|
|
1380
|
+
declare function startWebsite(options: WebsiteSpecificationOptions): Promise<WebsiteHandle>;
|
|
1381
|
+
//#endregion
|
|
1104
1382
|
//#region src/core/specification/shared/specification.d.ts
|
|
1105
1383
|
/**
|
|
1106
1384
|
* The three specification constructors (CONVENTIONS A2) — created in a
|
|
@@ -1146,6 +1424,14 @@ declare const specification: {
|
|
|
1146
1424
|
* server, no mode. `.trigger(name)` is the terminal action.
|
|
1147
1425
|
*/
|
|
1148
1426
|
jobs: typeof startJobs;
|
|
1427
|
+
/**
|
|
1428
|
+
* Test a deployed or locally-served website. `server` starts the site
|
|
1429
|
+
* (a shell command receiving `PORT`); `url` targets a running one.
|
|
1430
|
+
* `.visit(path)` renders the page in a single shared browser instance;
|
|
1431
|
+
* `.fetch(path)` performs one raw HTTP exchange (redirects never
|
|
1432
|
+
* followed).
|
|
1433
|
+
*/
|
|
1434
|
+
website: typeof startWebsite;
|
|
1149
1435
|
};
|
|
1150
1436
|
//#endregion
|
|
1151
1437
|
//#region src/integrations/docker/docker-lookup.d.ts
|
|
@@ -1176,6 +1462,26 @@ interface ContainerPort {
|
|
|
1176
1462
|
getLogs: () => Promise<string>;
|
|
1177
1463
|
}
|
|
1178
1464
|
//#endregion
|
|
1465
|
+
//#region src/core/specification/website/elements.d.ts
|
|
1466
|
+
/**
|
|
1467
|
+
* The element vocabulary — user-facing descriptors for visit scenarios.
|
|
1468
|
+
* Reads like English (`click(link('Articles'))`), translates to
|
|
1469
|
+
* accessibility-first locators in the browser integration. CSS/XPath is
|
|
1470
|
+
* deliberately not expressible; `testId()` is the single escape hatch.
|
|
1471
|
+
*/
|
|
1472
|
+
/** A button (or element with the button role), by accessible name. */
|
|
1473
|
+
declare const button: (name: string) => ElementRef;
|
|
1474
|
+
/** A form field, by label. */
|
|
1475
|
+
declare const field: (label: string) => ElementRef;
|
|
1476
|
+
/** A heading, by accessible name. */
|
|
1477
|
+
declare const heading: (name: string) => ElementRef;
|
|
1478
|
+
/** A link, by accessible name. */
|
|
1479
|
+
declare const link: (name: string) => ElementRef;
|
|
1480
|
+
/** An element containing the given text. */
|
|
1481
|
+
declare const content: (value: string) => ElementRef;
|
|
1482
|
+
/** The escape hatch: an element by `data-testid`. Prefer user-facing elements. */
|
|
1483
|
+
declare const testId: (id: string) => ElementRef;
|
|
1484
|
+
//#endregion
|
|
1179
1485
|
//#region src/integrations/postgres/postgres.d.ts
|
|
1180
1486
|
interface PostgresOptions {
|
|
1181
1487
|
/**
|
|
@@ -1466,8 +1772,13 @@ declare const mockOfDate: MockDatePort;
|
|
|
1466
1772
|
//#region src/index.d.ts
|
|
1467
1773
|
declare module 'vitest' {
|
|
1468
1774
|
interface Matchers<T = any> {
|
|
1469
|
-
/**
|
|
1470
|
-
|
|
1775
|
+
/**
|
|
1776
|
+
* Assert the subject is empty — zero rows for a table (async), an
|
|
1777
|
+
* empty stream for a text accessor (console, errors, stdout).
|
|
1778
|
+
*/
|
|
1779
|
+
toBeEmpty: T extends TableAccessor ? () => Promise<void> : T extends {
|
|
1780
|
+
readonly comparableText: string;
|
|
1781
|
+
} ? () => Promise<void> : never;
|
|
1471
1782
|
/** Assert the container is running. Async — docker-backed subject. */
|
|
1472
1783
|
toBeRunning: T extends ContainerAccessor ? () => Promise<void> : never;
|
|
1473
1784
|
/**
|
|
@@ -1498,4 +1809,4 @@ declare module 'vitest' {
|
|
|
1498
1809
|
}
|
|
1499
1810
|
}
|
|
1500
1811
|
//#endregion
|
|
1501
|
-
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type DatabaseKeys, type DatabasePort, DirectoryAccessor, type DockerSpecConfig, type ExecOptions, type FileAccessor, FilesystemAccessor, type HonoApp, HttpResult, type InterceptContract, type InterceptEntry, type InterceptResponder, type InterceptResponse, type InterceptResponseValue, type InterceptTrigger, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MockDatePort, type MockPort, Orchestrator, type PostgresOptions, type RedisOptions, ResponseAccessor, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, anthropic, defineContract, findContainersByLabel, http, inspectContainer, match, mockOf, mockOfDate, openai, postgres, redis, removeContainers, specification, sqlite, text };
|
|
1812
|
+
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type DatabaseKeys, type DatabasePort, DirectoryAccessor, type DockerSpecConfig, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, HttpResult, type InterceptContract, type InterceptEntry, type InterceptResponder, type InterceptResponse, type InterceptResponseValue, type InterceptTrigger, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MockDatePort, type MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ServeOptions, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, type VisitScenario, type Visitor, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, button, content, defineContract, field, findContainersByLabel, heading, http, inspectContainer, link, match, mockOf, mockOfDate, openai, postgres, redis, removeContainers, specification, sqlite, testId, text };
|