state-scenarios 0.3.0 → 0.5.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/dist/adapters/redux.js +2 -2
- package/dist/adapters/tanstack-query.js +1 -1
- package/dist/adapters/xstate.js +2 -2
- package/dist/adapters/zustand.js +1 -1
- package/dist/capture-BKVROWKV.js +10 -0
- package/dist/capture-BKVROWKV.js.map +1 -0
- package/dist/chunk-3VHH36HY.js +137 -0
- package/dist/chunk-3VHH36HY.js.map +1 -0
- package/dist/{chunk-DCURPYW5.js → chunk-BK4EEQXV.js} +9 -2
- package/dist/chunk-BK4EEQXV.js.map +1 -0
- package/dist/{chunk-KYU7RUWI.js → chunk-MS3OSAWQ.js} +23 -4
- package/dist/chunk-MS3OSAWQ.js.map +1 -0
- package/dist/{chunk-HEVJQ47T.js → chunk-XAH3YMWR.js} +2 -2
- package/dist/{chunk-FTWYRB6F.js → chunk-XLNYICOI.js} +1 -44
- package/dist/chunk-XLNYICOI.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +62 -14
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +7 -0
- package/dist/next.js +2 -2
- package/dist/panel.js +6 -4
- package/dist/panel.js.map +1 -1
- package/dist/playwright.d.ts +13 -0
- package/dist/playwright.js +63 -0
- package/dist/playwright.js.map +1 -1
- package/dist/ready-4MENAMNI.js +13 -0
- package/dist/ready-4MENAMNI.js.map +1 -0
- package/dist/redact.d.ts +22 -0
- package/dist/runtime.d.ts +45 -1
- package/dist/schema.d.ts +10 -0
- package/dist/vite.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-DCURPYW5.js.map +0 -1
- package/dist/chunk-FTWYRB6F.js.map +0 -1
- package/dist/chunk-KYU7RUWI.js.map +0 -1
- /package/dist/{chunk-HEVJQ47T.js.map → chunk-XAH3YMWR.js.map} +0 -0
package/dist/adapters/redux.js
CHANGED
package/dist/adapters/xstate.js
CHANGED
package/dist/adapters/zustand.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PARAM,
|
|
3
|
+
RECORD_PARAM,
|
|
4
|
+
STRICT_PARAM
|
|
5
|
+
} from "./chunk-3M6SD4TA.js";
|
|
6
|
+
import {
|
|
7
|
+
diffScenario
|
|
8
|
+
} from "./chunk-MS3OSAWQ.js";
|
|
9
|
+
import {
|
|
10
|
+
parseScenario
|
|
11
|
+
} from "./chunk-BK4EEQXV.js";
|
|
12
|
+
|
|
13
|
+
// src/redact.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
// src/jwt.ts
|
|
17
|
+
var encode = (value) => btoa(Array.from(new TextEncoder().encode(JSON.stringify(value)), (b) => String.fromCharCode(b)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
18
|
+
var unsignedJwt = (claims) => `${encode({ alg: "none", typ: "JWT" })}.${encode(claims)}.`;
|
|
19
|
+
|
|
20
|
+
// src/redact.ts
|
|
21
|
+
var SECRET_KEY = /pass(word|phrase)?$|secret|api[-_]?key|private[-_]?key|access[-_]?key|credential|^otp$|^pin$|card[-_]?number|^cvc$|^cvv$|ssn|signature|^salt$|nonce/i;
|
|
22
|
+
var TOKEN_KEY = /token|session[-_]?id|^sid$|^auth$|authorization|bearer|jwt|cookie/i;
|
|
23
|
+
var secretParam = (k) => k.toLowerCase() === "key" || /^sig$/i.test(k) || SECRET_KEY.test(k) || TOKEN_KEY.test(k);
|
|
24
|
+
var NUMERIC_ID_KEY = /session[-_]?id$|^sid$/i;
|
|
25
|
+
var JWT = /[\w-]+\.[\w-]+\.[\w-]*/g;
|
|
26
|
+
var PLACEHOLDER = "[redacted]";
|
|
27
|
+
var decode = (part) => {
|
|
28
|
+
try {
|
|
29
|
+
const bytes = Uint8Array.from(atob(part.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
30
|
+
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
31
|
+
return value instanceof Object && !Array.isArray(value) ? value : void 0;
|
|
32
|
+
} catch {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var unsign = (text) => text.replace(JWT, (token) => {
|
|
37
|
+
const [header = "", payload = "", signature = ""] = token.split(".");
|
|
38
|
+
if (!decode(header)) return token;
|
|
39
|
+
const claims = decode(payload);
|
|
40
|
+
if (claims) return unsignedJwt(claims);
|
|
41
|
+
return signature ? PLACEHOLDER : token;
|
|
42
|
+
});
|
|
43
|
+
var isUnsignedJwt = (token) => /^[\w-]+\.[\w-]+\.$/.test(token) && !!decode(token.split(".")[0] ?? "");
|
|
44
|
+
var isBasicCredential = (value) => {
|
|
45
|
+
try {
|
|
46
|
+
return atob(value).includes(":");
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var scrub = (text) => text.replace(/\bBearer\s+([\w.~+/=-]{8,})/g, (m, token) => isUnsignedJwt(token) ? m : `Bearer ${PLACEHOLDER}`).replace(/\bBasic\s+([\w+/=]{8,})/g, (m, value) => isBasicCredential(value) ? `Basic ${PLACEHOLDER}` : m).replace(/(\w+:\/\/)[^\s/@:]+:[^\s/@]+@/g, `$1${PLACEHOLDER}@`).replace(/([?&])([^=&\s#]+)=([^&\s#]+)/g, (m, sep, k) => secretParam(k) ? `${sep}${k}=${PLACEHOLDER}` : m);
|
|
52
|
+
function redactSecrets(value) {
|
|
53
|
+
const redacted = [];
|
|
54
|
+
const walk = (node, path, key, above = "") => {
|
|
55
|
+
const text = z.string().safeParse(node);
|
|
56
|
+
const jwt = text.success ? unsign(text.data) : void 0;
|
|
57
|
+
const secret = SECRET_KEY.test(key) || TOKEN_KEY.test(key) || !!above;
|
|
58
|
+
const whole = jwt !== void 0 && isUnsignedJwt(jwt);
|
|
59
|
+
if (jwt !== void 0 && secret && whole) {
|
|
60
|
+
if (jwt !== text.data) redacted.push(path);
|
|
61
|
+
return jwt;
|
|
62
|
+
}
|
|
63
|
+
const clean = jwt === void 0 || secret ? void 0 : scrub(jwt);
|
|
64
|
+
if (clean !== void 0 && clean !== text.data) {
|
|
65
|
+
redacted.push(path);
|
|
66
|
+
return clean;
|
|
67
|
+
}
|
|
68
|
+
const number = z.number().safeParse(node);
|
|
69
|
+
if ((SECRET_KEY.test(key) || SECRET_KEY.test(above) || NUMERIC_ID_KEY.test(key)) && number.success) {
|
|
70
|
+
redacted.push(path);
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
if (secret && text.success && text.data) {
|
|
74
|
+
redacted.push(path);
|
|
75
|
+
return PLACEHOLDER;
|
|
76
|
+
}
|
|
77
|
+
if (text.success || number.success) return node;
|
|
78
|
+
const holder = SECRET_KEY.test(key) || TOKEN_KEY.test(key) ? key : above;
|
|
79
|
+
if (Array.isArray(node)) return node.map((item, i) => walk(item, `${path}[${i}]`, key, holder));
|
|
80
|
+
if (node instanceof Object) {
|
|
81
|
+
const name = z.string().catch("").parse(node.name ?? node.key);
|
|
82
|
+
const pair = SECRET_KEY.test(name) || TOKEN_KEY.test(name) ? name : "";
|
|
83
|
+
return Object.fromEntries(
|
|
84
|
+
Object.entries(node).map(([k, v]) => [k, walk(v, path ? `${path}.${k}` : k, k, k === "value" && pair ? pair : holder)])
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return node;
|
|
88
|
+
};
|
|
89
|
+
return { value: walk(value, "", ""), redacted };
|
|
90
|
+
}
|
|
91
|
+
function redactQuery(query) {
|
|
92
|
+
return {
|
|
93
|
+
query: Object.fromEntries(Object.entries(query).filter(([k]) => !secretParam(k))),
|
|
94
|
+
dropped: Object.keys(query).filter(secretParam)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/capture.ts
|
|
99
|
+
var SCENARIO_PARAMS = /* @__PURE__ */ new Set([PARAM, STRICT_PARAM, RECORD_PARAM]);
|
|
100
|
+
function captureScenario(session, options) {
|
|
101
|
+
const params = {};
|
|
102
|
+
new URLSearchParams(location.search).forEach((v, k) => {
|
|
103
|
+
if (!SCENARIO_PARAMS.has(k)) params[k] = v;
|
|
104
|
+
});
|
|
105
|
+
const { query: url } = redactQuery(params);
|
|
106
|
+
const state = Object.fromEntries(
|
|
107
|
+
session.keys().flatMap((key) => {
|
|
108
|
+
const value = session.read(key);
|
|
109
|
+
return value === void 0 ? [] : [[key, redactSecrets(value).value]];
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
const network = [...session.resolved?.network ?? [], ...session.recorded()];
|
|
113
|
+
const draft = parseScenario({
|
|
114
|
+
name: options.name,
|
|
115
|
+
description: options.description || void 0,
|
|
116
|
+
path: location.pathname,
|
|
117
|
+
url,
|
|
118
|
+
network: network.length ? network : void 0,
|
|
119
|
+
handlers: session.resolved?.handlers,
|
|
120
|
+
viewport: session.resolved?.viewport,
|
|
121
|
+
state,
|
|
122
|
+
ready: options.ready || void 0
|
|
123
|
+
});
|
|
124
|
+
const flat = session.catalog.resolve(draft);
|
|
125
|
+
const flatBase = options.base ? session.catalog.resolve(options.base) : void 0;
|
|
126
|
+
const forCatalog = diffScenario(flat, flatBase);
|
|
127
|
+
if (options.schemaPath) forCatalog.$schema = options.schemaPath;
|
|
128
|
+
return { flat, forCatalog };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
unsignedJwt,
|
|
133
|
+
redactSecrets,
|
|
134
|
+
redactQuery,
|
|
135
|
+
captureScenario
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=chunk-3VHH36HY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redact.ts","../src/jwt.ts","../src/capture.ts"],"sourcesContent":["import { z } from 'zod';\nimport { unsignedJwt } from './jwt.js';\nimport type { Json, JsonObject } from './schema.js';\n\n/** Keys whose values are secrets whatever they hold. */\nconst SECRET_KEY =\n /pass(word|phrase)?$|secret|api[-_]?key|private[-_]?key|access[-_]?key|credential|^otp$|^pin$|card[-_]?number|^cvc$|^cvv$|ssn|signature|^salt$|nonce/i;\n\n/** Keys holding bearer values: a JWT keeps its claims, anything else is replaced. */\nconst TOKEN_KEY = /token|session[-_]?id|^sid$|^auth$|authorization|bearer|jwt|cookie/i;\n\n/** URL params that carry credentials, e.g. `?key=` for maps APIs or `X-Amz-Signature` in signed URLs. */\nconst secretParam = (k: string) =>\n k.toLowerCase() === 'key' || /^sig$/i.test(k) || SECRET_KEY.test(k) || TOKEN_KEY.test(k);\n\n/** Numeric bearer values: session ids. */\nconst NUMERIC_ID_KEY = /session[-_]?id$|^sid$/i;\n\n/** Three base64url parts, anywhere in a string (e.g. after `Bearer `): a JWT when the first decodes to JSON. */\nconst JWT = /[\\w-]+\\.[\\w-]+\\.[\\w-]*/g;\n\nconst PLACEHOLDER = '[redacted]';\n\nconst decode = (part: string): JsonObject | undefined => {\n try {\n const bytes = Uint8Array.from(atob(part.replace(/-/g, '+').replace(/_/g, '/')), (c) => c.charCodeAt(0));\n const value: Json = JSON.parse(new TextDecoder().decode(bytes));\n\n return value instanceof Object && !Array.isArray(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Each JWT in `text` re-issued unsigned with the same claims, so the app still decodes it but no\n * server accepts it. A signed token whose claims can't be read is replaced whole.\n */\nconst unsign = (text: string) =>\n text.replace(JWT, (token) => {\n const [header = '', payload = '', signature = ''] = token.split('.');\n\n // `v1.2.3`, `www.example.com`: not a token.\n if (!decode(header)) return token;\n const claims = decode(payload);\n\n if (claims) return unsignedJwt(claims);\n\n return signature ? PLACEHOLDER : token;\n });\n\nconst isUnsignedJwt = (token: string) =>\n /^[\\w-]+\\.[\\w-]+\\.$/.test(token) && !!decode(token.split('.')[0] ?? '');\n\nconst isBasicCredential = (value: string) => {\n try {\n return atob(value).includes(':');\n } catch {\n return false;\n }\n};\n\n/** Credentials inside any string: `Bearer`/`Basic` values, `user:pass@` in URLs, and secret URL params. */\nconst scrub = (text: string) =>\n text\n .replace(/\\bBearer\\s+([\\w.~+/=-]{8,})/g, (m, token: string) =>\n isUnsignedJwt(token) ? m : `Bearer ${PLACEHOLDER}`)\n .replace(/\\bBasic\\s+([\\w+/=]{8,})/g, (m, value: string) =>\n isBasicCredential(value) ? `Basic ${PLACEHOLDER}` : m)\n .replace(/(\\w+:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/g, `$1${PLACEHOLDER}@`)\n .replace(/([?&])([^=&\\s#]+)=([^&\\s#]+)/g, (m, sep: string, k: string) =>\n secretParam(k) ? `${sep}${k}=${PLACEHOLDER}` : m);\n\nexport interface Redacted {\n value: Json;\n /** Paths that changed, e.g. `user.accessToken`. */\n redacted: string[];\n}\n\n/**\n * Remove secrets from a recorded response body: strings under secret-like keys become\n * \"[redacted]\" and numbers become 0, and signed JWTs anywhere lose their signature but keep\n * their claims.\n */\nexport function redactSecrets(value: Json): Redacted {\n const redacted: string[] = [];\n\n /** `above`: the nearest secret-like key holding this node, e.g. `apiKey` for `apiKey.primary`. */\n const walk = (node: Json, path: string, key: string, above = ''): Json => {\n const text = z.string().safeParse(node);\n // A JWT keeps its claims, whatever key holds it: the app may decode it.\n const jwt = text.success ? unsign(text.data) : undefined;\n // Checked before the value's type: a card number or one-time code is often a number.\n // Token-like keys only for strings: `tokens: 3` is usually a count, not a credential.\n const secret = SECRET_KEY.test(key) || TOKEN_KEY.test(key) || !!above;\n // A secret key keeps its value only when the whole value is one token, now unsigned.\n const whole = jwt !== undefined && isUnsignedJwt(jwt);\n\n if (jwt !== undefined && secret && whole) {\n if (jwt !== text.data) redacted.push(path);\n\n return jwt;\n }\n\n const clean = jwt === undefined || secret ? undefined : scrub(jwt);\n\n if (clean !== undefined && clean !== text.data) {\n redacted.push(path);\n\n return clean;\n }\n\n const number = z.number().safeParse(node);\n\n if ((SECRET_KEY.test(key) || SECRET_KEY.test(above) || NUMERIC_ID_KEY.test(key)) && number.success) {\n redacted.push(path);\n\n return 0; // still a number, so the app's parsing and formatting keep working\n }\n\n if (secret && text.success && text.data) {\n redacted.push(path);\n\n return PLACEHOLDER;\n }\n\n if (text.success || number.success) return node;\n\n const holder = SECRET_KEY.test(key) || TOKEN_KEY.test(key) ? key : above;\n\n if (Array.isArray(node)) return node.map((item, i) => walk(item, `${path}[${i}]`, key, holder));\n\n if (node instanceof Object) {\n // Header-style pairs: `{ name: \"Authorization\", value: \"...\" }`.\n const name = z.string().catch('').parse(node.name ?? node.key);\n const pair = SECRET_KEY.test(name) || TOKEN_KEY.test(name) ? name : '';\n\n return Object.fromEntries(\n Object.entries(node).map((\n [k, v],\n ) => [k, walk(v, path ? `${path}.${k}` : k, k, k === 'value' && pair ? pair : holder)]),\n );\n }\n\n return node;\n };\n\n return { value: walk(value, '', ''), redacted };\n}\n\n/**\n * Drop secret query params, such as `key` or `access_token`. Replay matches only the params an\n * entry lists, so the recorded entry still answers the request.\n */\nexport function redactQuery(query: Record<string, string>) {\n return {\n query: Object.fromEntries(Object.entries(query).filter(([k]) => !secretParam(k))),\n dropped: Object.keys(query).filter(secretParam),\n };\n}\n","import type { JsonObject } from './schema.js';\n\n/** Base64url of UTF-8, so claims in any script (\"José\", \"李雷\", \"Zoë\") survive decoding. */\nconst encode = (value: JsonObject) =>\n btoa(Array.from(new TextEncoder().encode(JSON.stringify(value)), (b) => String.fromCharCode(b)).join(''))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\n/**\n * An unsigned JWT (`alg: \"none\"`) carrying `claims`, for mocked responses in \"signed-in\" scenarios\n * when the app only decodes the token it gets back. Anything that verifies signatures rejects it,\n * which keeps it out of real backends. Set `exp` in the future if the app checks expiry.\n */\nexport const unsignedJwt = (claims: JsonObject): string =>\n `${encode({ alg: 'none', typ: 'JWT' })}.${encode(claims)}.`;\n","import { diffScenario } from './catalog.js';\nimport { PARAM, RECORD_PARAM, STRICT_PARAM } from './link.js';\nimport { redactQuery, redactSecrets } from './redact.js';\nimport type { ScenarioSession } from './runtime.js';\nimport { parseScenario, type Scenario, type ScenarioInput } from './schema.js';\n\nconst SCENARIO_PARAMS = new Set([PARAM, STRICT_PARAM, RECORD_PARAM]);\n\nexport interface CaptureOptions {\n name: string;\n description?: string;\n /** Playwright selector that marks this UI state ready. */\n ready?: string;\n /**\n * Catalog scenario to `extends`. Diff uses its resolved (flattened) form.\n * Omit for a full self-contained snapshot.\n */\n base?: Scenario;\n /** Relative `$schema` path stamped on the catalog file only, e.g. `../scenario.schema.json`. */\n schemaPath?: string;\n}\n\nexport interface CapturedScenario {\n /** Flattened snapshot for inline share links. */\n flat: Scenario;\n /** File to commit: diffs against `base` when set, otherwise the flat snapshot as input. */\n forCatalog: ScenarioInput;\n}\n\n/**\n * Snapshot what the app shows now: current route and URL params, the active network\n * fixtures (plus recorded responses), and every connected adapter's live state.\n */\nexport function captureScenario(session: ScenarioSession, options: CaptureOptions): CapturedScenario {\n const params: Record<string, string> = {};\n new URLSearchParams(location.search).forEach((v, k) => {\n if (!SCENARIO_PARAMS.has(k)) params[k] = v;\n });\n\n // Saved scenarios get committed and shared: keep sessions and tokens out of them.\n const { query: url } = redactQuery(params);\n\n const state = Object.fromEntries(\n session.keys().flatMap((key) => {\n const value = session.read(key);\n\n return value === undefined ? [] : [[key, redactSecrets(value).value] as const];\n }),\n );\n\n // Recorded responses go after the scenario's own: they answered requests it didn't mock.\n const network = [...(session.resolved?.network ?? []), ...session.recorded()];\n\n const draft = parseScenario({\n name: options.name,\n description: options.description || undefined,\n path: location.pathname,\n url,\n network: network.length ? network : undefined,\n handlers: session.resolved?.handlers,\n viewport: session.resolved?.viewport,\n state,\n ready: options.ready || undefined,\n });\n\n const flat = session.catalog.resolve(draft);\n const flatBase = options.base ? session.catalog.resolve(options.base) : undefined;\n const forCatalog = diffScenario(flat, flatBase);\n\n if (options.schemaPath) forCatalog.$schema = options.schemaPath;\n\n return { flat, forCatalog };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -48,13 +48,20 @@ var ScenarioSchema = z.object({
|
|
|
48
48
|
* Otherwise earlier entries win, and a scenario's own entries come before its parents'.
|
|
49
49
|
*/
|
|
50
50
|
network: z.array(NetworkEntrySchema).optional(),
|
|
51
|
+
/**
|
|
52
|
+
* Names of MSW handlers passed to `startScenarios({ handlers })`, e.g. ones your unit tests
|
|
53
|
+
* already use. They answer after this scenario's `network` entries. Inherited and combined.
|
|
54
|
+
*/
|
|
55
|
+
handlers: z.array(z.string().min(1)).optional(),
|
|
51
56
|
/** Per-adapter state, keyed by adapter key. Objects deep-merge; null removes a key. */
|
|
52
57
|
state: z.record(z.string(), JsonSchema).optional(),
|
|
53
58
|
/**
|
|
54
59
|
* Playwright selector that is visible once the UI shows this state,
|
|
55
60
|
* e.g. "text=Loading countries…". Not inherited: each state looks different.
|
|
56
61
|
*/
|
|
57
|
-
ready: z.string().optional()
|
|
62
|
+
ready: z.string().optional(),
|
|
63
|
+
/** Browser size `shoot` and `record` use for this scenario, e.g. a phone layout. Inherited. */
|
|
64
|
+
viewport: z.object({ width: z.number().int().positive(), height: z.number().int().positive() }).optional()
|
|
58
65
|
});
|
|
59
66
|
function scenarioJsonSchema({ state = {} } = {}) {
|
|
60
67
|
return z.toJSONSchema(
|
|
@@ -88,4 +95,4 @@ export {
|
|
|
88
95
|
scenarioJsonSchema,
|
|
89
96
|
parseScenario
|
|
90
97
|
};
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
98
|
+
//# sourceMappingURL=chunk-BK4EEQXV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\n/** Scenarios live in JSON files and URLs, so everything they carry is JSON. */\nexport const JsonSchema = z.json();\n\nexport type Json = z.output<typeof JsonSchema>;\n\nexport type JsonObject = { [key: string]: Json; };\n\n/** Anything an app can hand over to be stored as JSON. */\nexport type Serializable = object | string | number | boolean | null;\n\n/**\n * Decode JSON text. Pair with `JSON.stringify` to copy app state into the format scenarios\n * are stored in: functions and undefined drop out, and the result is checked to be JSON.\n */\nexport const parseJson = (text: string): Json => JsonSchema.parse(JSON.parse(text));\n\nexport const MethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']);\n\nconst ResponseSchema = z\n .object({\n status: z.number().int().default(200),\n /** Sent as JSON. */\n body: JsonSchema.optional(),\n /** Sent as text, e.g. an HTML error page. */\n text: z.string().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n /** Milliseconds, or \"infinite\" to hold the request open (loading states). */\n delay: z.union([z.number().nonnegative(), z.literal('infinite')]).optional(),\n })\n .refine((r) => r.body === undefined || r.text === undefined, {\n message: 'Provide \"body\" (JSON) or \"text\", not both',\n });\n\nconst NetworkEntrySchema = z\n .object({\n method: MethodSchema.default('GET'),\n /** MSW path syntax: \"/api/users/:id\", absolute URLs and wildcards work. */\n path: z.string().min(1),\n /** Only match requests whose query string contains these values. */\n query: z.record(z.string(), z.string()).optional(),\n response: ResponseSchema.optional(),\n /** Successive responses for successive calls; the last one repeats. */\n sequence: z.array(ResponseSchema).min(1).optional(),\n /** Drop the inherited entry with the same method, path and query. */\n remove: z.literal(true).optional(),\n })\n .refine((e) => [e.response, e.sequence, e.remove].filter(Boolean).length === 1, {\n message: 'Provide exactly one of \"response\", \"sequence\" or \"remove\"',\n });\n\nexport const ScenarioSchema = z.object({\n $schema: z.string().optional(),\n // Kebab-case; a combined scenario is named by its parts, e.g. \"signed-in,server-error\".\n name: z.string().regex(/^[a-z0-9][a-z0-9-]*(,[a-z0-9][a-z0-9-]*)*$/, 'Use kebab-case, e.g. \"empty-search\"'),\n /** Not inherited. */\n description: z.string().optional(),\n /** Not inherited. */\n tags: z.array(z.string()).optional(),\n /** Scenario name(s) to build on. Later parents override earlier ones. */\n extends: z.union([z.string(), z.array(z.string())]).optional(),\n /** App route the scenario opens at, e.g. \"/orders/42\". Used by links, the panel, `shoot` and `openInlineScenario`. */\n path: z.string().startsWith('/').optional(),\n /** Query params the app should start with. null removes an inherited param. */\n url: z.record(z.string(), z.string().nullable()).optional(),\n /**\n * A child entry replaces the inherited entry with the same method, path and query.\n * Otherwise earlier entries win, and a scenario's own entries come before its parents'.\n */\n network: z.array(NetworkEntrySchema).optional(),\n /**\n * Names of MSW handlers passed to `startScenarios({ handlers })`, e.g. ones your unit tests\n * already use. They answer after this scenario's `network` entries. Inherited and combined.\n */\n handlers: z.array(z.string().min(1)).optional(),\n /** Per-adapter state, keyed by adapter key. Objects deep-merge; null removes a key. */\n state: z.record(z.string(), JsonSchema).optional(),\n /**\n * Playwright selector that is visible once the UI shows this state,\n * e.g. \"text=Loading countries…\". Not inherited: each state looks different.\n */\n ready: z.string().optional(),\n /** Browser size `shoot` and `record` use for this scenario, e.g. a phone layout. Inherited. */\n viewport: z.object({ width: z.number().int().positive(), height: z.number().int().positive() }).optional(),\n});\n\nexport type Scenario = z.output<typeof ScenarioSchema>;\n\nexport type ScenarioInput = z.input<typeof ScenarioSchema>;\n\nexport type NetworkEntry = z.output<typeof NetworkEntrySchema>;\n\nexport type ScenarioResponse = z.output<typeof ResponseSchema>;\n\n/**\n * JSON Schema for editors, validators and AI generators.\n * Pass adapter state schemas so `state` is described too.\n */\nexport function scenarioJsonSchema({ state = {} }: { state?: Record<string, z.ZodType>; } = {}) {\n return z.toJSONSchema(\n ScenarioSchema.extend({ state: z.object(state).partial().catchall(JsonSchema).optional() }),\n {\n io: 'input',\n // Refinements aren't representable in JSON Schema; spell the exclusivity rules out.\n override: ({ zodSchema, jsonSchema }) => {\n if (zodSchema === NetworkEntrySchema) {\n jsonSchema.oneOf = ['response', 'sequence', 'remove'].map((key) => ({ required: [key] }));\n }\n\n if (zodSchema === ResponseSchema) {\n jsonSchema.not = { required: ['body', 'text'] };\n }\n },\n },\n );\n}\n\n/** Validate scenario input (from a file, URL or editor) into a Scenario, with readable errors. */\nexport function parseScenario(data: ScenarioInput, source = 'scenario'): Scenario {\n const result = ScenarioSchema.safeParse(data);\n\n if (result.success) return result.data;\n throw new Error(`Invalid scenario \"${data.name || source}\":\\n${z.prettifyError(result.error)}`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseScenario
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BK4EEQXV.js";
|
|
4
4
|
|
|
5
5
|
// src/catalog.ts
|
|
6
6
|
var defineScenarioRules = (rules) => rules;
|
|
@@ -64,9 +64,14 @@ function merge(base, over) {
|
|
|
64
64
|
...(over.network ?? []).filter((e) => !e.remove),
|
|
65
65
|
...(base.network ?? []).filter((e) => !overKeys.has(routeKey(e)))
|
|
66
66
|
],
|
|
67
|
+
handlers: union(over.handlers, base.handlers),
|
|
67
68
|
state: mergeObjects(base.state ?? {}, over.state ?? {})
|
|
68
69
|
};
|
|
69
70
|
}
|
|
71
|
+
var union = (a = [], b = []) => {
|
|
72
|
+
const all = [.../* @__PURE__ */ new Set([...a, ...b])];
|
|
73
|
+
return all.length ? all : void 0;
|
|
74
|
+
};
|
|
70
75
|
var isJsonObject = (value) => value instanceof Object && !Array.isArray(value);
|
|
71
76
|
function mergeObjects(base, over) {
|
|
72
77
|
const out = { ...base };
|
|
@@ -88,13 +93,16 @@ function diffScenario(captured, base) {
|
|
|
88
93
|
path: captured.path,
|
|
89
94
|
url: captured.url,
|
|
90
95
|
network: captured.network,
|
|
96
|
+
handlers: captured.handlers,
|
|
91
97
|
state: captured.state,
|
|
92
|
-
ready: captured.ready
|
|
98
|
+
ready: captured.ready,
|
|
99
|
+
viewport: captured.viewport
|
|
93
100
|
});
|
|
94
101
|
}
|
|
95
102
|
const url = diffUrl(base.url, captured.url);
|
|
96
103
|
const network = diffNetwork(base.network ?? [], captured.network ?? []);
|
|
97
104
|
const state = diffObjects(base.state ?? {}, captured.state ?? {});
|
|
105
|
+
const handlers = diffHandlers(base.handlers ?? [], captured.handlers ?? []);
|
|
98
106
|
return compactScenario({
|
|
99
107
|
name: captured.name,
|
|
100
108
|
extends: base.name,
|
|
@@ -103,10 +111,19 @@ function diffScenario(captured, base) {
|
|
|
103
111
|
path: captured.path !== base.path ? captured.path : void 0,
|
|
104
112
|
url,
|
|
105
113
|
network: network.length ? network : void 0,
|
|
114
|
+
handlers,
|
|
106
115
|
state,
|
|
107
|
-
ready: captured.ready
|
|
116
|
+
ready: captured.ready,
|
|
117
|
+
viewport: jsonEqual(captured.viewport ?? null, base.viewport ?? null) ? void 0 : captured.viewport
|
|
108
118
|
});
|
|
109
119
|
}
|
|
120
|
+
function diffHandlers(base, captured) {
|
|
121
|
+
for (let k = 0; k <= captured.length; k++) {
|
|
122
|
+
const own = captured.slice(0, k);
|
|
123
|
+
if (JSON.stringify(union(own, base)) === JSON.stringify(captured)) return own.length ? own : void 0;
|
|
124
|
+
}
|
|
125
|
+
return captured.length ? captured : void 0;
|
|
126
|
+
}
|
|
110
127
|
function diffUrl(base, captured) {
|
|
111
128
|
const baseUrl = base ?? {};
|
|
112
129
|
const capturedUrl = captured ?? {};
|
|
@@ -165,8 +182,10 @@ function compactScenario(s) {
|
|
|
165
182
|
if (s.path !== void 0) out.path = s.path;
|
|
166
183
|
if (s.url !== void 0 && Object.keys(s.url).length) out.url = s.url;
|
|
167
184
|
if (s.network !== void 0 && s.network.length) out.network = s.network;
|
|
185
|
+
if (s.handlers !== void 0 && s.handlers.length) out.handlers = s.handlers;
|
|
168
186
|
if (s.state !== void 0 && Object.keys(s.state).length) out.state = s.state;
|
|
169
187
|
if (s.ready !== void 0) out.ready = s.ready;
|
|
188
|
+
if (s.viewport !== void 0) out.viewport = s.viewport;
|
|
170
189
|
return out;
|
|
171
190
|
}
|
|
172
191
|
|
|
@@ -176,4 +195,4 @@ export {
|
|
|
176
195
|
deepMerge,
|
|
177
196
|
diffScenario
|
|
178
197
|
};
|
|
179
|
-
//# sourceMappingURL=chunk-
|
|
198
|
+
//# sourceMappingURL=chunk-MS3OSAWQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["import {\n type Json,\n type JsonObject,\n type NetworkEntry,\n parseScenario,\n type Scenario,\n type ScenarioInput,\n} from './schema.js';\n\n/**\n * A business rule a resolved scenario must satisfy, beyond schema shape.\n * Return a message (or several) for each violation.\n */\nexport type ScenarioRule = (scenario: Scenario) => string | string[] | void | undefined;\n\n/** Identity helper for typing rule lists. */\nexport const defineScenarioRules = (rules: ScenarioRule[]) => rules;\n\nexport interface Catalog {\n list: Scenario[];\n get(name: string): Scenario | undefined;\n /** Flatten `extends` into one self-contained scenario, then check the rules. */\n resolve(scenario: Scenario): Scenario;\n /** Resolve every scenario; returns one message per problem. */\n check(): string[];\n}\n\nexport function createCatalog(\n raw: ScenarioInput[],\n { rules = [] }: { rules?: ScenarioRule[]; } = {},\n): Catalog {\n const byName = new Map<string, Scenario>();\n\n for (const [i, data] of raw.entries()) {\n const s = parseScenario(data, `#${i}`);\n\n // Commas join names in ?scenario=a,b, so a catalog name can't contain one.\n if (s.name.includes(',')) throw new Error(`Scenario name \"${s.name}\" can't contain a comma`);\n\n if (byName.has(s.name)) throw new Error(`Duplicate scenario name \"${s.name}\"`);\n byName.set(s.name, s);\n }\n\n const flatten = (s: Scenario, seen: string[]): Scenario => {\n if (seen.includes(s.name)) {\n throw new Error(`Circular extends: ${[...seen, s.name].join(' -> ')}`);\n }\n\n const parents = [s.extends ?? []].flat().map((name) => {\n const parent = byName.get(name);\n\n if (!parent) throw new Error(`Scenario \"${s.name}\" extends unknown \"${name}\"`);\n\n return flatten(parent, [...seen, s.name]);\n });\n\n const { extends: _extends, $schema: _schema, ...own } = s;\n\n // Start from an empty base so standalone scenarios are normalised too (remove entries, nulls).\n return [...parents, own].reduce(merge, { name: own.name });\n };\n\n const resolve = (s: Scenario) => {\n const flat = flatten(s, []);\n const violations = rules.flatMap((rule) => rule(flat) ?? []);\n\n if (violations.length) {\n throw new Error(`Scenario \"${s.name}\" breaks rules:\\n${violations.map((m) => `- ${m}`).join('\\n')}`);\n }\n\n return flat;\n };\n\n return {\n list: [...byName.values()],\n get: (name) => byName.get(name),\n resolve,\n check: () =>\n [...byName.values()].flatMap((s) => {\n try {\n resolve(s);\n\n return [];\n } catch (err) {\n return [err instanceof Error ? err.message : String(err)];\n }\n }),\n };\n}\n\nconst byKey = ([a]: [string, unknown], [b]: [string, unknown]) => a.localeCompare(b);\n\nconst routeKey = (e: NetworkEntry) =>\n `${e.method} ${e.path} ${JSON.stringify(Object.entries(e.query ?? {}).toSorted(byKey))}`;\n\nfunction merge(base: Scenario, over: Scenario): Scenario {\n const overKeys = new Set((over.network ?? []).map(routeKey));\n\n return {\n ...base,\n ...over,\n // Own-only fields: a child describes and finishes rendering differently.\n description: over.description,\n tags: over.tags,\n ready: over.ready,\n // A null param removes the inherited one.\n url: Object.fromEntries(Object.entries({ ...base.url, ...over.url }).filter(([, v]) => v !== null)),\n network: [\n ...(over.network ?? []).filter((e) => !e.remove),\n ...(base.network ?? []).filter((e) => !overKeys.has(routeKey(e))),\n ],\n handlers: union(over.handlers, base.handlers),\n state: mergeObjects(base.state ?? {}, over.state ?? {}),\n };\n}\n\nconst union = (a: string[] = [], b: string[] = []) => {\n const all = [...new Set([...a, ...b])];\n\n return all.length ? all : undefined;\n};\n\nconst isJsonObject = (value: Json | undefined): value is JsonObject =>\n value instanceof Object && !Array.isArray(value);\n\nfunction mergeObjects(base: JsonObject, over: JsonObject): JsonObject {\n const out = { ...base };\n\n for (const [k, v] of Object.entries(over)) {\n if (v === null) delete out[k];\n else out[k] = deepMerge(base[k], v);\n }\n\n return out;\n}\n\n/** Objects merge recursively, `null` deletes a key, anything else replaces. */\nexport function deepMerge(base: Json | undefined, over: Json): Json {\n return isJsonObject(base) && isJsonObject(over) ? mergeObjects(base, over) : over;\n}\n\n/**\n * Inverse of `extends` merge: overrides that turn a resolved `base` into `captured`.\n * Pass the flattened base (from `catalog.resolve`). Copy-link keeps `captured` as-is;\n * Download JSON uses this so the file can `extends` the base instead of repeating it.\n */\nexport function diffScenario(captured: Scenario, base?: Scenario): ScenarioInput {\n if (!base) {\n return compactScenario({\n name: captured.name,\n description: captured.description,\n tags: captured.tags,\n path: captured.path,\n url: captured.url,\n network: captured.network,\n handlers: captured.handlers,\n state: captured.state,\n ready: captured.ready,\n viewport: captured.viewport,\n });\n }\n\n const url = diffUrl(base.url, captured.url);\n const network = diffNetwork(base.network ?? [], captured.network ?? []);\n const state = diffObjects(base.state ?? {}, captured.state ?? {});\n const handlers = diffHandlers(base.handlers ?? [], captured.handlers ?? []);\n\n return compactScenario({\n name: captured.name,\n extends: base.name,\n description: captured.description,\n tags: captured.tags,\n path: captured.path !== base.path ? captured.path : undefined,\n url,\n network: network.length ? network : undefined,\n handlers,\n state,\n ready: captured.ready,\n viewport: jsonEqual(captured.viewport ?? null, base.viewport ?? null) ? undefined : captured.viewport,\n });\n}\n\n/**\n * The shortest own list that `extends` merges back into `captured`, order included: merge puts\n * the child's handlers first, then the base's it didn't name. Handler order is precedence.\n */\nfunction diffHandlers(base: string[], captured: string[]): string[] | undefined {\n for (let k = 0; k <= captured.length; k++) {\n const own = captured.slice(0, k);\n\n if (JSON.stringify(union(own, base)) === JSON.stringify(captured)) return own.length ? own : undefined;\n }\n\n // An inherited handler can't be removed: keep the captured order and let the base's extras follow.\n return captured.length ? captured : undefined;\n}\n\nfunction diffUrl(\n base: Scenario['url'],\n captured: Scenario['url'],\n): ScenarioInput['url'] {\n const baseUrl = base ?? {};\n const capturedUrl = captured ?? {};\n const out: Record<string, string | null> = {};\n\n for (const [k, v] of Object.entries(capturedUrl)) {\n if (v === null) continue;\n\n if (baseUrl[k] !== v) out[k] = v;\n }\n\n for (const [k, v] of Object.entries(baseUrl)) {\n if (v === null) continue;\n\n if (!(k in capturedUrl) || capturedUrl[k] === null) out[k] = null;\n }\n\n return Object.keys(out).length ? out : undefined;\n}\n\nfunction diffNetwork(base: NetworkEntry[], captured: NetworkEntry[]): NetworkEntry[] {\n const baseByKey = new Map(base.map((e) => [routeKey(e), e]));\n const capByKey = new Map<string, NetworkEntry>();\n\n // First match wins in MSW, so a later duplicate route is unreachable; keep the first.\n for (const e of captured) if (!capByKey.has(routeKey(e))) capByKey.set(routeKey(e), e);\n\n const out: NetworkEntry[] = [];\n\n for (const [key, e] of capByKey) {\n const prior = baseByKey.get(key);\n\n if (!prior || !jsonEqual(prior, e)) out.push(e);\n }\n\n for (const [key, e] of baseByKey) {\n if (!capByKey.has(key)) {\n const removed: NetworkEntry = { method: e.method, path: e.path, remove: true };\n\n if (e.query) removed.query = e.query;\n\n out.push(removed);\n }\n }\n\n return out;\n}\n\nfunction diffObjects(base: JsonObject, captured: JsonObject): JsonObject | undefined {\n const out: JsonObject = {};\n\n for (const [k, v] of Object.entries(captured)) {\n if (!(k in base)) out[k] = v;\n else if (isJsonObject(base[k]) && isJsonObject(v)) {\n const nested = diffObjects(base[k], v);\n\n if (nested) out[k] = nested;\n } else if (!jsonEqual(base[k], v)) out[k] = v;\n }\n\n for (const k of Object.keys(base)) {\n if (!(k in captured)) out[k] = null;\n }\n\n return Object.keys(out).length ? out : undefined;\n}\n\nfunction jsonEqual(a: Json | NetworkEntry, b: Json | NetworkEntry): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\nfunction compactScenario(s: ScenarioInput): ScenarioInput {\n const out: ScenarioInput = { name: s.name };\n\n if (s.$schema !== undefined) out.$schema = s.$schema;\n\n if (s.description !== undefined) out.description = s.description;\n\n if (s.tags !== undefined) out.tags = s.tags;\n\n if (s.extends !== undefined) out.extends = s.extends;\n\n if (s.path !== undefined) out.path = s.path;\n\n if (s.url !== undefined && Object.keys(s.url).length) out.url = s.url;\n\n if (s.network !== undefined && s.network.length) out.network = s.network;\n\n if (s.handlers !== undefined && s.handlers.length) out.handlers = s.handlers;\n\n if (s.state !== undefined && Object.keys(s.state).length) out.state = s.state;\n\n if (s.ready !== undefined) out.ready = s.ready;\n\n if (s.viewport !== undefined) out.viewport = s.viewport;\n\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
JsonSchema,
|
|
6
6
|
ScenarioSchema
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BK4EEQXV.js";
|
|
8
8
|
|
|
9
9
|
// src/save.ts
|
|
10
10
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
@@ -145,4 +145,4 @@ export {
|
|
|
145
145
|
createSaveHandler,
|
|
146
146
|
startSaveServer
|
|
147
147
|
};
|
|
148
|
-
//# sourceMappingURL=chunk-
|
|
148
|
+
//# sourceMappingURL=chunk-XAH3YMWR.js.map
|
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PARAM,
|
|
3
|
-
RECORD_PARAM,
|
|
4
|
-
STRICT_PARAM
|
|
5
|
-
} from "./chunk-3M6SD4TA.js";
|
|
6
|
-
import {
|
|
7
|
-
diffScenario
|
|
8
|
-
} from "./chunk-KYU7RUWI.js";
|
|
9
|
-
import {
|
|
10
|
-
parseScenario
|
|
11
|
-
} from "./chunk-DCURPYW5.js";
|
|
12
|
-
|
|
13
|
-
// src/capture.ts
|
|
14
|
-
var SCENARIO_PARAMS = /* @__PURE__ */ new Set([PARAM, STRICT_PARAM, RECORD_PARAM]);
|
|
15
|
-
function captureScenario(session, options) {
|
|
16
|
-
const url = {};
|
|
17
|
-
new URLSearchParams(location.search).forEach((v, k) => {
|
|
18
|
-
if (!SCENARIO_PARAMS.has(k)) url[k] = v;
|
|
19
|
-
});
|
|
20
|
-
const state = Object.fromEntries(
|
|
21
|
-
session.keys().flatMap((key) => {
|
|
22
|
-
const value = session.read(key);
|
|
23
|
-
return value === void 0 ? [] : [[key, value]];
|
|
24
|
-
})
|
|
25
|
-
);
|
|
26
|
-
const network = [...session.resolved?.network ?? [], ...session.recorded()];
|
|
27
|
-
const draft = parseScenario({
|
|
28
|
-
name: options.name,
|
|
29
|
-
description: options.description || void 0,
|
|
30
|
-
path: location.pathname,
|
|
31
|
-
url,
|
|
32
|
-
network: network.length ? network : void 0,
|
|
33
|
-
state,
|
|
34
|
-
ready: options.ready || void 0
|
|
35
|
-
});
|
|
36
|
-
const flat = session.catalog.resolve(draft);
|
|
37
|
-
const flatBase = options.base ? session.catalog.resolve(options.base) : void 0;
|
|
38
|
-
const forCatalog = diffScenario(flat, flatBase);
|
|
39
|
-
if (options.schemaPath) forCatalog.$schema = options.schemaPath;
|
|
40
|
-
return { flat, forCatalog };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
1
|
// src/ready.ts
|
|
44
2
|
var SKIP = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "META", "LINK", "HEAD", "HTML"]);
|
|
45
3
|
var MAX_TEXT = 60;
|
|
@@ -268,10 +226,9 @@ function isShown(el) {
|
|
|
268
226
|
}
|
|
269
227
|
|
|
270
228
|
export {
|
|
271
|
-
captureScenario,
|
|
272
229
|
selectorForElement,
|
|
273
230
|
closestReadySelector,
|
|
274
231
|
suggestReadySelectors,
|
|
275
232
|
checkReadySelector
|
|
276
233
|
};
|
|
277
|
-
//# sourceMappingURL=chunk-
|
|
234
|
+
//# sourceMappingURL=chunk-XLNYICOI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ready.ts"],"sourcesContent":["const SKIP = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'HEAD', 'HTML']);\n\nconst MAX_TEXT = 60;\n\nconst CHROME =\n 'header, nav, aside, footer, [role=banner], [role=navigation], [role=complementary], [role=contentinfo]';\n\n// Roles whose accessible name comes from their text. Others (alert, status, region…) are\n// named only by aria-label/labelledby, so Playwright's [name=…] never matches their text.\nconst NAME_FROM_CONTENT = new Set([\n 'button',\n 'cell',\n 'checkbox',\n 'columnheader',\n 'gridcell',\n 'heading',\n 'link',\n 'menuitem',\n 'menuitemcheckbox',\n 'menuitemradio',\n 'option',\n 'radio',\n 'row',\n 'rowheader',\n 'switch',\n 'tab',\n 'tooltip',\n 'treeitem',\n]);\n\n// Implicit roles implicitRole knows; for any other role only an explicit role=\"…\" is checkable.\nconst IMPLICIT_ROLES = new Set([\n 'button',\n 'link',\n 'heading',\n 'img',\n 'textbox',\n 'searchbox',\n 'checkbox',\n 'radio',\n 'combobox',\n]);\n\n/** Playwright selector for an element the user marked as the scenario's ready signal. */\nexport function selectorForElement(el: Element): string | undefined {\n if (!(el instanceof Element) || SKIP.has(el.tagName) || el.closest('scenario-panel')) {\n return undefined;\n }\n\n for (const attr of ['data-testid', 'data-test-id']) {\n const testId = el.getAttribute(attr);\n\n if (testId) return `[${attr}=${JSON.stringify(testId)}]`;\n }\n\n const role = roleOf(el);\n const name = role && accessibleName(el, role);\n\n if (role && name && name.length <= MAX_TEXT) {\n return `role=${role}[name=${JSON.stringify(name)}]`;\n }\n\n const text = ownText(el);\n\n if (text && text.length <= MAX_TEXT) {\n const selector = `text=${JSON.stringify(text)}`;\n const check = checkReadySelector(selector, el.ownerDocument);\n\n if (check.status === 'match' && check.count === 1) return selector;\n }\n\n if (role) return `role=${role}`;\n\n return undefined;\n}\n\n/**\n * The nearest element at or above `el` that has a ready selector, so clicking an icon or a\n * wrapper still marks something.\n */\nexport function closestReadySelector(el: Element): { element: Element; selector: string; } | undefined {\n for (let node: Element | null = el; node && node !== node.ownerDocument.body; node = node.parentElement) {\n const selector = selectorForElement(node);\n\n if (selector) return { element: node, selector };\n }\n\n return undefined;\n}\n\n/**\n * Ranked Playwright selectors for visible, distinctive UI, offered when the user\n * hasn't marked ready. Prefers test ids, then named roles, then unique short text.\n */\nexport function suggestReadySelectors(\n root?: ParentNode | null,\n { limit = 3 }: { limit?: number; } = {},\n): string[] {\n const target = root ?? globalThis.document?.body;\n\n if (!target) return [];\n\n const ranked: { score: number; selector: string; }[] = [];\n const seen = new Set<string>();\n\n const walk = (node: Element) => {\n if (SKIP.has(node.tagName) || node.localName === 'scenario-panel' || !isVisible(node)) return;\n\n const selector = selectorForElement(node);\n // A count or a single character (\"0\" in \"0 countries\") is too generic to suggest.\n const tooShort = selector?.startsWith('text=') && selector.length < 'text=\"\"'.length + 3;\n\n if (selector && !tooShort && !seen.has(selector)) {\n seen.add(selector);\n ranked.push({ score: scoreSelector(selector, node), selector });\n }\n\n for (const child of node.children) walk(child);\n };\n\n if (target instanceof Element) walk(target);\n else for (const child of target.children) walk(child);\n\n return ranked.toSorted((a, b) => b.score - a.score).slice(0, limit).map((r) => r.selector);\n}\n\nexport type ReadyCheck =\n /** `first` is what `openScenario` waits on; `visible` says whether it's showing now. */\n | { status: 'match'; count: number; first: Element; visible: boolean; }\n | { status: 'none'; }\n /** A selector form this page can't evaluate (xpath, chains, regex…); Playwright still can. */\n | { status: 'unchecked'; }\n | { status: 'invalid'; error: string; };\n\n/**\n * Evaluate a ready selector against the live page the way Playwright would, for the forms\n * the panel generates and plain CSS. Anything else comes back `unchecked`, never a guess.\n */\nexport function checkReadySelector(selector: string, doc: Document = document): ReadyCheck {\n const s = selector.trim();\n\n if (!s || s.includes('>>')) return { status: 'unchecked' };\n\n const found = s.startsWith('text=')\n ? matchText(s.slice(5), doc)\n : s.startsWith('role=')\n ? matchRole(s.slice(5), doc)\n : s.startsWith('css=') || !/^[a-z-]+=/i.test(s)\n ? matchCss(s.replace(/^css=/, ''), doc)\n : undefined;\n\n if (!found) return { status: 'unchecked' };\n\n if ('error' in found) return { status: 'invalid', error: found.error };\n\n const [first] = found;\n\n return first\n ? { status: 'match', count: found.length, first, visible: isShown(first) }\n : { status: 'none' };\n}\n\ntype Found = Element[] | { error: string; } | undefined;\n\nfunction matchText(body: string, doc: Document): Found {\n const quoted = unquote(body);\n\n // Quoted: an element with a text node equal to it, case-sensitive.\n if (quoted !== undefined) {\n return appElements(doc).filter((el) =>\n [...el.childNodes].some((n) =>\n n.nodeType === Node.TEXT_NODE && collapse(n.textContent ?? '') === quoted\n )\n );\n }\n\n if (body.startsWith('/')) return undefined;\n\n // Unquoted: the smallest elements whose text contains it, ignoring case.\n const needle = collapse(body).toLowerCase();\n\n if (!needle) return undefined;\n\n const has = (el: Element) => collapse(el.textContent ?? '').toLowerCase().includes(needle);\n\n return appElements(doc).filter((el) => has(el) && ![...el.children].some(has));\n}\n\nfunction matchRole(body: string, doc: Document): Found {\n const m = /^([a-z]+)(?:\\[name=(\".*\")\\])?$/.exec(body);\n\n if (!m) return undefined;\n\n const [, role, rawName] = m;\n const name = rawName === undefined ? undefined : unquote(rawName);\n\n if (rawName !== undefined && name === undefined) return undefined;\n\n // Like Playwright's role engine, hidden elements don't count.\n const found = appElements(doc).filter((el) =>\n roleOf(el) === role && isShown(el) && !el.closest('[aria-hidden=true]')\n && (name === undefined || accessibleName(el, role) === name)\n );\n\n // We only know some implicit roles (<main> is \"main\", <p> is \"paragraph\"…), so a miss\n // on any other role isn't proof of absence.\n if (!found.length && !IMPLICIT_ROLES.has(role)) return undefined;\n\n return found;\n}\n\nfunction matchCss(css: string, doc: Document): Found {\n try {\n return [...doc.querySelectorAll(css)].filter((el) => !el.closest('scenario-panel'));\n } catch {\n return { error: 'Not a valid CSS selector' };\n }\n}\n\n/** A JSON-style \"quoted\" string, or undefined when `s` isn't one. */\nfunction unquote(s: string): string | undefined {\n if (!s.startsWith('\"') || !s.endsWith('\"') || s.length < 2) return undefined;\n\n try {\n return JSON.parse(s);\n } catch {\n return undefined;\n }\n}\n\nfunction appElements(doc: Document): Element[] {\n const out: Element[] = [];\n\n const walk = (el: Element) => {\n if (SKIP.has(el.tagName) || el.localName === 'scenario-panel') return;\n out.push(el);\n\n for (const child of el.children) walk(child);\n };\n\n if (doc.body) walk(doc.body);\n\n return out;\n}\n\nfunction scoreSelector(selector: string, el: Element): number {\n let score = 0;\n\n if (selector.startsWith('[data-test')) score += 100;\n else if (selector.startsWith('role=') && selector.includes('[name=')) score += 80;\n else if (selector.startsWith('text=')) score += 60;\n else if (selector.startsWith('role=')) score += 40;\n\n // Prefer leaf-ish content over giant containers.\n if (el.children.length === 0) score += 10;\n\n if (el.tagName === 'H1' || el.tagName === 'H2' || el.tagName === 'H3') score += 15;\n\n const role = el.getAttribute('role');\n\n if (role === 'alert' || role === 'status') score += 20;\n\n // Headers, navs, sidebars and inputs render in every state, so they make weak ready signals.\n if (el.closest(CHROME)) score -= 50;\n\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') score -= 50;\n\n return score;\n}\n\nfunction roleOf(el: Element): string | undefined {\n return el.getAttribute('role')?.trim().split(/\\s+/)[0] || implicitRole(el);\n}\n\nfunction implicitRole(el: Element): string | undefined {\n const tag = el.tagName;\n\n if (tag === 'BUTTON') return 'button';\n\n if (tag === 'A' && el.hasAttribute('href')) return 'link';\n\n if (tag === 'H1' || tag === 'H2' || tag === 'H3' || tag === 'H4' || tag === 'H5' || tag === 'H6') {\n return 'heading';\n }\n\n if (tag === 'IMG') return 'img';\n\n if (tag === 'INPUT') {\n const type = (el.getAttribute('type') ?? 'text').toLowerCase();\n\n if (type === 'submit' || type === 'button') return 'button';\n\n if (type === 'checkbox') return 'checkbox';\n\n if (type === 'radio') return 'radio';\n\n if (type === 'search') return 'searchbox';\n\n return 'textbox';\n }\n\n if (tag === 'TEXTAREA') return 'textbox';\n\n if (tag === 'SELECT') return 'combobox';\n\n return undefined;\n}\n\n/** The accessible name Playwright's `[name=…]` compares against, for the common cases. */\nfunction accessibleName(el: Element, role: string): string | undefined {\n const labelledBy = el.getAttribute('aria-labelledby');\n\n if (labelledBy) {\n const text = collapse(\n labelledBy.split(/\\s+/).map((id) => el.ownerDocument.getElementById(id)?.textContent ?? '').join(' '),\n );\n\n if (text) return text;\n }\n\n const label = collapse(el.getAttribute('aria-label') ?? '');\n\n if (label) return label;\n\n if (\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement\n ) {\n const fromLabel = collapse([...(el.labels ?? [])].map((l) => l.textContent ?? '').join(' '));\n\n if (fromLabel) return fromLabel;\n\n if (!(el instanceof HTMLSelectElement) && el.placeholder) return collapse(el.placeholder);\n }\n\n if (el instanceof HTMLImageElement && el.alt) return collapse(el.alt);\n\n if (NAME_FROM_CONTENT.has(role)) return collapse(el.textContent ?? '') || undefined;\n\n return collapse(el.getAttribute('title') ?? '') || undefined;\n}\n\n/** The element's first own text node, which is what a quoted `text=` selector matches. */\nfunction ownText(el: Element): string {\n for (const n of el.childNodes) {\n const text = n.nodeType === Node.TEXT_NODE ? collapse(n.textContent ?? '') : '';\n\n if (text) return text;\n }\n\n // No own text: a lone descendant text still identifies this element's content.\n return el.children.length === 1 ? ownText(el.children[0]) : '';\n}\n\nfunction collapse(s: string): string {\n return s.replace(/\\s+/g, ' ').trim();\n}\n\nfunction isVisible(el: Element): boolean {\n if (!(el instanceof HTMLElement)) return true;\n const style = el.ownerDocument.defaultView?.getComputedStyle(el);\n\n if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;\n\n if (el.hidden || el.getAttribute('aria-hidden') === 'true') return false;\n\n return true;\n}\n\n/** Rendered including ancestors: what Playwright's waitFor needs. */\nfunction isShown(el: Element): boolean {\n for (let node: Element | null = el; node; node = node.parentElement) {\n if (!(node instanceof HTMLElement)) continue;\n\n if (node.hidden) return false;\n const style = node.ownerDocument.defaultView?.getComputedStyle(node);\n\n if (style && (style.display === 'none' || style.visibility === 'hidden')) return false;\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { type CapturedScenario, type CaptureOptions, captureScenario } from './capture.js';
|
|
2
2
|
export { type Catalog, createCatalog, deepMerge, defineScenarioRules, diffScenario, type ScenarioRule, } from './catalog.js';
|
|
3
|
+
export { unsignedJwt } from './jwt.js';
|
|
4
|
+
export { redactSecrets } from './redact.js';
|
|
3
5
|
export { DATA_KEY, PARAM, readInline, RECORD_PARAM, scenarioHref, STRICT_PARAM } from './link.js';
|
|
4
6
|
export { toHandlers } from './network.js';
|
|
5
7
|
export { checkReadySelector, type ReadyCheck, selectorForElement, suggestReadySelectors } from './ready.js';
|
|
6
|
-
export { type Adapter, type Connection, currentSession, type JsonAdapter, type MswWorker, navigate, type ScenarioSession, type StartOptions, startScenarios, type TypedAdapter, } from './runtime.js';
|
|
8
|
+
export { type Adapter, type Connection, currentSession, type JsonAdapter, type MswWorker, navigate, type PageHooks, type ReadyReport, type ScenarioSession, type StartOptions, startScenarios, type TypedAdapter, } from './runtime.js';
|
|
7
9
|
export { type Json, type JsonObject, JsonSchema, type NetworkEntry, parseJson, parseScenario, type Scenario, type ScenarioInput, scenarioJsonSchema, ScenarioSchema, type Serializable, } from './schema.js';
|