@jterrazz/test 9.0.0 → 9.2.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/README.md +81 -28
- package/dist/checker.js +9 -9
- package/dist/index.d.ts +314 -3
- package/dist/index.js +428 -7
- package/dist/oxlint.cjs +132 -66
- package/dist/oxlint.d.cts +5 -0
- package/dist/oxlint.d.ts +5 -0
- package/dist/oxlint.js +132 -66
- package/dist/playwright.adapter.js +135 -0
- package/package.json +12 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
//#region src/integrations/playwright/playwright.adapter.ts
|
|
5
|
+
/** Translate a user-facing element descriptor into a playwright locator. */
|
|
6
|
+
function locate(page, element) {
|
|
7
|
+
switch (element.kind) {
|
|
8
|
+
case "button": return page.getByRole("button", { name: element.name });
|
|
9
|
+
case "field": return page.getByLabel(element.name);
|
|
10
|
+
case "heading": return page.getByRole("heading", { name: element.name });
|
|
11
|
+
case "link": return page.getByRole("link", { name: element.name });
|
|
12
|
+
case "testId": return page.getByTestId(element.name);
|
|
13
|
+
case "text": return page.getByText(element.name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** The visitor implementation — every action auto-waits via playwright actionability. */
|
|
17
|
+
function createVisitor(page, baseUrl) {
|
|
18
|
+
return {
|
|
19
|
+
check: (element) => locate(page, element).first().check(),
|
|
20
|
+
click: (element) => locate(page, element).first().click(),
|
|
21
|
+
fill: (element, value) => locate(page, element).first().fill(value),
|
|
22
|
+
goto: async (path) => {
|
|
23
|
+
await page.goto(`${baseUrl}${path}`, { waitUntil: "load" });
|
|
24
|
+
},
|
|
25
|
+
hover: (element) => locate(page, element).first().hover(),
|
|
26
|
+
press: (key) => page.keyboard.press(key),
|
|
27
|
+
see: (element) => locate(page, element).first().waitFor({ state: "visible" }),
|
|
28
|
+
select: async (element, option) => {
|
|
29
|
+
await locate(page, element).first().selectOption(option);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Browser adapter backed by playwright chromium.
|
|
35
|
+
*
|
|
36
|
+
* ONE browser process per adapter (= per runner = per vitest worker),
|
|
37
|
+
* launched lazily on the first `open()`. Each visit gets a fresh
|
|
38
|
+
* `BrowserContext` — isolation without paying a browser launch per spec.
|
|
39
|
+
*
|
|
40
|
+
* Playwright is an optional peer dependency: it is only imported here, and
|
|
41
|
+
* this module is only loaded when a spec calls `.visit()`.
|
|
42
|
+
*/
|
|
43
|
+
var PlaywrightAdapter = class {
|
|
44
|
+
browser = null;
|
|
45
|
+
async close() {
|
|
46
|
+
if (this.browser) {
|
|
47
|
+
await this.browser.close();
|
|
48
|
+
this.browser = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async open(url, options) {
|
|
52
|
+
const context = await (await this.launch()).newContext({ extraHTTPHeaders: options.headers });
|
|
53
|
+
if (options.external === "block") {
|
|
54
|
+
const origin = new URL(options.baseUrl).origin;
|
|
55
|
+
await context.route("**/*", (route) => {
|
|
56
|
+
if (new URL(route.request().url()).origin === origin) route.continue();
|
|
57
|
+
else route.abort();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const page = await context.newPage();
|
|
62
|
+
const consoleMessages = [];
|
|
63
|
+
page.on("console", (message) => {
|
|
64
|
+
consoleMessages.push({
|
|
65
|
+
text: message.text(),
|
|
66
|
+
type: message.type()
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const response = await page.goto(url, { waitUntil: "load" });
|
|
70
|
+
if (options.scenario) try {
|
|
71
|
+
await options.scenario(createVisitor(page, options.baseUrl));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const evidence = await this.captureEvidence(page);
|
|
74
|
+
throw new Error(`visit scenario failed: ${error instanceof Error ? error.message : String(error)}${evidence ? `\nEvidence: ${evidence}` : ""}`, { cause: error });
|
|
75
|
+
}
|
|
76
|
+
const extraction = await page.evaluate(() => {
|
|
77
|
+
const links = [...document.querySelectorAll("link")].map((link) => ({
|
|
78
|
+
href: link.href,
|
|
79
|
+
hreflang: link.hreflang || void 0,
|
|
80
|
+
rel: link.rel,
|
|
81
|
+
type: link.type || void 0
|
|
82
|
+
}));
|
|
83
|
+
const metas = [...document.querySelectorAll("meta")].map((meta) => ({
|
|
84
|
+
content: meta.content,
|
|
85
|
+
name: meta.name || void 0,
|
|
86
|
+
property: meta.getAttribute("property") ?? void 0
|
|
87
|
+
}));
|
|
88
|
+
const jsonLdBlocks = [...document.querySelectorAll("script[type=\"application/ld+json\"]")].map((script) => script.textContent ?? "");
|
|
89
|
+
return {
|
|
90
|
+
html: document.documentElement.outerHTML,
|
|
91
|
+
jsonLdBlocks,
|
|
92
|
+
links,
|
|
93
|
+
metas,
|
|
94
|
+
text: document.body?.innerText ?? "",
|
|
95
|
+
title: document.title
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
consoleMessages,
|
|
100
|
+
status: response?.status() ?? 0,
|
|
101
|
+
url: page.url(),
|
|
102
|
+
...extraction
|
|
103
|
+
};
|
|
104
|
+
} finally {
|
|
105
|
+
await context.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Screenshot the failing state into a temp file; never masks the original error. */
|
|
109
|
+
async captureEvidence(page) {
|
|
110
|
+
try {
|
|
111
|
+
const path = resolve(mkdtempSync(resolve(tmpdir(), "spec-website-")), "failure.png");
|
|
112
|
+
await page.screenshot({
|
|
113
|
+
fullPage: true,
|
|
114
|
+
path
|
|
115
|
+
});
|
|
116
|
+
return path;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Launch the shared chromium instance (once), with an actionable error when playwright is absent. */
|
|
122
|
+
async launch() {
|
|
123
|
+
if (this.browser) return this.browser;
|
|
124
|
+
let chromium;
|
|
125
|
+
try {
|
|
126
|
+
({chromium} = await import("playwright"));
|
|
127
|
+
} catch {
|
|
128
|
+
throw new Error(".visit() requires playwright (optional peer dependency): npm install -D playwright && npx playwright install chromium");
|
|
129
|
+
}
|
|
130
|
+
this.browser = await chromium.launch();
|
|
131
|
+
return this.browser;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
export { PlaywrightAdapter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/test",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "Declarative testing framework for HTTP APIs, CLIs, and background jobs — one entry point, real infrastructure, golden-file assertions, plus an oxlint convention plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-testing",
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsdown --config tsdown.config.ts",
|
|
49
|
-
"docs": "
|
|
49
|
+
"docs": "npm run build && node dist/catalog.js && typescript docs",
|
|
50
50
|
"lint": "typescript check && node dist/checker.js specs",
|
|
51
51
|
"lint:fix": "typescript fix",
|
|
52
|
-
"test": "vitest --run"
|
|
52
|
+
"test": "vitest --run",
|
|
53
|
+
"pretest": "playwright install chromium"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
56
|
"better-sqlite3": "^12.11.1",
|
|
@@ -63,18 +64,25 @@
|
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@hono/node-server": "^2.0.8",
|
|
66
|
-
"@jterrazz/typescript": "^
|
|
67
|
+
"@jterrazz/typescript": "^7.0.0",
|
|
67
68
|
"@types/better-sqlite3": "^7.6.13",
|
|
68
69
|
"@types/node": "^26.1.1",
|
|
69
70
|
"@types/pg": "^8.20.0",
|
|
70
71
|
"hono": "^4.12.30",
|
|
71
72
|
"oxlint": "^1.74.0",
|
|
73
|
+
"playwright": "^1.49.0",
|
|
72
74
|
"tsdown": "^0.22.7",
|
|
73
75
|
"vitest": "^4.1.10"
|
|
74
76
|
},
|
|
75
77
|
"peerDependencies": {
|
|
78
|
+
"playwright": "^1.49.0",
|
|
76
79
|
"vitest": "^4.1.10"
|
|
77
80
|
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"playwright": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
78
86
|
"engines": {
|
|
79
87
|
"node": ">=20"
|
|
80
88
|
}
|