waygraph 0.15.23 → 0.15.25
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/cli/discover.d.ts +40 -0
- package/dist/cli/discover.js +148 -0
- package/dist/cli/flags.d.ts +91 -0
- package/dist/cli/flags.js +442 -0
- package/dist/cli/run-chain.d.ts +8 -0
- package/dist/cli/run-chain.js +67 -0
- package/dist/cli/usage.d.ts +4 -0
- package/dist/cli/usage.js +236 -0
- package/dist/cli/util.d.ts +4 -0
- package/dist/cli/util.js +24 -0
- package/dist/cli.js +45 -2448
- package/dist/commands/agent-dive.d.ts +1 -0
- package/dist/commands/agent-dive.js +52 -0
- package/dist/commands/check.d.ts +4 -0
- package/dist/commands/check.js +192 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +54 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +67 -0
- package/dist/commands/map.d.ts +1 -0
- package/dist/commands/map.js +29 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +104 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +600 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +33 -0
- package/dist/commands/traverse.d.ts +1 -0
- package/dist/commands/traverse.js +177 -0
- package/dist/commands/try.d.ts +1 -0
- package/dist/commands/try.js +298 -0
- package/dist/engine/blocks/effect.d.ts +41 -0
- package/dist/engine/blocks/effect.js +23 -0
- package/dist/engine/blocks/method.d.ts +90 -0
- package/dist/engine/blocks/method.js +70 -0
- package/dist/engine/blocks/nav.d.ts +138 -0
- package/dist/engine/blocks/nav.js +110 -0
- package/dist/engine/blocks/page.d.ts +59 -0
- package/dist/engine/blocks/page.js +91 -0
- package/dist/engine/compose.d.ts +75 -0
- package/dist/engine/compose.js +76 -0
- package/dist/engine/config.d.ts +55 -0
- package/dist/engine/config.js +17 -0
- package/dist/engine/core.d.ts +92 -0
- package/dist/engine/core.js +256 -0
- package/dist/engine/engine-class.d.ts +289 -0
- package/dist/engine/engine-class.js +257 -0
- package/dist/engine/flow.d.ts +266 -0
- package/dist/engine/flow.js +387 -0
- package/dist/engine/locate.d.ts +26 -0
- package/dist/engine/locate.js +62 -0
- package/dist/engine/run-graph.d.ts +147 -0
- package/dist/engine/run-graph.js +223 -0
- package/dist/engine.d.ts +11 -1224
- package/dist/engine.js +12 -1550
- package/examples/saucedemo/.waygraph-auto/0ac58118.log +0 -0
- package/examples/saucedemo/.waygraph-auto/309c107f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/3ab7c7bd.log +44 -0
- package/examples/saucedemo/.waygraph-auto/45864828.log +0 -0
- package/examples/saucedemo/.waygraph-auto/95bd8371.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bcb9e31a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/cd3e528c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/dce1535d.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e280bf20.log +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Split out of the former 8,700-line cli.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { existsSync, statSync } from "node:fs";
|
|
4
|
+
import { parseBlocksSelect } from "../blocks-select.js";
|
|
5
|
+
import { parseMinEdgeCoverage } from "../traverse-coverage.js";
|
|
6
|
+
import { runTraverse } from "../traverse-run.js";
|
|
7
|
+
export async function traverseCase(args) {
|
|
8
|
+
if (args.includes("--step") || args.includes("--auto-next") || args.includes("--autoplay")) {
|
|
9
|
+
console.error("waygraph traverse: --step / --auto-next not supported (not a demo)");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const rest = args.slice(1);
|
|
13
|
+
let from;
|
|
14
|
+
let data;
|
|
15
|
+
let maxSteps;
|
|
16
|
+
let maxVisits;
|
|
17
|
+
let maxEdge;
|
|
18
|
+
let headed = false;
|
|
19
|
+
let baseUrl;
|
|
20
|
+
let blocksFilter;
|
|
21
|
+
let parallel;
|
|
22
|
+
let session;
|
|
23
|
+
let minEdgeCoverageRaw;
|
|
24
|
+
let coverageOut;
|
|
25
|
+
let noCoverageReport = false;
|
|
26
|
+
let projectDir = process.cwd();
|
|
27
|
+
for (let i = 0; i < rest.length; i++) {
|
|
28
|
+
const a = rest[i];
|
|
29
|
+
if (a === "--from" || a.startsWith("--from=")) {
|
|
30
|
+
from = a.startsWith("--from=") ? a.slice(7) : rest[++i];
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (a === "--data" || a.startsWith("--data=")) {
|
|
34
|
+
data = a.startsWith("--data=") ? a.slice(7) : rest[++i];
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (a === "--blocks" || a.startsWith("--blocks=")) {
|
|
38
|
+
if (a.startsWith("--blocks=")) {
|
|
39
|
+
blocksFilter = a.slice("--blocks=".length);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const v = rest[++i];
|
|
43
|
+
if (!v || v.startsWith("-")) {
|
|
44
|
+
console.error("waygraph traverse: --blocks needs a glob, /regex/, or path substring");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
blocksFilter = v;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (a === "--parallel" || a.startsWith("--parallel=")) {
|
|
52
|
+
const v = a.startsWith("--parallel=") ? a.slice("--parallel=".length) : rest[++i];
|
|
53
|
+
parallel = Number(v);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (a === "--session" || a.startsWith("--session=")) {
|
|
57
|
+
const v = a.startsWith("--session=") ? a.slice("--session=".length) : rest[++i];
|
|
58
|
+
if (v !== "clone" && v !== "inherit") {
|
|
59
|
+
console.error('waygraph traverse: --session must be "clone" or "inherit"');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
session = v;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (a === "--min-edge-coverage" || a.startsWith("--min-edge-coverage=")) {
|
|
66
|
+
minEdgeCoverageRaw = a.startsWith("--min-edge-coverage=")
|
|
67
|
+
? a.slice("--min-edge-coverage=".length)
|
|
68
|
+
: rest[++i];
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (a === "--coverage-out" || a.startsWith("--coverage-out=")) {
|
|
72
|
+
coverageOut = a.startsWith("--coverage-out=")
|
|
73
|
+
? a.slice("--coverage-out=".length)
|
|
74
|
+
: rest[++i];
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (a === "--no-coverage-report") {
|
|
78
|
+
noCoverageReport = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (a === "--max-steps" || a.startsWith("--max-steps=")) {
|
|
82
|
+
const v = a.startsWith("--max-steps=") ? a.slice(12) : rest[++i];
|
|
83
|
+
maxSteps = Number(v);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (a === "--max-visits" || a.startsWith("--max-visits=")) {
|
|
87
|
+
const v = a.startsWith("--max-visits=") ? a.slice(13) : rest[++i];
|
|
88
|
+
maxVisits = Number(v);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (a === "--max-visits-per-edge" || a.startsWith("--max-visits-per-edge=")) {
|
|
92
|
+
const v = a.startsWith("--max-visits-per-edge=")
|
|
93
|
+
? a.slice("--max-visits-per-edge=".length)
|
|
94
|
+
: rest[++i];
|
|
95
|
+
maxEdge = Number(v);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (a === "--non-headless" || a === "--headed") {
|
|
99
|
+
headed = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (a === "--base-url" || a.startsWith("--base-url=")) {
|
|
103
|
+
baseUrl = a.startsWith("--base-url=") ? a.slice(11) : rest[++i];
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (a === "--help" || a === "-h") {
|
|
107
|
+
console.log(`waygraph traverse [project] [flags]
|
|
108
|
+
|
|
109
|
+
Graph crawl (RFC Phase B-E). Walks unused legal edges until a leaf,
|
|
110
|
+
budget kill, or first broken edge. --parallel N uses session clone + edge leases.
|
|
111
|
+
|
|
112
|
+
--blocks <glob|/regex/|substr> Phase C: filter *.block.ts discovery
|
|
113
|
+
--parallel N Phase D: N clone workers (default 1, max 4)
|
|
114
|
+
--session clone|inherit Phase D: default clone; inherit refused if parallel>1
|
|
115
|
+
--min-edge-coverage X Phase E: suite gate 0.8 | 80% | 80 (exit 2 if below)
|
|
116
|
+
--coverage-out PATH Phase E: write JSON (default .waygraph-traverse/coverage.json)
|
|
117
|
+
--no-coverage-report Phase E: print Coverage line only (no JSON file)
|
|
118
|
+
--from <Checkpoint> start checkpoint (optional)
|
|
119
|
+
--data '{...}' Mem seed JSON
|
|
120
|
+
--max-steps N default 50 (per worker)
|
|
121
|
+
--max-visits N per-node visit cap (default 2)
|
|
122
|
+
--max-visits-per-edge N default 1
|
|
123
|
+
--non-headless show browser
|
|
124
|
+
--base-url URL
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
waygraph traverse --blocks '**/mailpit/**/*.block.ts'
|
|
128
|
+
waygraph traverse --parallel 2 --session clone --max-steps 20
|
|
129
|
+
waygraph traverse --min-edge-coverage 80% --coverage-out ./cov.json
|
|
130
|
+
|
|
131
|
+
PASS: [Reached Leaf Node[traverse-1] at=... steps=N]
|
|
132
|
+
FAIL: [Broke at edge[traverse-1] block=... from=... to=...]
|
|
133
|
+
COVER: [Coverage edges=H/T ratio=R% min=M% PASS|FAIL]
|
|
134
|
+
`);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
if (!a.startsWith("-")) {
|
|
138
|
+
projectDir = resolve(a);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
142
|
+
console.error(`waygraph traverse: no such directory: ${projectDir}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const blocksSelect = blocksFilter
|
|
146
|
+
? parseBlocksSelect(blocksFilter)
|
|
147
|
+
: undefined;
|
|
148
|
+
let minEdgeCoverage;
|
|
149
|
+
if (minEdgeCoverageRaw !== undefined) {
|
|
150
|
+
const parsed = parseMinEdgeCoverage(minEdgeCoverageRaw);
|
|
151
|
+
if (parsed === null) {
|
|
152
|
+
console.error(`waygraph traverse: --min-edge-coverage expects 0.8, 80%, or 80 (got ${JSON.stringify(minEdgeCoverageRaw)})`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
minEdgeCoverage = parsed;
|
|
156
|
+
}
|
|
157
|
+
const code = await runTraverse(projectDir, {
|
|
158
|
+
...(from ? { from } : {}),
|
|
159
|
+
...(data ? { data } : {}),
|
|
160
|
+
...(maxSteps !== undefined && Number.isFinite(maxSteps) ? { maxSteps } : {}),
|
|
161
|
+
...(maxVisits !== undefined && Number.isFinite(maxVisits)
|
|
162
|
+
? { maxVisitsPerNode: maxVisits }
|
|
163
|
+
: {}),
|
|
164
|
+
...(maxEdge !== undefined && Number.isFinite(maxEdge)
|
|
165
|
+
? { maxVisitsPerEdge: maxEdge }
|
|
166
|
+
: {}),
|
|
167
|
+
headed,
|
|
168
|
+
...(baseUrl ? { baseURL: baseUrl } : {}),
|
|
169
|
+
...(blocksSelect ? { blocksSelect } : {}),
|
|
170
|
+
...(parallel !== undefined && Number.isFinite(parallel) ? { parallel } : {}),
|
|
171
|
+
...(session ? { session } : {}),
|
|
172
|
+
...(minEdgeCoverage !== undefined ? { minEdgeCoverage } : {}),
|
|
173
|
+
...(coverageOut ? { coverageOut: resolve(coverageOut) } : {}),
|
|
174
|
+
...(noCoverageReport ? { noCoverageReport: true } : {}),
|
|
175
|
+
});
|
|
176
|
+
process.exit(code);
|
|
177
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function tryCase(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// Split out of the former 8,700-line cli.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { packageRoot, runInherited } from "../cli/util.js";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { runChain } from "../cli/run-chain.js";
|
|
8
|
+
import { applyRunFlags, parseRunFlags } from "../cli/flags.js";
|
|
9
|
+
/**
|
|
10
|
+
* Runs a throwaway probe script rooted at `projectDir` - the exact same
|
|
11
|
+
* directory the real demo files get copied into, so it resolves "waygraph"
|
|
12
|
+
* and "@playwright/test" exactly as they will - and actually launches (then
|
|
13
|
+
* immediately closes) chromium, a real check rather than a guess at whether
|
|
14
|
+
* the browser's installed. Never leaves the probe script behind.
|
|
15
|
+
*/
|
|
16
|
+
async function probeTryPrereqs(projectDir) {
|
|
17
|
+
const tsxEsm = import.meta.resolve("tsx/esm");
|
|
18
|
+
const probeScript = [
|
|
19
|
+
"try {",
|
|
20
|
+
' await import("waygraph");',
|
|
21
|
+
"} catch {",
|
|
22
|
+
' console.error("WAYGRAPH_TRY_MISSING_WAYGRAPH");',
|
|
23
|
+
" process.exit(1);",
|
|
24
|
+
"}",
|
|
25
|
+
"let mod;",
|
|
26
|
+
"try {",
|
|
27
|
+
' mod = await import("@playwright/test");',
|
|
28
|
+
"} catch {",
|
|
29
|
+
' console.error("WAYGRAPH_TRY_MISSING_PLAYWRIGHT_TEST");',
|
|
30
|
+
" process.exit(1);",
|
|
31
|
+
"}",
|
|
32
|
+
"try {",
|
|
33
|
+
" const browser = await mod.chromium.launch({ headless: true });",
|
|
34
|
+
" await browser.close();",
|
|
35
|
+
"} catch (err) {",
|
|
36
|
+
" if (String(err && err.message).includes(\"Executable doesn't exist\")) {",
|
|
37
|
+
' console.error("WAYGRAPH_TRY_MISSING_BROWSER");',
|
|
38
|
+
" process.exit(1);",
|
|
39
|
+
" }",
|
|
40
|
+
" console.error(String((err && err.stack) || err));",
|
|
41
|
+
" process.exit(1);",
|
|
42
|
+
"}",
|
|
43
|
+
].join("\n");
|
|
44
|
+
const probePath = join(projectDir, `.waygraph-try-probe-${process.pid}.mjs`);
|
|
45
|
+
writeFileSync(probePath, probeScript);
|
|
46
|
+
try {
|
|
47
|
+
const result = await new Promise((res, rej) => {
|
|
48
|
+
const child = spawn(process.execPath, ["--import", tsxEsm, probePath], {
|
|
49
|
+
cwd: projectDir,
|
|
50
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
51
|
+
env: process.env,
|
|
52
|
+
});
|
|
53
|
+
let stderrBuf = "";
|
|
54
|
+
child.stderr?.on("data", (d) => { stderrBuf += d.toString(); });
|
|
55
|
+
child.on("error", rej);
|
|
56
|
+
child.on("exit", (code) => res({ code: code ?? 1, stderr: stderrBuf }));
|
|
57
|
+
});
|
|
58
|
+
if (result.code === 0)
|
|
59
|
+
return { ok: true };
|
|
60
|
+
const stderr = result.stderr;
|
|
61
|
+
if (stderr.includes("WAYGRAPH_TRY_MISSING_WAYGRAPH"))
|
|
62
|
+
return { ok: false, missing: "waygraph" };
|
|
63
|
+
if (stderr.includes("WAYGRAPH_TRY_MISSING_PLAYWRIGHT_TEST"))
|
|
64
|
+
return { ok: false, missing: "playwright-test" };
|
|
65
|
+
if (stderr.includes("WAYGRAPH_TRY_MISSING_BROWSER"))
|
|
66
|
+
return { ok: false, missing: "browser" };
|
|
67
|
+
return { ok: false, missing: "unknown", detail: stderr.trim() || `probe exited with code ${result.code}` };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
rmSync(probePath, { force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Installs deps + chromium inside an already-copied quickstart folder. */
|
|
74
|
+
async function ensureDirPrereqs(dir, label) {
|
|
75
|
+
let status = await probeTryPrereqs(dir);
|
|
76
|
+
if (status.ok)
|
|
77
|
+
return null;
|
|
78
|
+
if (!existsSync(join(dir, "package.json"))) {
|
|
79
|
+
return `${label}: no package.json in ${dir}`;
|
|
80
|
+
}
|
|
81
|
+
if (status.missing === "waygraph" || status.missing === "playwright-test" || status.missing === "unknown") {
|
|
82
|
+
// Copied quickstart must not keep a workspace-tainted lockfile (breaks temp-dir npm install).
|
|
83
|
+
rmSync(join(dir, "package-lock.json"), { force: true });
|
|
84
|
+
console.log(`${label}: installing dependencies in ${dir} ...`);
|
|
85
|
+
const installCode = await runInherited("npm", ["install"], dir);
|
|
86
|
+
if (installCode !== 0) {
|
|
87
|
+
return `npm install exited with code ${installCode} - see the output above.`;
|
|
88
|
+
}
|
|
89
|
+
status = await probeTryPrereqs(dir);
|
|
90
|
+
if (status.ok)
|
|
91
|
+
return null;
|
|
92
|
+
if (status.missing === "unknown") {
|
|
93
|
+
return formatProbeFailure(status.detail ?? "probe failed");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.log(`${label}: downloading Playwright's chromium browser ...`);
|
|
97
|
+
const localPlaywrightBin = join(dir, "node_modules", ".bin", "playwright");
|
|
98
|
+
const browserCode = existsSync(localPlaywrightBin)
|
|
99
|
+
? await runInherited(localPlaywrightBin, ["install", "chromium"], dir)
|
|
100
|
+
: await runInherited("npx", ["--yes", "playwright", "install", "chromium"], dir);
|
|
101
|
+
if (browserCode !== 0) {
|
|
102
|
+
return `"playwright install chromium" exited with code ${browserCode} - see the output above.`;
|
|
103
|
+
}
|
|
104
|
+
status = await probeTryPrereqs(dir);
|
|
105
|
+
if (status.ok)
|
|
106
|
+
return null;
|
|
107
|
+
return formatProbeFailure(status.detail ?? "prerequisites still aren't ready after attempting to install them.");
|
|
108
|
+
}
|
|
109
|
+
/** Playwright browser binary present but OS libs missing (common in Docker/Codespaces). */
|
|
110
|
+
function formatProbeFailure(detail) {
|
|
111
|
+
const needsDeps = /shared libraries|libnspr4|libnss3|libatk|cannot open shared object file/i.test(detail);
|
|
112
|
+
const hint = needsDeps
|
|
113
|
+
? "\n\nChromium needs OS packages too - try:\n sudo npx playwright install-deps chromium\n" +
|
|
114
|
+
"In cloud/CI without a display, record headless:\n npx waygraph try demo --video ./out --no-step"
|
|
115
|
+
: "";
|
|
116
|
+
return `couldn't verify prerequisites:\n${detail}${hint}`;
|
|
117
|
+
}
|
|
118
|
+
const TRY_DEMO_CHAIN = 'loginFlow({"saucedemo.credentials":{"username":"standard_user","password":"secret_sauce"}}) then ' +
|
|
119
|
+
'shopFlow({"saucedemo.selectedItem":{"id":"sauce-labs-backpack","name":"Sauce Labs Backpack"}}) then ' +
|
|
120
|
+
'viewerBlockedFlow({"saucedemo.credentials":{"username":"locked_out_user","password":"secret_sauce"}})';
|
|
121
|
+
/**
|
|
122
|
+
* Point a copied quickstart at this checkout's waygraph build (not npm registry).
|
|
123
|
+
* Uses `npm pack` into destDir - a file: symlink to the dev tree can double-load
|
|
124
|
+
* @playwright/test when sibling projects also install Playwright.
|
|
125
|
+
*/
|
|
126
|
+
async function wireQuickstartToPackageRoot(destDir) {
|
|
127
|
+
const root = packageRoot();
|
|
128
|
+
const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
|
|
129
|
+
const packCode = await runInherited("npm", ["pack", "--pack-destination", destDir, "--silent"], root);
|
|
130
|
+
if (packCode !== 0) {
|
|
131
|
+
throw new Error(`waygraph try demo: npm pack failed with exit code ${packCode}`);
|
|
132
|
+
}
|
|
133
|
+
const tgz = join(destDir, `waygraph-${version}.tgz`);
|
|
134
|
+
const pkgPath = join(destDir, "package.json");
|
|
135
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
136
|
+
if (!pkg.dependencies)
|
|
137
|
+
pkg.dependencies = {};
|
|
138
|
+
pkg.dependencies.waygraph = `file:${tgz}`;
|
|
139
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Copy templates/quickstart into a fresh OS temp dir and wire it to this
|
|
143
|
+
* package build. Returns destDir, or null after setting exitCode on failure.
|
|
144
|
+
*/
|
|
145
|
+
async function prepareTryQuickstart(label) {
|
|
146
|
+
const destDir = mkdtempSync(join(tmpdir(), `${label}-`));
|
|
147
|
+
cpSync(join(packageRoot(), "templates", "quickstart"), destDir, { recursive: true });
|
|
148
|
+
rmSync(join(destDir, "node_modules"), { recursive: true, force: true });
|
|
149
|
+
rmSync(join(destDir, "test-results"), { recursive: true, force: true });
|
|
150
|
+
rmSync(join(destDir, "package-lock.json"), { force: true });
|
|
151
|
+
try {
|
|
152
|
+
await wireQuickstartToPackageRoot(destDir);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const problem = await ensureDirPrereqs(destDir, label);
|
|
160
|
+
if (problem) {
|
|
161
|
+
console.error(`${label}: couldn't get ready.\n${problem}`);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return destDir;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* `waygraph try demo` - copy a self-contained saucedemo project into a temp
|
|
169
|
+
* dir (never the caller's cwd), run a chainFlow step demo (Sign In + Shop +
|
|
170
|
+
* blocked Viewer), then run the headless Playwright test, and print where
|
|
171
|
+
* everything lives.
|
|
172
|
+
*/
|
|
173
|
+
async function runTryDemo() {
|
|
174
|
+
const destDir = await prepareTryQuickstart("waygraph-try-demo");
|
|
175
|
+
if (!destDir)
|
|
176
|
+
return;
|
|
177
|
+
process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
|
|
178
|
+
process.env.WAYGRAPH_STEP ??= "1";
|
|
179
|
+
process.env.WAYGRAPH_AUTOPLAY ??= "0";
|
|
180
|
+
// Explicit --no-step --video = unattended headless .webm only (the
|
|
181
|
+
// documented cloud/CI recipe above) - nobody needs to watch a live
|
|
182
|
+
// window for Playwright's own recordVideo to capture the right thing.
|
|
183
|
+
// This must be decided BEFORE the headed default below, or the
|
|
184
|
+
// unconditional "??=" there always wins and the documented recipe never
|
|
185
|
+
// actually runs headless.
|
|
186
|
+
const unattendedVideo = Boolean(process.env.WAYGRAPH_VIDEO) && process.env.WAYGRAPH_STEP === "0";
|
|
187
|
+
if (unattendedVideo) {
|
|
188
|
+
process.env.WAYGRAPH_AUTOPLAY ??= "1";
|
|
189
|
+
process.env.WAYGRAPH_HEADED ??= "0";
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Default: headed stepper, manual Next. --video records that session
|
|
193
|
+
// too (not a headless rush) as long as --step is still on.
|
|
194
|
+
process.env.WAYGRAPH_HEADED ??= "1";
|
|
195
|
+
}
|
|
196
|
+
const videoTo = process.env.WAYGRAPH_VIDEO;
|
|
197
|
+
console.log(videoTo
|
|
198
|
+
? "waygraph try demo: step-through + record (Sign In -> Shop & Checkout -> blocked Viewer login) - click Next; video -> " +
|
|
199
|
+
(videoTo === "1" ? ".waygraph-videos/" : videoTo)
|
|
200
|
+
: "waygraph try demo: step-through (Sign In -> Shop & Checkout -> blocked Viewer login) - click Next for each step ...");
|
|
201
|
+
await runChain(destDir, TRY_DEMO_CHAIN);
|
|
202
|
+
if (process.exitCode)
|
|
203
|
+
return;
|
|
204
|
+
console.log("\nwaygraph try demo: running headless chainFlow test ...");
|
|
205
|
+
const testCode = await runInherited("npm", ["test"], destDir);
|
|
206
|
+
if (testCode !== 0) {
|
|
207
|
+
process.exitCode = testCode;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const testFile = join(destDir, "tests", "chain-flow.spec.ts");
|
|
211
|
+
const loginFlowFile = join(destDir, "src", "flows", "login.flow.ts");
|
|
212
|
+
const shopFlowFile = join(destDir, "src", "flows", "shop.flow.ts");
|
|
213
|
+
const viewerBlockedFlowFile = join(destDir, "src", "flows", "viewer-blocked.flow.ts");
|
|
214
|
+
console.log("\nwaygraph try demo: done.\n\n" +
|
|
215
|
+
"Temp project (does not touch your cwd - lives under the OS temp dir):\n" +
|
|
216
|
+
` ${destDir}\n\n` +
|
|
217
|
+
"Episodes you just watched (same Flow objects the test imports):\n" +
|
|
218
|
+
` ${loginFlowFile}\n` +
|
|
219
|
+
` ${shopFlowFile}\n` +
|
|
220
|
+
` ${viewerBlockedFlowFile}\n\n` +
|
|
221
|
+
"Headless chainFlow test (automated - no clicking):\n" +
|
|
222
|
+
` ${testFile}\n\n` +
|
|
223
|
+
"Run again in that temp folder:\n" +
|
|
224
|
+
` cd ${destDir}\n` +
|
|
225
|
+
" npm run demo # step-through, manual Next (--step --no-autoplay)\n" +
|
|
226
|
+
" npm run auto # interactive explore (Effect Add/Remove, MemNav Open details)\n" +
|
|
227
|
+
" npm test # playwright chainFlow test\n\n" +
|
|
228
|
+
"Permanent full example in this package: examples/saucedemo\n" +
|
|
229
|
+
"Keep a permanent scaffold in your tree: waygraph init my-app\n");
|
|
230
|
+
}
|
|
231
|
+
const TRY_AUTO_DATA = '{"saucedemo.credentials":{"username":"standard_user","password":"secret_sauce"}}';
|
|
232
|
+
/**
|
|
233
|
+
* `waygraph try auto` / `try auto:cli` - temp Sauce Demo explore.
|
|
234
|
+
* Default is the **headed** browser panel. Pass `auto:cli` or `--cli` for the
|
|
235
|
+
* terminal menu (same rows).
|
|
236
|
+
*
|
|
237
|
+
* Runs as a child in the temp project so Block imports share that install's
|
|
238
|
+
* `@playwright/test`. In-process explore from the outer CLI against a packed
|
|
239
|
+
* `file:*.tgz` copy double-loads Playwright and silently yields 0 graph edges
|
|
240
|
+
* ("LoginPage / No moves").
|
|
241
|
+
*/
|
|
242
|
+
async function runTryAuto(opts = { cli: false }) {
|
|
243
|
+
const destDir = await prepareTryQuickstart("waygraph-try-auto");
|
|
244
|
+
if (!destDir)
|
|
245
|
+
return;
|
|
246
|
+
process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
|
|
247
|
+
const cli = opts.cli === true;
|
|
248
|
+
console.log(cli
|
|
249
|
+
? "waygraph try auto: CLI explore on Sauce Demo (temp dir).\n" +
|
|
250
|
+
" Creds pre-seeded (saucedemo.credentials).\n" +
|
|
251
|
+
" Pick [1] submit-login, then inventory Add/Remove / Open details.\n" +
|
|
252
|
+
" Type q to quit. Headed panel: waygraph try auto\n"
|
|
253
|
+
: "waygraph try auto: headed explore on Sauce Demo (temp dir).\n" +
|
|
254
|
+
" Creds pre-seeded. CLI menu: waygraph try auto:cli\n" +
|
|
255
|
+
" Pick submit-login, then inventory menus. Quit from the panel.\n");
|
|
256
|
+
const bin = join(destDir, "node_modules", ".bin", "waygraph");
|
|
257
|
+
const args = ["auto"];
|
|
258
|
+
if (cli)
|
|
259
|
+
args.push("--cli");
|
|
260
|
+
args.push("--data", TRY_AUTO_DATA);
|
|
261
|
+
const code = await runInherited(bin, args, destDir);
|
|
262
|
+
if (code !== 0)
|
|
263
|
+
process.exitCode = code;
|
|
264
|
+
console.log("\nwaygraph try auto: explorer closed.\n\n" +
|
|
265
|
+
`Temp project:\n ${destDir}\n\n` +
|
|
266
|
+
"Run again:\n" +
|
|
267
|
+
` cd ${destDir} && npm run auto\n` +
|
|
268
|
+
` cd ${destDir} && npm run auto:cli\n\n` +
|
|
269
|
+
"In-package: examples/saucedemo\n" +
|
|
270
|
+
"Docs: https://deviate-dv8.github.io/waygraph/auto.html\n");
|
|
271
|
+
}
|
|
272
|
+
export async function tryCase(args) {
|
|
273
|
+
const flags = parseRunFlags(args.slice(1));
|
|
274
|
+
applyRunFlags(flags);
|
|
275
|
+
const modeRaw = (flags.positionals[0] ?? "demo").toLowerCase();
|
|
276
|
+
// auto:cli / --cli = CLI; bare try auto = headed panel
|
|
277
|
+
const mode = modeRaw === "auto:cli" || modeRaw === "auto-cli"
|
|
278
|
+
? "auto:cli"
|
|
279
|
+
: modeRaw === "auto"
|
|
280
|
+
? "auto"
|
|
281
|
+
: modeRaw;
|
|
282
|
+
if (mode === "auto" || mode === "auto:cli") {
|
|
283
|
+
const cli = mode === "auto:cli" || flags.cli === true
|
|
284
|
+
? true
|
|
285
|
+
: flags.headed === true || flags.nonHeadless === true
|
|
286
|
+
? false
|
|
287
|
+
: false; // try auto default = headed
|
|
288
|
+
await runTryAuto({ cli });
|
|
289
|
+
}
|
|
290
|
+
else if (mode === "demo" || mode === "") {
|
|
291
|
+
await runTryDemo();
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.error(`waygraph try: unknown mode "${modeRaw}" - use "demo", "auto", or "auto:cli"`);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Block, Checkpoint, DefinedBlock } from "../../types.js";
|
|
2
|
+
import type { MemKey } from "../../mem-page.js";
|
|
3
|
+
import type { Trait } from "../../trait.js";
|
|
4
|
+
import { defineBlock } from "../core.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript salt over {@link defineBlock}: same runtime Block, but the type
|
|
7
|
+
* forces `requires` + `instanceOptions` so `waygraph auto` can list one menu
|
|
8
|
+
* row per live instance (add/remove/toggle). Not a separate engine concept -
|
|
9
|
+
* everything is still a Block.
|
|
10
|
+
*/
|
|
11
|
+
export type EffectBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = Omit<DefinedBlock<In, Out>, "withVerify" | "modVerify" | "modVerifyAll" | "stubBefore" | "stubAfter" | "stubOnError"> & {
|
|
12
|
+
readonly __wgFactory?: "effect";
|
|
13
|
+
requires: readonly MemKey<any>[];
|
|
14
|
+
instanceOptions: NonNullable<Block<In, Out>["instanceOptions"]>;
|
|
15
|
+
withVerify(verify: Trait[] | ((out: Out) => Trait[])): EffectBlock<In, Out>;
|
|
16
|
+
modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): EffectBlock<In, Out>;
|
|
17
|
+
modVerifyAll(patches: Record<string, Trait["check"] | Trait>): EffectBlock<In, Out>;
|
|
18
|
+
stubBefore(stub: import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
19
|
+
stubBefore(stub: import("../../highlights.js").StubLifecycleFn<Out>): EffectBlock<In, Out>;
|
|
20
|
+
stubBefore(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
21
|
+
stubAfter(stub: import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
22
|
+
stubAfter(stub: import("../../highlights.js").StubLifecycleFn<Out>): EffectBlock<In, Out>;
|
|
23
|
+
stubAfter(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
24
|
+
stubOnError(stub: import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
25
|
+
stubOnError(stub: import("../../highlights.js").StubLifecycleFn<Out>): EffectBlock<In, Out>;
|
|
26
|
+
stubOnError(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): EffectBlock<In, Out>;
|
|
27
|
+
};
|
|
28
|
+
/** @deprecated Prefer {@link EffectBlock} - same type; Mem* was an early name. */
|
|
29
|
+
export type MemEffectBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = EffectBlock<In, Out>;
|
|
30
|
+
/**
|
|
31
|
+
* {@link defineBlock} constrained for effect-style instance menus: `requires` +
|
|
32
|
+
* `instanceOptions` are mandatory. Prefer this when the Block adds / removes /
|
|
33
|
+
* toggles a live instance rather than navigating. Runtime result is an ordinary
|
|
34
|
+
* Block - the helper is TypeScript salt only.
|
|
35
|
+
*/
|
|
36
|
+
export declare function defineEffectBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: Parameters<typeof defineBlock<In, Out>>[0] & {
|
|
37
|
+
requires: readonly MemKey<any>[];
|
|
38
|
+
instanceOptions: NonNullable<Block<In, Out>["instanceOptions"]>;
|
|
39
|
+
}): EffectBlock<In, Out>;
|
|
40
|
+
/** @deprecated Prefer {@link defineEffectBlock} - identical helper. */
|
|
41
|
+
export declare const defineMemEffectBlock: typeof defineEffectBlock;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineBlock } from "../core.js";
|
|
2
|
+
/**
|
|
3
|
+
* {@link defineBlock} constrained for effect-style instance menus: `requires` +
|
|
4
|
+
* `instanceOptions` are mandatory. Prefer this when the Block adds / removes /
|
|
5
|
+
* toggles a live instance rather than navigating. Runtime result is an ordinary
|
|
6
|
+
* Block - the helper is TypeScript salt only.
|
|
7
|
+
*/
|
|
8
|
+
export function defineEffectBlock(base) {
|
|
9
|
+
const built = defineBlock(base);
|
|
10
|
+
Object.defineProperty(built, "__waygraphMemKind", {
|
|
11
|
+
value: "effect",
|
|
12
|
+
enumerable: false,
|
|
13
|
+
configurable: false,
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
16
|
+
value: "effect",
|
|
17
|
+
enumerable: false,
|
|
18
|
+
configurable: false,
|
|
19
|
+
});
|
|
20
|
+
return built;
|
|
21
|
+
}
|
|
22
|
+
/** @deprecated Prefer {@link defineEffectBlock} - identical helper. */
|
|
23
|
+
export const defineMemEffectBlock = defineEffectBlock;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Checkpoint, DefinedBlock } from "../../types.js";
|
|
2
|
+
import type { Trait } from "../../trait.js";
|
|
3
|
+
import { defineBlock } from "../core.js";
|
|
4
|
+
import type { MemKey } from "../../mem-page.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript salt over {@link defineBlock} for one-shot non-nav app steps
|
|
7
|
+
* (submit, upload, finish, logout) - **methods** on a page. Same runtime Block;
|
|
8
|
+
* marks `__waygraphSalt = "method"` so auto/docs can tell Method from bare
|
|
9
|
+
* {@link defineBlock}. Does not force `instanceOptions` - use
|
|
10
|
+
* {@link defineEffectBlock} when the auto menu should spawn one row per live
|
|
11
|
+
* DOM instance. Prefer hanging methods off a {@link definePageBlock} hub.
|
|
12
|
+
*/
|
|
13
|
+
export type MethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = Omit<DefinedBlock<In, Out>, "withVerify" | "modVerify" | "modVerifyAll" | "stubBefore" | "stubAfter" | "stubOnError"> & {
|
|
14
|
+
readonly __wgFactory?: "method";
|
|
15
|
+
withVerify(verify: Trait[] | ((out: Out) => Trait[])): MethodBlock<In, Out>;
|
|
16
|
+
modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): MethodBlock<In, Out>;
|
|
17
|
+
modVerifyAll(patches: Record<string, Trait["check"] | Trait>): MethodBlock<In, Out>;
|
|
18
|
+
stubBefore(stub: import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
19
|
+
stubBefore(stub: import("../../highlights.js").StubLifecycleFn<Out>): MethodBlock<In, Out>;
|
|
20
|
+
stubBefore(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
21
|
+
stubAfter(stub: import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
22
|
+
stubAfter(stub: import("../../highlights.js").StubLifecycleFn<Out>): MethodBlock<In, Out>;
|
|
23
|
+
stubAfter(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
24
|
+
stubOnError(stub: import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
25
|
+
stubOnError(stub: import("../../highlights.js").StubLifecycleFn<Out>): MethodBlock<In, Out>;
|
|
26
|
+
stubOnError(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): MethodBlock<In, Out>;
|
|
27
|
+
};
|
|
28
|
+
/** @deprecated Prefer {@link MethodBlock} - same type; Action was the 0.7 name. */
|
|
29
|
+
export type ActionBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = MethodBlock<In, Out>;
|
|
30
|
+
export declare function defineMethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: Parameters<typeof defineBlock<In, Out>>[0]): MethodBlock<In, Out>;
|
|
31
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
32
|
+
export declare const defineActionBlock: typeof defineMethodBlock;
|
|
33
|
+
export interface AssertBlockOptions<Out extends Checkpoint<string> = Checkpoint<string>> {
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Self-loop target - the Checkpoint this assertion runs on and returns to. */
|
|
37
|
+
checkpoint: Out["__state"];
|
|
38
|
+
/**
|
|
39
|
+
* Shorthand for the wait-then-assert shape found repeatedly in hand-written
|
|
40
|
+
* assertion Blocks: waits for a heading with this accessible name before
|
|
41
|
+
* `verify` runs. Omit for a pure "assert whatever is already on the page"
|
|
42
|
+
* check with no wait. Not a general `act()` override - a Block that needs
|
|
43
|
+
* more than this stays a plain `defineMethodBlock`.
|
|
44
|
+
*/
|
|
45
|
+
waitForHeading?: string;
|
|
46
|
+
verify: Trait[] | ((out: Out) => Trait[]);
|
|
47
|
+
/** Mem keys this assertion's `verify` reads (e.g. a mem-aware Trait) - checked by preflight before the flow runs. */
|
|
48
|
+
requires?: readonly MemKey<any>[];
|
|
49
|
+
stubBefore?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
50
|
+
stubAfter?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
51
|
+
stubOnError?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
52
|
+
/** Multi-step yap slides (demo only) - same field `Instruction.slides` exposes elsewhere. */
|
|
53
|
+
slides?: import("../../highlights.js").WaygraphSlidesOrFn<Out>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sugar for a self-loop-only "pure assertion" Block: no hand-written
|
|
57
|
+
* `act`/`resolve`, just a name, a `checkpoint` tag, and a `verify` array.
|
|
58
|
+
* `resolve` is generated from the one `checkpoint` field given, closing the
|
|
59
|
+
* copy/rename footgun where a hand-restated checkpoint string can silently
|
|
60
|
+
* drift wrong across a copy/pasted file. Mirrors `definePageBlock`'s own
|
|
61
|
+
* construction pattern (flat options in, `defineBlock` built internally).
|
|
62
|
+
* See openspec/changes/waygraph-agent-skill-hardening/spec.md.
|
|
63
|
+
* @example
|
|
64
|
+
* defineAssertBlock({
|
|
65
|
+
* name: "assert-raw-materials-table",
|
|
66
|
+
* checkpoint: "RawMaterials",
|
|
67
|
+
* waitForHeading: "Raw Materials & Lead Times",
|
|
68
|
+
* verify: [Trait.visible(RawMaterialsSel.reorderBadge)],
|
|
69
|
+
* });
|
|
70
|
+
*/
|
|
71
|
+
export declare function defineAssertBlock<Out extends Checkpoint<string> = Checkpoint<string>>(options: AssertBlockOptions<Out>): AssertBlock<Out>;
|
|
72
|
+
/**
|
|
73
|
+
* Self-loop verify Block from {@link defineAssertBlock}. Branded for
|
|
74
|
+
* {@link MapBuilder.assert} (not accepted by `.method()`).
|
|
75
|
+
*/
|
|
76
|
+
export type AssertBlock<Out extends Checkpoint<string>> = Omit<DefinedBlock<Out, Out>, "withVerify" | "modVerify" | "modVerifyAll" | "stubBefore" | "stubAfter" | "stubOnError"> & {
|
|
77
|
+
readonly __wgFactory?: "assert";
|
|
78
|
+
withVerify(verify: Trait[] | ((out: Out) => Trait[])): AssertBlock<Out>;
|
|
79
|
+
modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): AssertBlock<Out>;
|
|
80
|
+
modVerifyAll(patches: Record<string, Trait["check"] | Trait>): AssertBlock<Out>;
|
|
81
|
+
stubBefore(stub: import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
82
|
+
stubBefore(stub: import("../../highlights.js").StubLifecycleFn<Out>): AssertBlock<Out>;
|
|
83
|
+
stubBefore(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
84
|
+
stubAfter(stub: import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
85
|
+
stubAfter(stub: import("../../highlights.js").StubLifecycleFn<Out>): AssertBlock<Out>;
|
|
86
|
+
stubAfter(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
87
|
+
stubOnError(stub: import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
88
|
+
stubOnError(stub: import("../../highlights.js").StubLifecycleFn<Out>): AssertBlock<Out>;
|
|
89
|
+
stubOnError(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): AssertBlock<Out>;
|
|
90
|
+
};
|