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,70 @@
|
|
|
1
|
+
// Split out of the former 2,900-line engine.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
|
+
import { checkpoint } from "../../types.js";
|
|
3
|
+
import { defineBlock } from "../core.js";
|
|
4
|
+
export function defineMethodBlock(base) {
|
|
5
|
+
const built = defineBlock(base);
|
|
6
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
7
|
+
value: "method",
|
|
8
|
+
enumerable: false,
|
|
9
|
+
configurable: false,
|
|
10
|
+
});
|
|
11
|
+
return built;
|
|
12
|
+
}
|
|
13
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
14
|
+
export const defineActionBlock = defineMethodBlock;
|
|
15
|
+
/**
|
|
16
|
+
* Sugar for a self-loop-only "pure assertion" Block: no hand-written
|
|
17
|
+
* `act`/`resolve`, just a name, a `checkpoint` tag, and a `verify` array.
|
|
18
|
+
* `resolve` is generated from the one `checkpoint` field given, closing the
|
|
19
|
+
* copy/rename footgun where a hand-restated checkpoint string can silently
|
|
20
|
+
* drift wrong across a copy/pasted file. Mirrors `definePageBlock`'s own
|
|
21
|
+
* construction pattern (flat options in, `defineBlock` built internally).
|
|
22
|
+
* See openspec/changes/waygraph-agent-skill-hardening/spec.md.
|
|
23
|
+
* @example
|
|
24
|
+
* defineAssertBlock({
|
|
25
|
+
* name: "assert-raw-materials-table",
|
|
26
|
+
* checkpoint: "RawMaterials",
|
|
27
|
+
* waitForHeading: "Raw Materials & Lead Times",
|
|
28
|
+
* verify: [Trait.visible(RawMaterialsSel.reorderBadge)],
|
|
29
|
+
* });
|
|
30
|
+
*/
|
|
31
|
+
export function defineAssertBlock(options) {
|
|
32
|
+
const built = defineMethodBlock({
|
|
33
|
+
name: options.name,
|
|
34
|
+
...(options.description ? { description: options.description } : {}),
|
|
35
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
36
|
+
instruction: {
|
|
37
|
+
async act(page) {
|
|
38
|
+
if (options.waitForHeading !== undefined) {
|
|
39
|
+
await page.getByRole("heading", { name: options.waitForHeading }).waitFor();
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
43
|
+
verify: options.verify,
|
|
44
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
45
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
46
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
47
|
+
...(options.slides ? { slides: options.slides } : {}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
// Real bug this closes: `waygraph graph`'s static discovery only knows
|
|
51
|
+
// how to read a Block's Checkpoint tags from an EXPLICIT `<In, Out>`
|
|
52
|
+
// generic written in the source text (or, for Nav/Page Blocks, by calling
|
|
53
|
+
// their own input-independent `resolve()` directly). `defineAssertBlock`'s
|
|
54
|
+
// whole point is skipping that generic - called exactly as documented
|
|
55
|
+
// (`defineAssertBlock({ name, checkpoint, verify })`, no generics), it was
|
|
56
|
+
// silently invisible to both `waygraph graph` and the live Pilot menu
|
|
57
|
+
// ("2 Block(s) skipped (Out not resolvable)", confirmed live against a
|
|
58
|
+
// real veciro-waygraph session). `resolve()` here is ALSO input-
|
|
59
|
+
// independent (always `checkpoint(options.checkpoint)`), so the same
|
|
60
|
+
// resolve()-calling path Nav/Page Blocks already use is reused via this
|
|
61
|
+
// marker instead of requiring authors to write generics just to be found.
|
|
62
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
63
|
+
value: "assert",
|
|
64
|
+
enumerable: false,
|
|
65
|
+
configurable: false,
|
|
66
|
+
});
|
|
67
|
+
// Brand for map().assert() - distinct from MethodBlock so .method(assert)
|
|
68
|
+
// is a compile error even though runtime still carries method salt.
|
|
69
|
+
return built;
|
|
70
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Block, Checkpoint, DefinedBlock, WaygraphHighlight } from "../../types.js";
|
|
2
|
+
import type { Trait } from "../../trait.js";
|
|
3
|
+
import { MemPage } from "../../mem-page.js";
|
|
4
|
+
import type { MemKey } from "../../mem-page.js";
|
|
5
|
+
/**
|
|
6
|
+
* A Block whose only possible action is navigating to a URL - built via
|
|
7
|
+
* {@link defineNavBlock}, never with a hand-authored `act()`. Extends `Block`
|
|
8
|
+
* (same shape `DefinedBlock`/`ComposedBlock` already use), so it drops into
|
|
9
|
+
* `defineFlow([...])`, `connect()`, `composeBlock()` exactly like any other
|
|
10
|
+
* Block - no special-casing needed anywhere that only expects a `Block`.
|
|
11
|
+
* See `openspec/specs/nav-block-and-check/spec.md` for why this exists.
|
|
12
|
+
*
|
|
13
|
+
* `__wgFactory` is a TypeScript-only brand so {@link MapBuilder.gotoPage} /
|
|
14
|
+
* {@link MapBuilder.gotoExternal} can reject Assert/Method Blocks at compile
|
|
15
|
+
* time (runtime still checks `__waygraphKind`). Decorate methods are re-stated
|
|
16
|
+
* (via Omit) so `.stubBefore()` / `.withVerify()` keep the brand.
|
|
17
|
+
*/
|
|
18
|
+
export type NavBlock<Out extends Checkpoint<string>> = Omit<DefinedBlock<Checkpoint<string>, Out>, "withVerify" | "modVerify" | "modVerifyAll" | "stubBefore" | "stubAfter" | "stubOnError"> & {
|
|
19
|
+
readonly __wgFactory?: "nav";
|
|
20
|
+
withVerify(verify: Trait[] | ((out: Out) => Trait[])): NavBlock<Out>;
|
|
21
|
+
modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): NavBlock<Out>;
|
|
22
|
+
modVerifyAll(patches: Record<string, Trait["check"] | Trait>): NavBlock<Out>;
|
|
23
|
+
stubBefore(stub: import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
24
|
+
stubBefore(stub: import("../../highlights.js").StubLifecycleFn<Out>): NavBlock<Out>;
|
|
25
|
+
stubBefore(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
26
|
+
stubAfter(stub: import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
27
|
+
stubAfter(stub: import("../../highlights.js").StubLifecycleFn<Out>): NavBlock<Out>;
|
|
28
|
+
stubAfter(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
29
|
+
stubOnError(stub: import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
30
|
+
stubOnError(stub: import("../../highlights.js").StubLifecycleFn<Out>): NavBlock<Out>;
|
|
31
|
+
stubOnError(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): NavBlock<Out>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* `defineNavBlock`'s own options - exactly one of `url`/`click` required,
|
|
35
|
+
* enforced at compile time (giving both, or neither, is a type error - see
|
|
36
|
+
* `typecheck/nav-block.types.ts`). `checkpoint` is the tag this NavBlock
|
|
37
|
+
* resolves to once navigation completes - a NavBlock never branches on
|
|
38
|
+
* observed evidence the way a regular Block can, so there's no `resolve`
|
|
39
|
+
* logic to author; it always just declares "arrived."
|
|
40
|
+
*/
|
|
41
|
+
export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
checkpoint: Out["__state"];
|
|
45
|
+
requires?: readonly MemKey<any>[];
|
|
46
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
47
|
+
stubBefore?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
48
|
+
stubAfter?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
49
|
+
stubOnError?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
50
|
+
/** @deprecated Prefer stubAfter. */
|
|
51
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
52
|
+
/**
|
|
53
|
+
* Live per-instance menu rows for `waygraph auto` (see {@link Block.instanceOptions}).
|
|
54
|
+
* When set, the explore menu lists one row per option instead of a single
|
|
55
|
+
* generic nav edge - used by {@link defineMemNavBlock}.
|
|
56
|
+
*/
|
|
57
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
58
|
+
} & ({
|
|
59
|
+
/**
|
|
60
|
+
* Direct URL navigation. Accepts a plain string or a function
|
|
61
|
+
* `(mem) => string` for parameterized routes (e.g.
|
|
62
|
+
* `/dashboard/requests/:id` with a real id read from mem). The
|
|
63
|
+
* escape hatch for the few real cases with no click path to get
|
|
64
|
+
* there (a deep link from an email, a public share link) - not for
|
|
65
|
+
* skipping past navigation an agent didn't feel like finding. Prefer
|
|
66
|
+
* `click` whenever the target app actually has one.
|
|
67
|
+
*/
|
|
68
|
+
url: string | ((mem: MemPage) => string);
|
|
69
|
+
click?: never;
|
|
70
|
+
} | {
|
|
71
|
+
/**
|
|
72
|
+
* Navigates by clicking a real element already on the page (a nav
|
|
73
|
+
* link, a sidebar item) instead of teleporting straight to a URL -
|
|
74
|
+
* the opinionated default. Accepts a Playwright selector string, or
|
|
75
|
+
* a function `(mem) => selector` for a target that depends on mem
|
|
76
|
+
* (e.g. clicking the row for a specific id). Verifying arrival is
|
|
77
|
+
* `verify`'s job (typically `Trait.url(...)`), same as a `url`-based
|
|
78
|
+
* NavBlock - the click itself is only ever awaited for its own
|
|
79
|
+
* completion, not for whatever navigation it triggers.
|
|
80
|
+
*/
|
|
81
|
+
click: string | ((mem: MemPage) => string);
|
|
82
|
+
url?: never;
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* Builds a {@link NavBlock}. The generated `act()` is always exactly
|
|
86
|
+
* `page.goto(url)` or `page.locator(click).click()` - never anything else,
|
|
87
|
+
* never author-supplied. Callers get a real `Page` here internally (the one
|
|
88
|
+
* legitimate place navigation belongs); `defineBlock`'s own `ActionPage`
|
|
89
|
+
* narrowing is what discourages navigation everywhere else, and never
|
|
90
|
+
* applies to this generated function since no author ever writes it by
|
|
91
|
+
* hand.
|
|
92
|
+
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
93
|
+
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
94
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
95
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
96
|
+
*/
|
|
97
|
+
export declare function defineNavBlock<Out extends Checkpoint<string>>(options: NavBlockOptions<Out>): NavBlock<Out>;
|
|
98
|
+
/**
|
|
99
|
+
* Click-only {@link NavBlock}. Same runtime as {@link defineNavBlock} with
|
|
100
|
+
* `click` - use this in app code so you never wonder whether a Nav is url or
|
|
101
|
+
* click (no `__waygraphNavClick` sniffing at author time). Prefer
|
|
102
|
+
* {@link defineNavBlock} only for `url` / `goto` deep links.
|
|
103
|
+
*/
|
|
104
|
+
export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
105
|
+
name: string;
|
|
106
|
+
description?: string;
|
|
107
|
+
checkpoint: Out["__state"];
|
|
108
|
+
click: string | ((mem: MemPage) => string);
|
|
109
|
+
requires?: readonly MemKey<any>[];
|
|
110
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
111
|
+
stubBefore?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
112
|
+
stubAfter?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
113
|
+
stubOnError?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
114
|
+
/** @deprecated Prefer stubAfter. */
|
|
115
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
116
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
117
|
+
};
|
|
118
|
+
export type NavClickBlock<Out extends Checkpoint<string>> = NavBlock<Out>;
|
|
119
|
+
/** @see {@link NavClickBlockOptions} */
|
|
120
|
+
export declare function defineNavClickBlock<Out extends Checkpoint<string>>(options: NavClickBlockOptions<Out>): NavClickBlock<Out>;
|
|
121
|
+
/**
|
|
122
|
+
* TypeScript salt over {@link defineNavBlock}: mem-picked destination
|
|
123
|
+
* (which product row / which request id). Still a NavBlock / Block at runtime.
|
|
124
|
+
*/
|
|
125
|
+
export type MemNavBlock<Out extends Checkpoint<string>> = NavBlock<Out> & {
|
|
126
|
+
requires: readonly MemKey<any>[];
|
|
127
|
+
instanceOptions: NonNullable<Block<Checkpoint<string>, Out>["instanceOptions"]>;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* {@link defineNavBlock} constrained to mem-picked navigation: requires +
|
|
131
|
+
* instanceOptions mandatory, and `click`/`url` should depend on that mem
|
|
132
|
+
* (typically `click: (mem) => …`). Prefer this when the Block opens one of
|
|
133
|
+
* several live rows/items rather than a fixed nav target. Still a Block.
|
|
134
|
+
*/
|
|
135
|
+
export declare function defineMemNavBlock<Out extends Checkpoint<string>>(options: NavBlockOptions<Out> & {
|
|
136
|
+
requires: readonly MemKey<any>[];
|
|
137
|
+
instanceOptions: NonNullable<Block<Checkpoint<string>, Out>["instanceOptions"]>;
|
|
138
|
+
}): MemNavBlock<Out>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Split out of the former 2,900-line engine.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
|
+
import { checkpoint } from "../../types.js";
|
|
3
|
+
import { defineBlock } from "../core.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds a {@link NavBlock}. The generated `act()` is always exactly
|
|
6
|
+
* `page.goto(url)` or `page.locator(click).click()` - never anything else,
|
|
7
|
+
* never author-supplied. Callers get a real `Page` here internally (the one
|
|
8
|
+
* legitimate place navigation belongs); `defineBlock`'s own `ActionPage`
|
|
9
|
+
* narrowing is what discourages navigation everywhere else, and never
|
|
10
|
+
* applies to this generated function since no author ever writes it by
|
|
11
|
+
* hand.
|
|
12
|
+
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
13
|
+
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
14
|
+
* @example defineNavClickBlock({ name: "nav-web-documents", checkpoint: "DocumentsList", click: "nav >> text=Documents" })
|
|
15
|
+
* For click-nav in app code prefer {@link defineNavClickBlock} (url stays here).
|
|
16
|
+
*/
|
|
17
|
+
export function defineNavBlock(options) {
|
|
18
|
+
const built = defineBlock({
|
|
19
|
+
name: options.name,
|
|
20
|
+
...(options.description ? { description: options.description } : {}),
|
|
21
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
22
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
23
|
+
instruction: {
|
|
24
|
+
async act(page, _input, mem) {
|
|
25
|
+
if (options.url !== undefined) {
|
|
26
|
+
const url = typeof options.url === "function" ? options.url(mem) : options.url;
|
|
27
|
+
await page.goto(url);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Always Locator.click() so step-mode's demo cursor/pulse patch
|
|
31
|
+
// (instrumentInteractionHighlighting) sees NavBlock click-nav -
|
|
32
|
+
// never page.click() / goto teleport.
|
|
33
|
+
const selector = typeof options.click === "function" ? options.click(mem) : options.click;
|
|
34
|
+
await page.locator(selector).click();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
38
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
39
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
40
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
41
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
42
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
// Non-enumerable so it never shows up in Object.keys/JSON/autocomplete -
|
|
46
|
+
// purely a runtime marker `waygraph check` (or anything else walking a
|
|
47
|
+
// project's Blocks) can test for to tell a NavBlock apart from a regular
|
|
48
|
+
// one, since the TypeScript type alone (NavBlock = DefinedBlock) doesn't
|
|
49
|
+
// survive to runtime.
|
|
50
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
51
|
+
value: "nav",
|
|
52
|
+
enumerable: false,
|
|
53
|
+
configurable: false,
|
|
54
|
+
});
|
|
55
|
+
// Demo/step tooling: remember the click selector (or factory) so
|
|
56
|
+
// overlays can label "nav click" without re-parsing source.
|
|
57
|
+
if (options.click !== undefined) {
|
|
58
|
+
Object.defineProperty(built, "__waygraphNavClick", {
|
|
59
|
+
value: options.click,
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Same idea, for the url case - only when it's a plain string (a
|
|
65
|
+
// function URL is mem-dependent/dynamic, can't be compared statically
|
|
66
|
+
// without running it). Real, direct request this responds to: an agent
|
|
67
|
+
// driving Blind Pilot had no structured way to ask "which of the links
|
|
68
|
+
// on THIS page are already covered by a NavBlock" without re-parsing
|
|
69
|
+
// every nav.block.ts's own source - this is what `AutoSession` now reads
|
|
70
|
+
// to warn about real, unmapped nav links found live on the page.
|
|
71
|
+
if (typeof options.url === "string") {
|
|
72
|
+
Object.defineProperty(built, "__waygraphNavUrl", {
|
|
73
|
+
value: options.url,
|
|
74
|
+
enumerable: false,
|
|
75
|
+
configurable: false,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return built;
|
|
79
|
+
}
|
|
80
|
+
/** @see {@link NavClickBlockOptions} */
|
|
81
|
+
export function defineNavClickBlock(options) {
|
|
82
|
+
return defineNavBlock({
|
|
83
|
+
name: options.name,
|
|
84
|
+
checkpoint: options.checkpoint,
|
|
85
|
+
click: options.click,
|
|
86
|
+
...(options.description ? { description: options.description } : {}),
|
|
87
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
88
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
89
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
90
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
91
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
92
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
93
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* {@link defineNavBlock} constrained to mem-picked navigation: requires +
|
|
98
|
+
* instanceOptions mandatory, and `click`/`url` should depend on that mem
|
|
99
|
+
* (typically `click: (mem) => …`). Prefer this when the Block opens one of
|
|
100
|
+
* several live rows/items rather than a fixed nav target. Still a Block.
|
|
101
|
+
*/
|
|
102
|
+
export function defineMemNavBlock(options) {
|
|
103
|
+
const built = defineNavBlock(options);
|
|
104
|
+
Object.defineProperty(built, "__waygraphMemKind", {
|
|
105
|
+
value: "nav",
|
|
106
|
+
enumerable: false,
|
|
107
|
+
configurable: false,
|
|
108
|
+
});
|
|
109
|
+
return built;
|
|
110
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Block, Checkpoint, DefinedBlock, WaygraphHighlight } from "../../types.js";
|
|
2
|
+
import type { Trait } from "../../trait.js";
|
|
3
|
+
import { MemPage } from "../../mem-page.js";
|
|
4
|
+
import type { MemKey } from "../../mem-page.js";
|
|
5
|
+
/** One Block (or lazy factory) registered on a {@link PageBlock}. */
|
|
6
|
+
export type PageMethodEntry = DefinedBlock<any, any> | (() => DefinedBlock<any, any>);
|
|
7
|
+
export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Checkpoint tag for this screen (hub). */
|
|
11
|
+
checkpoint: Out["__state"];
|
|
12
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
13
|
+
stubBefore?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
14
|
+
stubAfter?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
15
|
+
stubOnError?: import("../../highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
16
|
+
/** @deprecated Prefer stubAfter. */
|
|
17
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
18
|
+
requires?: readonly MemKey<any>[];
|
|
19
|
+
/**
|
|
20
|
+
* Methods available on this page. Array, or a record of Blocks / `() => Block`
|
|
21
|
+
* factories (lazy so the page object can colocate method definitions).
|
|
22
|
+
*/
|
|
23
|
+
methods?: readonly PageMethodEntry[] | Record<string, PageMethodEntry>;
|
|
24
|
+
/**
|
|
25
|
+
* Optional deep-link onto this page (`goto`). XOR with `click`. Omit when the
|
|
26
|
+
* hub is arrival-only (previous Block already resolved here).
|
|
27
|
+
*/
|
|
28
|
+
url?: string | ((mem: MemPage) => string);
|
|
29
|
+
/** Optional click-nav onto this page. XOR with `url`. */
|
|
30
|
+
click?: string | ((mem: MemPage) => string);
|
|
31
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Page hub Block: one Checkpoint = this screen; methods hang off the hub for
|
|
35
|
+
* readability / auto grouping. Runtime is still a Block (`__waygraphKind = "page"`).
|
|
36
|
+
* `act` is goto/click when `url`/`click` is set, otherwise a no-op (already here).
|
|
37
|
+
* Child methods stay ordinary Blocks for graph edges - the page only registers them.
|
|
38
|
+
* Branded `__wgFactory: "page"` (not `"nav"`) - both are accepted by
|
|
39
|
+
* {@link MapBuilder.gotoPage} / {@link MapBuilder.gotoExternal}.
|
|
40
|
+
*/
|
|
41
|
+
export type PageBlock<Out extends Checkpoint<string>> = Omit<DefinedBlock<Checkpoint<string>, Out>, "withVerify" | "modVerify" | "modVerifyAll" | "stubBefore" | "stubAfter" | "stubOnError"> & {
|
|
42
|
+
readonly __wgFactory?: "page";
|
|
43
|
+
/** Resolved method Blocks registered at define time. */
|
|
44
|
+
readonly methods: readonly DefinedBlock<any, any>[];
|
|
45
|
+
withVerify(verify: Trait[] | ((out: Out) => Trait[])): PageBlock<Out>;
|
|
46
|
+
modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): PageBlock<Out>;
|
|
47
|
+
modVerifyAll(patches: Record<string, Trait["check"] | Trait>): PageBlock<Out>;
|
|
48
|
+
stubBefore(stub: import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
49
|
+
stubBefore(stub: import("../../highlights.js").StubLifecycleFn<Out>): PageBlock<Out>;
|
|
50
|
+
stubBefore(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
51
|
+
stubAfter(stub: import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
52
|
+
stubAfter(stub: import("../../highlights.js").StubLifecycleFn<Out>): PageBlock<Out>;
|
|
53
|
+
stubAfter(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
54
|
+
stubOnError(stub: import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
55
|
+
stubOnError(stub: import("../../highlights.js").StubLifecycleFn<Out>): PageBlock<Out>;
|
|
56
|
+
stubOnError(stub: (out: Out) => import("../../highlights.js").HighlightStubPhase): PageBlock<Out>;
|
|
57
|
+
};
|
|
58
|
+
/** @see {@link PageBlockOptions} */
|
|
59
|
+
export declare function definePageBlock<Out extends Checkpoint<string>>(options: PageBlockOptions<Out>): PageBlock<Out>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Split out of the former 2,900-line engine.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
|
+
import { checkpoint } from "../../types.js";
|
|
3
|
+
import { defineBlock } from "../core.js";
|
|
4
|
+
function resolvePageMethods(methods) {
|
|
5
|
+
if (!methods)
|
|
6
|
+
return [];
|
|
7
|
+
const entries = Array.isArray(methods) ? methods : Object.values(methods);
|
|
8
|
+
return entries.map((entry) => (typeof entry === "function" ? entry() : entry));
|
|
9
|
+
}
|
|
10
|
+
/** @see {@link PageBlockOptions} */
|
|
11
|
+
export function definePageBlock(options) {
|
|
12
|
+
if (options.url !== undefined && options.click !== undefined) {
|
|
13
|
+
throw new Error(`definePageBlock("${options.name}"): pass url XOR click (or neither for arrival-only hub)`);
|
|
14
|
+
}
|
|
15
|
+
const methodList = resolvePageMethods(options.methods);
|
|
16
|
+
const built = defineBlock({
|
|
17
|
+
name: options.name,
|
|
18
|
+
...(options.description ? { description: options.description } : {}),
|
|
19
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
20
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
21
|
+
instruction: {
|
|
22
|
+
async act(page, _input, mem) {
|
|
23
|
+
if (options.url !== undefined) {
|
|
24
|
+
const url = typeof options.url === "function" ? options.url(mem) : options.url;
|
|
25
|
+
await page.goto(url);
|
|
26
|
+
}
|
|
27
|
+
else if (options.click !== undefined) {
|
|
28
|
+
const selector = typeof options.click === "function" ? options.click(mem) : options.click;
|
|
29
|
+
await page.locator(selector).click();
|
|
30
|
+
}
|
|
31
|
+
// else: arrival-only hub — previous Block already landed here
|
|
32
|
+
},
|
|
33
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
34
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
35
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
36
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
37
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
38
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
42
|
+
value: "page",
|
|
43
|
+
enumerable: false,
|
|
44
|
+
configurable: false,
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
47
|
+
value: "page",
|
|
48
|
+
enumerable: false,
|
|
49
|
+
configurable: false,
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(built, "methods", {
|
|
52
|
+
value: Object.freeze([...methodList]),
|
|
53
|
+
enumerable: true,
|
|
54
|
+
configurable: false,
|
|
55
|
+
writable: false,
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(built, "__waygraphMethods", {
|
|
58
|
+
value: methodList.map((m) => m.name),
|
|
59
|
+
enumerable: false,
|
|
60
|
+
configurable: false,
|
|
61
|
+
});
|
|
62
|
+
// Same marker `defineNavBlock` sets, same reason - a page hub with a
|
|
63
|
+
// `url` (deep-link) is exactly as "known, coverable" as a plain
|
|
64
|
+
// `defineNavBlock`, and in practice most real projects' own nav Blocks
|
|
65
|
+
// ARE page hubs (methods hanging off them), not bare NavBlocks - the real
|
|
66
|
+
// saucedemo fixture this whole package tests against uses definePageBlock
|
|
67
|
+
// for every one of its own nav-* Blocks. Missing this here meant the
|
|
68
|
+
// marker existed but was never actually set on any real project's Blocks.
|
|
69
|
+
if (typeof options.url === "string") {
|
|
70
|
+
Object.defineProperty(built, "__waygraphNavUrl", {
|
|
71
|
+
value: options.url,
|
|
72
|
+
enumerable: false,
|
|
73
|
+
configurable: false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (options.url !== undefined || options.click !== undefined) {
|
|
77
|
+
Object.defineProperty(built, "__waygraphPageDeepLink", {
|
|
78
|
+
value: true,
|
|
79
|
+
enumerable: false,
|
|
80
|
+
configurable: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (options.click !== undefined) {
|
|
84
|
+
Object.defineProperty(built, "__waygraphNavClick", {
|
|
85
|
+
value: options.click,
|
|
86
|
+
enumerable: false,
|
|
87
|
+
configurable: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return built;
|
|
91
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { BlockInfo } from "./flow.js";
|
|
2
|
+
import type { Block, Checkpoint } from "../types.js";
|
|
3
|
+
import type { Trait } from "../trait.js";
|
|
4
|
+
/**
|
|
5
|
+
* A Block guaranteed to have `withStepVerify`/`modStepVerify` attached - what
|
|
6
|
+
* {@link composeBlock} returns. Extends plain `Block`, so it drops straight into
|
|
7
|
+
* `defineFlow([start, ..., composed, ..., end])` like any other Block; no special
|
|
8
|
+
* casing needed anywhere that only expects a `Block`.
|
|
9
|
+
*/
|
|
10
|
+
export interface ComposedBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> extends Block<In, Out> {
|
|
11
|
+
/**
|
|
12
|
+
* This composed Block's constituent steps, in order, as plain data - same
|
|
13
|
+
* shape and purpose as {@link Flow.blocks}.
|
|
14
|
+
* @example GovFormBlock.steps() // [{ name: "step-1-name" }, { name: "step-2-address" }, ...]
|
|
15
|
+
*/
|
|
16
|
+
steps(): readonly BlockInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* Replaces one step's whole verify list, wherever it sits in this composed
|
|
19
|
+
* Block - same rules as {@link withVerify}. Throws if no step matches.
|
|
20
|
+
* @example GovFormBlock.withStepVerify("step-2-address", [])
|
|
21
|
+
*/
|
|
22
|
+
withStepVerify(step: Block<any, any> | string | number, verify: Trait[] | ((out: any) => Trait[])): ComposedBlock<In, Out>;
|
|
23
|
+
/**
|
|
24
|
+
* Replaces one Trait inside one step's verify list - same rules as
|
|
25
|
+
* {@link modVerify}. Throws if no step matches, or that step has no verify
|
|
26
|
+
* trait at the given name/index.
|
|
27
|
+
* @example GovFormBlock.modStepVerify("step-2-address", "zip-visible", Trait.visible("#zip-confirmed"))
|
|
28
|
+
*/
|
|
29
|
+
modStepVerify(step: Block<any, any> | string | number, nameOrIndex: string | number, newCheck: Trait["check"] | Trait): ComposedBlock<In, Out>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Chains several Blocks into one named, exported unit - what a naturally
|
|
33
|
+
* multi-step feature (a stepper form with several pages, say) wants instead of
|
|
34
|
+
* either one giant Block with no per-step confirmation, or several separately
|
|
35
|
+
* exported Blocks the caller has to wire together by hand every time. Each
|
|
36
|
+
* step keeps its own real `act`/`resolve`/`verify`, chained the same way
|
|
37
|
+
* `connect()` already chains any two Blocks - `composeBlock` is not a new
|
|
38
|
+
* execution mechanism, just `connect()` reduced over the list plus a name and
|
|
39
|
+
* the same step-list closure `Flow` already keeps for its own Block list, so
|
|
40
|
+
* `withStepVerify`/`modStepVerify` can patch one step from outside without the
|
|
41
|
+
* caller needing to hand-rebuild the chain. Resolution is by `.name`, not
|
|
42
|
+
* object identity, so a step reference stays valid even after an earlier patch
|
|
43
|
+
* swapped that slot for a new object - see {@link Flow.withBlockVerify} for
|
|
44
|
+
* the identical guarantee one level up. Each call returns a new composed
|
|
45
|
+
* Block; the original is untouched.
|
|
46
|
+
* @example composeBlock("gov-form", [step1, step2, step3, step4])
|
|
47
|
+
*/
|
|
48
|
+
export declare function composeBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, Out>]): ComposedBlock<In, Out>;
|
|
49
|
+
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, Out>]): ComposedBlock<In, Out>;
|
|
50
|
+
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, Out>]): ComposedBlock<In, Out>;
|
|
51
|
+
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, Out>]): ComposedBlock<In, Out>;
|
|
52
|
+
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, Out>]): ComposedBlock<In, Out>;
|
|
53
|
+
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, F>, Block<F, Out>]): ComposedBlock<In, Out>;
|
|
54
|
+
/**
|
|
55
|
+
* Same shape as {@link composeBlock}, marked {@link FastForwardComposedBlock.fastForward}
|
|
56
|
+
* so demo/run/traverse treat it as **one opaque step** that **blitzes wall-clock**
|
|
57
|
+
* (skip overlay theater + no Playwright slowMo on that step's acts) unless
|
|
58
|
+
* `--ff-expand` / `WAYGRAPH_FF_EXPAND=1`. Inner acts still run for real;
|
|
59
|
+
* failures are prefixed `ff-name > …` so the locus names both the FF unit and
|
|
60
|
+
* the inner step. Prefer this for boring or heavy-check prefixes (auth, seed,
|
|
61
|
+
* dashboard settle) — you may put **several** FF units in one Flow with normal
|
|
62
|
+
* Blocks between them.
|
|
63
|
+
* @example fastForwardComposeBlock("ff-owner-auth", [NavLoginBlock, SubmitLoginForFlow])
|
|
64
|
+
*/
|
|
65
|
+
export interface FastForwardComposedBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> extends ComposedBlock<In, Out> {
|
|
66
|
+
readonly fastForward: true;
|
|
67
|
+
}
|
|
68
|
+
/** True when `block` came from {@link fastForwardComposeBlock}. */
|
|
69
|
+
export declare function isFastForwardBlock(block: Block<any, any>): block is FastForwardComposedBlock<any, any>;
|
|
70
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, Out>]): FastForwardComposedBlock<In, Out>;
|
|
71
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, Out>]): FastForwardComposedBlock<In, Out>;
|
|
72
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, Out>]): FastForwardComposedBlock<In, Out>;
|
|
73
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, Out>]): FastForwardComposedBlock<In, Out>;
|
|
74
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, Out>]): FastForwardComposedBlock<In, Out>;
|
|
75
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, F>, Block<F, Out>]): FastForwardComposedBlock<In, Out>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { connect } from "../types.js";
|
|
2
|
+
import { findBlockIndex, modVerify, withVerify } from "./core.js";
|
|
3
|
+
export function composeBlock(name, steps) {
|
|
4
|
+
if (steps.length === 0) {
|
|
5
|
+
throw new Error(`composeBlock: "${name}" needs at least one step`);
|
|
6
|
+
}
|
|
7
|
+
const chain = steps.reduce((a, b) => connect(a, b));
|
|
8
|
+
return {
|
|
9
|
+
...chain,
|
|
10
|
+
name,
|
|
11
|
+
steps: () => steps.map((b) => ({ name: b.name, block: b, ...(b.routes ? { routes: b.routes } : {}) })),
|
|
12
|
+
withStepVerify(step, verify) {
|
|
13
|
+
const index = findBlockIndex(steps, step);
|
|
14
|
+
const patched = [...steps];
|
|
15
|
+
patched[index] = withVerify(steps[index], verify);
|
|
16
|
+
return composeBlock(name, patched);
|
|
17
|
+
},
|
|
18
|
+
modStepVerify(step, nameOrIndex, newCheck) {
|
|
19
|
+
const index = findBlockIndex(steps, step);
|
|
20
|
+
const patched = [...steps];
|
|
21
|
+
patched[index] = modVerify(steps[index], nameOrIndex, newCheck);
|
|
22
|
+
return composeBlock(name, patched);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** True when `block` came from {@link fastForwardComposeBlock}. */
|
|
27
|
+
export function isFastForwardBlock(block) {
|
|
28
|
+
return (block.fastForward === true &&
|
|
29
|
+
typeof block.steps === "function");
|
|
30
|
+
}
|
|
31
|
+
function wrapFfError(ffName, err) {
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
+
if (msg.startsWith(`${ffName} > `)) {
|
|
34
|
+
return err instanceof Error ? err : new Error(msg);
|
|
35
|
+
}
|
|
36
|
+
const wrapped = new Error(`${ffName} > ${msg}`);
|
|
37
|
+
if (err instanceof Error && err.stack) {
|
|
38
|
+
wrapped.stack = err.stack;
|
|
39
|
+
}
|
|
40
|
+
return wrapped;
|
|
41
|
+
}
|
|
42
|
+
export function fastForwardComposeBlock(name, steps) {
|
|
43
|
+
if (steps.length === 0) {
|
|
44
|
+
throw new Error(`fastForwardComposeBlock: "${name}" needs at least one step`);
|
|
45
|
+
}
|
|
46
|
+
const base = composeBlock(name, steps);
|
|
47
|
+
const origAct = base.instruction.act.bind(base.instruction);
|
|
48
|
+
const ff = {
|
|
49
|
+
...base,
|
|
50
|
+
fastForward: true,
|
|
51
|
+
instruction: {
|
|
52
|
+
...base.instruction,
|
|
53
|
+
async act(page, input, mem) {
|
|
54
|
+
try {
|
|
55
|
+
await origAct(page, input, mem);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
throw wrapFfError(name, e);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
withStepVerify(step, verify) {
|
|
63
|
+
const index = findBlockIndex(steps, step);
|
|
64
|
+
const patched = [...steps];
|
|
65
|
+
patched[index] = withVerify(steps[index], verify);
|
|
66
|
+
return fastForwardComposeBlock(name, patched);
|
|
67
|
+
},
|
|
68
|
+
modStepVerify(step, nameOrIndex, newCheck) {
|
|
69
|
+
const index = findBlockIndex(steps, step);
|
|
70
|
+
const patched = [...steps];
|
|
71
|
+
patched[index] = modVerify(steps[index], nameOrIndex, newCheck);
|
|
72
|
+
return fastForwardComposeBlock(name, patched);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
return ff;
|
|
76
|
+
}
|