waygraph 0.16.1 → 0.16.2
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/commands/codemod-stubs.js +90 -2
- package/dist/cli/run-chain.js +2 -2
- package/dist/cli/util.js +1 -1
- package/dist/demo/narration/demo-pace.d.ts +1 -1
- package/dist/demo/narration/demo-pace.js +1 -1
- package/dist/demo/narration/merge.d.ts +1 -1
- package/dist/demo/narration/merge.js +1 -1
- package/dist/demo/narration/narration.d.ts +2 -6
- package/dist/demo/narration/narration.js +5 -17
- package/dist/demo/narration/stub-ctx.js +3 -3
- package/dist/demo/overlay/inpage/core.js +0 -6
- package/dist/demo/overlay/overlay-css.js +1 -55
- package/dist/demo/overlay/overlay.js +3 -2
- package/dist/demo/overlay/rings.d.ts +0 -8
- package/dist/demo/overlay/rings.js +0 -15
- package/dist/demo/overlay/ui/overlay.css +40 -0
- package/dist/demo/stepper/after.js +4 -17
- package/dist/demo/stepper/before.js +3 -1
- package/dist/demo/stepper/demo-log.js +1 -1
- package/dist/demo/stepper/interaction-patch.js +3 -15
- package/dist/demo/stepper/panel-after.js +1 -1
- package/dist/demo/stepper/slides.d.ts +3 -0
- package/dist/demo/stepper/slides.js +18 -2
- package/dist/demo/stepper/teardown.js +0 -1
- package/dist/demo/verbose-log.js +1 -1
- package/dist/engine/block/block.d.ts +1 -12
- package/dist/engine/block/kinds/method.d.ts +0 -2
- package/dist/engine/block/kinds/method.js +0 -1
- package/dist/engine/block/kinds/nav.js +1 -1
- package/dist/engine/run/run.d.ts +1 -19
- package/dist/engine/run/run.js +0 -88
- package/dist/engine/stub/ring.d.ts +2 -4
- package/dist/engine/stub/ring.js +1 -0
- package/dist/engine/stub/stub.d.ts +16 -8
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -2
- package/dist/runner/plan/find.js +2 -2
- package/dist/runner/plan/plan.js +1 -1
- package/dist/runner/plan/seed-mem.js +5 -5
- package/dist/runner/plan/spec.js +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/examples/saucedemo/src/flows/checkout.flow.ts +2 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +2 -1
- package/examples/saucedemo/src/stubs/device-showcase.ts +37 -43
- package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/templates/quickstart/src/flows/checkout.flow.ts +2 -1
- package/templates/quickstart/src/flows/shop.flow.ts +2 -1
- package/templates/quickstart/src/stubs/device-showcase.ts +37 -43
- package/templates/quickstart/src/stubs/finish-order.ts +38 -0
- package/templates/scaffold/src/flows/home/hello.flow.ts +3 -0
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeDemoPace, resolveFixtureDwellMs } from "../narration/demo-pace.js";
|
|
2
|
-
import { formatHighlightCaption } from "../narration/style.js";
|
|
3
2
|
import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "../../engine/stub/ring.js";
|
|
4
3
|
import { installOverlay } from "../overlay/overlay.js";
|
|
5
4
|
import { applyHighlightZoom, ensureSelectorInView, hideRing, showRing } from "../overlay/rings.js";
|
|
6
|
-
import { demoPaceKind } from "./demo-log.js";
|
|
5
|
+
import { demoLog, demoPaceKind } from "./demo-log.js";
|
|
7
6
|
import { envFlag, envNumber, envString } from "../../engine/env.js";
|
|
7
|
+
import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
|
|
8
8
|
/**
|
|
9
9
|
* Multi-step yap captions - not block lifecycle. Each slide waits for Next
|
|
10
10
|
* (or auto-next), optional ring via slide.selector.
|
|
@@ -231,3 +231,19 @@ export async function presentSlides(page, slides, opts) {
|
|
|
231
231
|
}
|
|
232
232
|
await hideRing(page);
|
|
233
233
|
}
|
|
234
|
+
/** Plays a stub phase's `ctx.slides(...)` for the step on screen, in the flow's highlight style. */
|
|
235
|
+
export async function showSlides(ctx, view, slides) {
|
|
236
|
+
if (slides.length === 0)
|
|
237
|
+
return;
|
|
238
|
+
if (!envFlag("WAYGRAPH_JSON"))
|
|
239
|
+
demoLog(` slides ${slides.length}: ` + slides.map((s) => JSON.stringify(s.caption)).join(" | "));
|
|
240
|
+
await presentSlides(ctx.page, slides.map((s) => applyHighlightStyleDefaults(s, view.step.highlightStyle)), {
|
|
241
|
+
title: ctx.title,
|
|
242
|
+
blockName: view.step.block.name,
|
|
243
|
+
fast: ctx.pacing.gatesFast,
|
|
244
|
+
pace: ctx.pacing.demoPace,
|
|
245
|
+
episodeNumber: view.step.episodeNumber,
|
|
246
|
+
episodeTitle: view.step.episodeTitle,
|
|
247
|
+
autoplayMs: ctx.gate.autoplayMs,
|
|
248
|
+
});
|
|
249
|
+
}
|
package/dist/demo/verbose-log.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { envFlag } from "../engine/env.js";
|
|
2
2
|
/**
|
|
3
3
|
* One log format for the live overlay surfaces (auto / browser / pilot): `[HH:MM:SS.mmm] waygraph <surface>: ...`, the
|
|
4
|
-
* same shape as the demo
|
|
4
|
+
* same shape as the demo stepper's `stepper/demo-log.ts`. Silenced by WAYGRAPH_JSON=1.
|
|
5
5
|
*/
|
|
6
6
|
function ts() {
|
|
7
7
|
const d = new Date();
|
|
@@ -2,7 +2,6 @@ import type { Page } from "@playwright/test";
|
|
|
2
2
|
import type { Checkpoint } from "../checkpoint/checkpoint.js";
|
|
3
3
|
import type { MemKey } from "../mem/mem.js";
|
|
4
4
|
import { MemPage } from "../mem/mem.js";
|
|
5
|
-
import type { WaygraphSlidesOrFn } from "../stub/ring.js";
|
|
6
5
|
import type { StubLifecycleFn } from "../stub/stub.js";
|
|
7
6
|
import type { Trait } from "../trait/trait.js";
|
|
8
7
|
import type { ActionPage, WaygraphInstanceOption } from "./action-page.js";
|
|
@@ -56,16 +55,6 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
|
|
|
56
55
|
stubAfter?: StubLifecycleFn<Out>;
|
|
57
56
|
/** When the step fails, before the failure panel; written by `Block.stubOnError((ctx) => ...)`. */
|
|
58
57
|
stubOnError?: StubLifecycleFn<Out>;
|
|
59
|
-
/**
|
|
60
|
-
* Multi-step demo captions ("yap" slides) with Next between each -
|
|
61
|
-
* not block lifecycle. Explains a long process; optional `selector` rings
|
|
62
|
-
* while that slide is up. Flow fixtures may override via `slides`.
|
|
63
|
-
* @example slides: [
|
|
64
|
-
* { caption: "Checkout is three screens", tag: "YAP" },
|
|
65
|
-
* { caption: "Overview confirms totals", selector: ".summary_info" },
|
|
66
|
-
* ]
|
|
67
|
-
*/
|
|
68
|
-
slides?: WaygraphSlidesOrFn<Out>;
|
|
69
58
|
}
|
|
70
59
|
/**
|
|
71
60
|
* A Block wraps one instruction. Its Observed type is an internal detail, erased here.
|
|
@@ -90,7 +79,7 @@ export interface Block<In extends Checkpoint<string>, Out extends Checkpoint<str
|
|
|
90
79
|
* A human-readable sentence describing what this Block actually tests or
|
|
91
80
|
* does - "the outsider case: a user outside a request's audience is
|
|
92
81
|
* denied its detail page," not a restatement of the code. Purely
|
|
93
|
-
* additive, optional narration for a human watching a run (
|
|
82
|
+
* additive, optional narration for a human watching a run (demo
|
|
94
83
|
* tooling shows it before the Block runs) - never read by
|
|
95
84
|
* `runGraph`/`connect`/anything that decides behavior. A Block with none
|
|
96
85
|
* just shows its `name` instead.
|
|
@@ -39,8 +39,6 @@ export interface AssertBlockOptions<Out extends Checkpoint<string> = Checkpoint<
|
|
|
39
39
|
verify: Trait[] | ((out: Out) => Trait[]);
|
|
40
40
|
/** Mem keys this assertion's `verify` reads (e.g. a mem-aware Trait) - checked by preflight before the flow runs. */
|
|
41
41
|
requires?: readonly MemKey<unknown>[];
|
|
42
|
-
/** Multi-step yap slides (demo only) - same field `Instruction.slides` exposes elsewhere. */
|
|
43
|
-
slides?: import("../../stub/ring.js").WaygraphSlidesOrFn<Out>;
|
|
44
42
|
}
|
|
45
43
|
/**
|
|
46
44
|
* Sugar for a self-loop-only "pure assertion" Block: no hand-written
|
|
@@ -28,7 +28,7 @@ export function defineNavBlock(options) {
|
|
|
28
28
|
await page.goto(url);
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
|
-
// Always Locator.click() so
|
|
31
|
+
// Always Locator.click() so the demo stepper's cursor/pulse patch
|
|
32
32
|
// (instrumentInteractionHighlighting) sees NavBlock click-nav -
|
|
33
33
|
// never page.click() / goto teleport.
|
|
34
34
|
const selector = typeof options.click === "function" ? options.click(mem) : options.click;
|
package/dist/engine/run/run.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { MemPage } from "../mem/mem.js";
|
|
|
2
2
|
import type { Block } from "../block/block.js";
|
|
3
3
|
import type { Checkpoint } from "../checkpoint/checkpoint.js";
|
|
4
4
|
import type { Trait } from "../trait/trait.js";
|
|
5
|
-
import type { BrowserContext,
|
|
5
|
+
import type { BrowserContext, Page } from "@playwright/test";
|
|
6
6
|
import type { SomeBlock } from "../block/block.js";
|
|
7
7
|
/**
|
|
8
8
|
* Checks `block.requires` against `mem` and throws, naming every missing key at
|
|
@@ -125,21 +125,3 @@ export interface RunGraphOptions {
|
|
|
125
125
|
* }
|
|
126
126
|
*/
|
|
127
127
|
export declare function spawnTab<TOut extends Checkpoint<string>>(entry: Block<Checkpoint<"__start__">, TOut>, page: Page, mem: MemPage, maxSteps?: number): Promise<TOut>;
|
|
128
|
-
/**
|
|
129
|
-
* Wraps one `act()` interaction with a caption and highlight, for a Block
|
|
130
|
-
* author who wants to explicitly narrate a specific step rather than rely
|
|
131
|
-
* on the generic per-fill/per-click narration `waygraph chain
|
|
132
|
-
* WAYGRAPH_STEP=1` already does automatically -
|
|
133
|
-
* `await narrate(submitButton, "confirms the order", () => submitButton.click())`
|
|
134
|
-
* reads at the call site instead of being inferred.
|
|
135
|
-
*
|
|
136
|
-
* A true no-op everywhere except inside a `WAYGRAPH_STEP=1` run: it checks
|
|
137
|
-
* for a `window.__wgPositionRing` hook the step-mode overlay installs on
|
|
138
|
-
* the page before doing anything, so a headless/CI/automated run (nobody
|
|
139
|
-
* watching, per-character delays and highlight pauses would just be wasted
|
|
140
|
-
* time) pays zero extra cost - not even the boundingBox() lookup runs. The
|
|
141
|
-
* `action` itself always runs regardless; only the narration is
|
|
142
|
-
* conditional.
|
|
143
|
-
* @example await narrate(submitButton, "confirms the order", () => submitButton.click())
|
|
144
|
-
*/
|
|
145
|
-
export declare function narrate<T>(locator: Locator, caption: string, action: () => Promise<T>): Promise<T>;
|
package/dist/engine/run/run.js
CHANGED
|
@@ -2,7 +2,6 @@ import { WaygraphError, MSG_PREFLIGHT_MISSING_KEY, MSG_RUNGRAPH_MAX_STEPS, MSG_R
|
|
|
2
2
|
import { checkpoint } from "../checkpoint/checkpoint.js";
|
|
3
3
|
import { beginRun, endRun, isConnected, lastLeaf, stepEnded, stepFailed, stepStarted } from "./events.js";
|
|
4
4
|
import { runPrecondition, runVerify } from "../trait/trait.js";
|
|
5
|
-
import { ignorePageGone } from "../page-gone.js";
|
|
6
5
|
/**
|
|
7
6
|
* Checks `block.requires` against `mem` and throws, naming every missing key at
|
|
8
7
|
* once, if any are unset. `connect()` unions each Block's `requires` into the
|
|
@@ -144,90 +143,3 @@ export async function runGraph(entry, terminals, context, mem, maxSteps = 5000,
|
|
|
144
143
|
export async function spawnTab(entry, page, mem, maxSteps = 5000) {
|
|
145
144
|
return runGraph(entry, undefined, page.context(), mem, maxSteps);
|
|
146
145
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Wraps one `act()` interaction with a caption and highlight, for a Block
|
|
149
|
-
* author who wants to explicitly narrate a specific step rather than rely
|
|
150
|
-
* on the generic per-fill/per-click narration `waygraph chain
|
|
151
|
-
* WAYGRAPH_STEP=1` already does automatically -
|
|
152
|
-
* `await narrate(submitButton, "confirms the order", () => submitButton.click())`
|
|
153
|
-
* reads at the call site instead of being inferred.
|
|
154
|
-
*
|
|
155
|
-
* A true no-op everywhere except inside a `WAYGRAPH_STEP=1` run: it checks
|
|
156
|
-
* for a `window.__wgPositionRing` hook the step-mode overlay installs on
|
|
157
|
-
* the page before doing anything, so a headless/CI/automated run (nobody
|
|
158
|
-
* watching, per-character delays and highlight pauses would just be wasted
|
|
159
|
-
* time) pays zero extra cost - not even the boundingBox() lookup runs. The
|
|
160
|
-
* `action` itself always runs regardless; only the narration is
|
|
161
|
-
* conditional.
|
|
162
|
-
* @example await narrate(submitButton, "confirms the order", () => submitButton.click())
|
|
163
|
-
*/
|
|
164
|
-
export async function narrate(locator, caption, action) {
|
|
165
|
-
const page = locator.page();
|
|
166
|
-
const active = await page
|
|
167
|
-
.evaluate(() => typeof globalThis.__wgPositionRing === "function")
|
|
168
|
-
.catch(() => false);
|
|
169
|
-
if (!active)
|
|
170
|
-
return action();
|
|
171
|
-
let owning = false;
|
|
172
|
-
try {
|
|
173
|
-
const box = await locator.boundingBox();
|
|
174
|
-
if (box) {
|
|
175
|
-
await page
|
|
176
|
-
.evaluate(({ box, caption }) => {
|
|
177
|
-
const w = globalThis;
|
|
178
|
-
if (w.__wgPositionRing)
|
|
179
|
-
w.__wgPositionRing(box, caption, "planned");
|
|
180
|
-
// The CLI's own automatic per-fill/per-click narration checks
|
|
181
|
-
// this before showing (and overwriting) its own guess - an
|
|
182
|
-
// explicitly authored caption should win, not get immediately
|
|
183
|
-
// replaced a moment later by the generic one.
|
|
184
|
-
w.__wgLastNarrate = Date.now();
|
|
185
|
-
// Also claim the ring itself: the auto-highlight click/fill
|
|
186
|
-
// patches each hide the ring right after THEIR own step
|
|
187
|
-
// finishes, which - if action() below does more than one thing
|
|
188
|
-
// (e.g. a click then a wait for a toast) - would wipe this
|
|
189
|
-
// caption after just the first sub-step instead of the whole
|
|
190
|
-
// narrated action. Their hideRing() calls no-op while this is
|
|
191
|
-
// true; narrate() below is the one that actually clears it.
|
|
192
|
-
w.__wgNarrateOwnsRing = true;
|
|
193
|
-
}, { box, caption })
|
|
194
|
-
.catch(ignorePageGone);
|
|
195
|
-
owning = true;
|
|
196
|
-
// The ring alone is pointless if action() fires on the very next
|
|
197
|
-
// tick - a click that navigates (e.g. a form submit) wipes the ring
|
|
198
|
-
// before a human can read the caption at all. Give it the same
|
|
199
|
-
// "pop for a few seconds" dwell as the CLI's own auto-highlight
|
|
200
|
-
// clicks before the real action runs.
|
|
201
|
-
await new Promise((resolve) => setTimeout(resolve, 650));
|
|
202
|
-
// Re-stamp __wgLastNarrate right before handing off to action() - the
|
|
203
|
-
// CLI's auto-highlight patches only treat a narrate() as "just
|
|
204
|
-
// handled" within a short window (500ms), which the dwell above
|
|
205
|
-
// already burned through. Without this, action()'s own click/fill
|
|
206
|
-
// (same patched Locator prototype) would stomp this caption with its
|
|
207
|
-
// own generic one a moment after it finally became visible.
|
|
208
|
-
await page
|
|
209
|
-
.evaluate(() => {
|
|
210
|
-
globalThis.__wgLastNarrate = Date.now();
|
|
211
|
-
})
|
|
212
|
-
.catch(ignorePageGone);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
// best-effort - the real action below still runs either way
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
return await action();
|
|
220
|
-
}
|
|
221
|
-
finally {
|
|
222
|
-
if (owning) {
|
|
223
|
-
await page
|
|
224
|
-
.evaluate(() => {
|
|
225
|
-
const w = globalThis;
|
|
226
|
-
w.__wgNarrateOwnsRing = false;
|
|
227
|
-
if (w.__wgHideRing)
|
|
228
|
-
w.__wgHideRing();
|
|
229
|
-
})
|
|
230
|
-
.catch(ignorePageGone);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Checkpoint } from "../checkpoint/checkpoint.js";
|
|
2
1
|
/** Shared dwell fields on stubs, fixtures, and yap slides. */
|
|
3
2
|
export type FixtureDurationFields = {
|
|
4
3
|
/**
|
|
@@ -83,8 +82,8 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
83
82
|
/** A static stub phase: named ring slots to paint before or after a step. */
|
|
84
83
|
export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
|
|
85
84
|
/**
|
|
86
|
-
* One yap/slide in a multi-step demo caption sequence.
|
|
87
|
-
*
|
|
85
|
+
* One yap/slide in a multi-step demo caption sequence, painted with {@link StubCtx.slides}.
|
|
86
|
+
* The demo pauses on each (Next, or auto-next) before moving on.
|
|
88
87
|
*/
|
|
89
88
|
export interface WaygraphSlide extends FixtureDurationFields {
|
|
90
89
|
/** Primary caption (required). */
|
|
@@ -119,7 +118,6 @@ export type HighlightStyleDefaults = {
|
|
|
119
118
|
size?: HighlightSize;
|
|
120
119
|
weight?: HighlightWeight;
|
|
121
120
|
};
|
|
122
|
-
export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
|
|
123
121
|
/** A ring or caption after its defaults and overrides are merged, ready to paint. */
|
|
124
122
|
export type ResolvedHighlight = WaygraphHighlightStub;
|
|
125
123
|
/** Map author aliases (`error` -> danger) to a canonical tone. */
|
package/dist/engine/stub/ring.js
CHANGED
|
@@ -2,7 +2,7 @@ import type { Checkpoint } from "../checkpoint/checkpoint.js";
|
|
|
2
2
|
import type { MemPage } from "../mem/mem.js";
|
|
3
3
|
import type { TodoDockState, TodoDockUiOpts, TodoListStyle, WaygraphTodoGroupInput, WaygraphTodoInput, WaygraphTodoItem, WaygraphTodosOpts } from "./todos.js";
|
|
4
4
|
import type { DeviceOrientation, DevicePreset, DeviceState, DeviceViewport } from "./viewport.js";
|
|
5
|
-
import type { HighlightStubPhase, ResolvedHighlight, WaygraphHighlightStub } from "./ring.js";
|
|
5
|
+
import type { HighlightStubPhase, ResolvedHighlight, WaygraphHighlightStub, WaygraphSlide } from "./ring.js";
|
|
6
6
|
/** Where the top banner sits. */
|
|
7
7
|
export type BannerPos = "left" | "center" | "right";
|
|
8
8
|
/** Banner UX, mirrors {@link TodoDockUiOpts}: pass `false` fields to opt out for this phase. */
|
|
@@ -87,10 +87,8 @@ export type StubPhaseFixtures = {
|
|
|
87
87
|
* LoginBlock.stubBefore((ctx) => {
|
|
88
88
|
* ctx.todos("sign-in", ["Enter email", "Enter password", "Click Sign in"], { index: 0 });
|
|
89
89
|
* ctx.zoom(1.35);
|
|
90
|
-
* ctx.
|
|
91
|
-
*
|
|
92
|
-
* submit: { selector: "#login-button", label: "Sign in" },
|
|
93
|
-
* });
|
|
90
|
+
* ctx.ring("email", { selector: "#email", label: "Email" });
|
|
91
|
+
* ctx.ring("submit", { selector: "#login-button", label: "Sign in" });
|
|
94
92
|
* })
|
|
95
93
|
* // Sequential plan: bump todoIndex in a later phase / a later Block's stubBefore
|
|
96
94
|
* .stubAfter((ctx) => {
|
|
@@ -109,9 +107,7 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
109
107
|
readonly out: Out | undefined;
|
|
110
108
|
/** Present on stubOnError when the step threw. */
|
|
111
109
|
readonly error: unknown | undefined;
|
|
112
|
-
/**
|
|
113
|
-
highlights(slots: HighlightStubPhase): void;
|
|
114
|
-
/** Set / overwrite one named ring. */
|
|
110
|
+
/** Paints one named ring this phase; the same id again replaces it. */
|
|
115
111
|
ring(id: string, stub: WaygraphHighlightStub): void;
|
|
116
112
|
/**
|
|
117
113
|
* Floating checklist: `ctx.todos("login", ["Enter email", "Enter password"], { title: "Sign in", index: 0 })`.
|
|
@@ -174,6 +170,15 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
174
170
|
caption(text: string): void;
|
|
175
171
|
/** Clears the caption (fades it out). */
|
|
176
172
|
hideCaption(): void;
|
|
173
|
+
/**
|
|
174
|
+
* Multi-step "yap" captions with Next between each, for explaining a longer process. In `stubBefore` they play
|
|
175
|
+
* before the step runs, in `stubAfter` after it lands; a slide with a `selector` rings it while it is up.
|
|
176
|
+
* @example ctx.slides([
|
|
177
|
+
* { caption: "Checkout is three screens", tag: "YAP" },
|
|
178
|
+
* { caption: "Overview confirms totals", selector: ".summary_info", duration: true },
|
|
179
|
+
* ]);
|
|
180
|
+
*/
|
|
181
|
+
slides(slides: readonly WaygraphSlide[]): void;
|
|
177
182
|
/** Shorthand for `bannerUi({ hidden: true })`. */
|
|
178
183
|
hideBanner(): void;
|
|
179
184
|
/**
|
|
@@ -226,6 +231,8 @@ export type StubPhaseResult = {
|
|
|
226
231
|
bannerUi?: BannerUiOpts;
|
|
227
232
|
caption?: string;
|
|
228
233
|
networkTabVisible?: boolean;
|
|
234
|
+
/** Slides to play in this phase ({@link StubCtx.slides}); empty when the stub set none. */
|
|
235
|
+
slides: WaygraphSlide[];
|
|
229
236
|
todoPos?: "left" | "right";
|
|
230
237
|
/** When true, keep other docks (multi-todo). Default false = replace. */
|
|
231
238
|
todoParallel?: boolean;
|
|
@@ -260,6 +267,7 @@ export type StubBagState = {
|
|
|
260
267
|
bannerUi?: BannerUiOpts | undefined;
|
|
261
268
|
caption?: string | undefined;
|
|
262
269
|
networkTabVisible?: boolean | undefined;
|
|
270
|
+
slides?: readonly WaygraphSlide[] | undefined;
|
|
263
271
|
todoPos?: "left" | "right" | undefined;
|
|
264
272
|
/** Author patched todo-dock UX this phase. */
|
|
265
273
|
todoDockUi?: TodoDockUiOpts | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { css, byText, byRole } from "./engine/trait/selector.js";
|
|
|
11
11
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./engine/errors.js";
|
|
12
12
|
export type { WaygraphErrorCode } from "./engine/errors.js";
|
|
13
13
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText } from "./engine/trait/trait.js";
|
|
14
|
-
export { runGraph, spawnTab,
|
|
14
|
+
export { runGraph, spawnTab, preflight, defineLayout } from "./engine/run/run.js";
|
|
15
15
|
export { Engine, map } from "./engine/engine.js";
|
|
16
16
|
export { branch } from "./engine/block/branch.js";
|
|
17
17
|
export { withVerify, modVerify, modVerifyAll, defineBlock } from "./engine/block/block.js";
|
|
@@ -29,7 +29,7 @@ export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from ".
|
|
|
29
29
|
export type { ApiBlockOptions, ApiCallResult } from "./engine/block/kinds/api.js";
|
|
30
30
|
export type { MapStepOpts } from "./engine/flow/map-steps.js";
|
|
31
31
|
export type { MapFlowOpts } from "./engine/flow/map-builder.js";
|
|
32
|
-
export { resolveHighlightSlots,
|
|
32
|
+
export { resolveHighlightSlots, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
|
|
33
33
|
export { resolveFixtureDwellMs, normalizeDemoPace, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT } from "./demo/narration/demo-pace.js";
|
|
34
34
|
export { formatHighlightCaption, applyHighlightStyleDefaults, toneIconPrefix } from "./demo/narration/style.js";
|
|
35
35
|
export { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight } from "./engine/stub/ring.js";
|
|
@@ -79,7 +79,6 @@ export { resolveExtension, KNOWN_EXTENSIONS } from "./runtime/extensions.js";
|
|
|
79
79
|
export type { ExtensionSpec } from "./engine/config.js";
|
|
80
80
|
export type { WebServerConfig } from "./runtime/webserver.js";
|
|
81
81
|
export type { MapStart } from "./engine/engine.js";
|
|
82
|
-
export type { WaygraphSlidesOrFn } from "./engine/stub/ring.js";
|
|
83
82
|
export type { ConnectLayout } from "./engine/run/connect.js";
|
|
84
83
|
export type { EffectExtras } from "./engine/block/kinds/effect.js";
|
|
85
84
|
export type { BlockBase, RoutedBlockBase } from "./engine/checkpoint/route.js";
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { registerMemStub, seedMemStub, getMemStub } from "./engine/mem/mem-stub.
|
|
|
9
9
|
export { css, byText, byRole } from "./engine/trait/selector.js";
|
|
10
10
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./engine/errors.js";
|
|
11
11
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText } from "./engine/trait/trait.js";
|
|
12
|
-
export { runGraph, spawnTab,
|
|
12
|
+
export { runGraph, spawnTab, preflight, defineLayout } from "./engine/run/run.js";
|
|
13
13
|
export { Engine, map } from "./engine/engine.js";
|
|
14
14
|
export { branch } from "./engine/block/branch.js";
|
|
15
15
|
export { withVerify, modVerify, modVerifyAll, defineBlock } from "./engine/block/block.js";
|
|
@@ -23,7 +23,7 @@ export { locate } from "./engine/locate.js";
|
|
|
23
23
|
export { composeBlock, fastForwardComposeBlock, isFastForwardBlock } from "./engine/block/compose.js";
|
|
24
24
|
export { MapBuilder } from "./engine/flow/map-builder.js";
|
|
25
25
|
export { branchRoutes, collectBranchFlows, runBranchRegression } from "./engine/flow/branch-regression.js";
|
|
26
|
-
export { resolveHighlightSlots,
|
|
26
|
+
export { resolveHighlightSlots, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
|
|
27
27
|
export { resolveFixtureDwellMs, normalizeDemoPace, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT } from "./demo/narration/demo-pace.js";
|
|
28
28
|
export { formatHighlightCaption, applyHighlightStyleDefaults, toneIconPrefix } from "./demo/narration/style.js";
|
|
29
29
|
export { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight } from "./engine/stub/ring.js";
|
package/dist/runner/plan/find.js
CHANGED
|
@@ -32,11 +32,11 @@ export async function findBlock(projectDir, ref) {
|
|
|
32
32
|
if (byRuntimeName.length === 1)
|
|
33
33
|
return byRuntimeName[0];
|
|
34
34
|
if (byRuntimeName.length > 1) {
|
|
35
|
-
throw new Error("waygraph
|
|
35
|
+
throw new Error("waygraph run --blocks: \"" + ref + "\" matches " + byRuntimeName.length + " Blocks by name - " +
|
|
36
36
|
byRuntimeName.map((b) => b.exportName + " (" + b.file + ")").join(", ") +
|
|
37
37
|
". Use the export name to disambiguate.");
|
|
38
38
|
}
|
|
39
|
-
throw new Error("waygraph
|
|
39
|
+
throw new Error("waygraph run --blocks: no Block named \"" + ref + "\" found under " + projectDir);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Finds an already-defined Flow by its export name (e.g. loginFlow) among
|
package/dist/runner/plan/plan.js
CHANGED
|
@@ -24,7 +24,7 @@ async function findBareFlow(projectDir, spec) {
|
|
|
24
24
|
function parseSegments(spec) {
|
|
25
25
|
const segments = parseChainSpec(spec);
|
|
26
26
|
if (segments.length === 0)
|
|
27
|
-
throw new Error("waygraph
|
|
27
|
+
throw new Error("waygraph run --blocks: empty spec - give at least one block name");
|
|
28
28
|
return segments;
|
|
29
29
|
}
|
|
30
30
|
async function resolveSegment(projectDir, seg, mem, options) {
|
|
@@ -4,7 +4,7 @@ const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v)
|
|
|
4
4
|
function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
5
5
|
if (requires.length === 0) {
|
|
6
6
|
if (json !== undefined) {
|
|
7
|
-
throw new Error("waygraph
|
|
7
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" takes no input (empty requires) but got a payload: " + json);
|
|
8
8
|
}
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
@@ -22,7 +22,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
22
22
|
haveParsed = true;
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
25
|
-
throw new Error("waygraph
|
|
25
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" payload is not valid JSON - " + String(err));
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if (!haveParsed) {
|
|
@@ -30,7 +30,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
30
30
|
// this flow; when it is, fall through with an empty object so the
|
|
31
31
|
// per-key loop below gets a chance to fill each one from the registry.
|
|
32
32
|
if (!stubActive) {
|
|
33
|
-
throw new Error("waygraph
|
|
33
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.map((k) => k.name).join(", ") +
|
|
34
34
|
" - give a JSON payload (inline blockName({...}) or --data '{...}'), or enable --mem-stub" +
|
|
35
35
|
" with registerMemStub for these keys");
|
|
36
36
|
}
|
|
@@ -54,7 +54,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
if (!isRecord(parsed)) {
|
|
57
|
-
throw new Error("waygraph
|
|
57
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.length + " keys (" +
|
|
58
58
|
requires.map((k) => k.name).join(", ") + ") - payload must be an object keyed by each key's name");
|
|
59
59
|
}
|
|
60
60
|
const payload = parsed;
|
|
@@ -72,7 +72,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
72
72
|
stillMissing.push(k);
|
|
73
73
|
}
|
|
74
74
|
if (stillMissing.length > 0) {
|
|
75
|
-
throw new Error("waygraph
|
|
75
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" payload is missing required key(s): " +
|
|
76
76
|
stillMissing.map((k) => "\"" + k.name + "\"").join(", "));
|
|
77
77
|
}
|
|
78
78
|
}
|
package/dist/runner/plan/spec.js
CHANGED
|
@@ -8,7 +8,7 @@ export function parseChainSpec(spec) {
|
|
|
8
8
|
.map((s) => {
|
|
9
9
|
const m = /^([A-Za-z_$][\w-]*)\s*(?:\(([\s\S]*)\))?$/.exec(s);
|
|
10
10
|
if (!m) {
|
|
11
|
-
throw new Error("waygraph
|
|
11
|
+
throw new Error("waygraph run --blocks: could not parse segment \"" + s + "\" - expected \"blockName\" or \"blockName({...json...})\"");
|
|
12
12
|
}
|
|
13
13
|
const ref = m[1];
|
|
14
14
|
const json = m[2] ? m[2].trim() : undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineMethodBlock, Trait
|
|
1
|
+
import { defineMethodBlock, Trait } from "waygraph";
|
|
2
2
|
import {
|
|
3
3
|
CheckoutOverviewPage,
|
|
4
4
|
OrderComplete,
|
|
@@ -21,9 +21,7 @@ export const FinishOrderBlock = defineMethodBlock({
|
|
|
21
21
|
instruction: {
|
|
22
22
|
async act(page) {
|
|
23
23
|
const finishButton = page.locator(CheckoutOverviewSel.finishBtn);
|
|
24
|
-
await
|
|
25
|
-
finishButton.click(),
|
|
26
|
-
);
|
|
24
|
+
await finishButton.click();
|
|
27
25
|
},
|
|
28
26
|
verify: [
|
|
29
27
|
Trait.text(
|
|
@@ -31,35 +29,5 @@ export const FinishOrderBlock = defineMethodBlock({
|
|
|
31
29
|
"Thank you for your order!",
|
|
32
30
|
),
|
|
33
31
|
],
|
|
34
|
-
// Shown AFTER act (order already placed) - selectors must match the
|
|
35
|
-
// completion page, not #finish on the overview.
|
|
36
|
-
slides: [
|
|
37
|
-
{
|
|
38
|
-
caption: "Checkout is a short pipeline",
|
|
39
|
-
detail: "Cart -> info -> overview -> done. You just placed the order.",
|
|
40
|
-
tag: "YAP",
|
|
41
|
-
tone: "info",
|
|
42
|
-
duration: true,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
caption: "Completion page is the durable proof",
|
|
46
|
-
detail:
|
|
47
|
-
"tone: success - the thank-you header is what verify asserts on.",
|
|
48
|
-
tag: "YAP",
|
|
49
|
-
tone: "success",
|
|
50
|
-
selector: CheckoutOverviewSel.completeHeader,
|
|
51
|
-
duration: 2500,
|
|
52
|
-
fastMode: 700,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
caption: "Episode continues after this yap",
|
|
56
|
-
detail: "Next step rings the same banner, then you move on.",
|
|
57
|
-
tag: "YAP",
|
|
58
|
-
tone: "planned",
|
|
59
|
-
selector: CheckoutOverviewSel.completeHeader,
|
|
60
|
-
duration: true,
|
|
61
|
-
fastMode: 500,
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
32
|
},
|
|
65
33
|
});
|
|
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
12
12
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
13
13
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
14
14
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
15
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
15
16
|
import { NavBackToProductsBlock } from "../blocks/saucedemo-web/inventory-item/nav-back-to-products.block.js";
|
|
16
17
|
|
|
17
18
|
/** Full checkout, then the "Back Home" click-based Nav Block returns to the catalogue. */
|
|
@@ -35,6 +36,6 @@ export const checkoutThenBrowseAgainFlow = engine
|
|
|
35
36
|
.method(FillLastNameBlock)
|
|
36
37
|
.method(FillPostalCodeBlock)
|
|
37
38
|
.method(SubmitCheckoutInfoBlock)
|
|
38
|
-
.method(FinishOrderBlock)
|
|
39
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
39
40
|
.gotoPage(NavBackToProductsBlock)
|
|
40
41
|
.end();
|
|
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
12
12
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
13
13
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
14
14
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
15
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
15
16
|
import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
|
|
16
17
|
|
|
17
18
|
export const checkoutFlow = engine
|
|
@@ -80,5 +81,5 @@ export const checkoutFlow = engine
|
|
|
80
81
|
}),
|
|
81
82
|
)
|
|
82
83
|
.method(SubmitCheckoutInfoBlock.stubBefore((ctx) => ctx.hideCaption()))
|
|
83
|
-
.method(FinishOrderBlock)
|
|
84
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
84
85
|
.end();
|
|
@@ -8,6 +8,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
8
8
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
9
9
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
10
10
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
11
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
11
12
|
|
|
12
13
|
/** Add the selected item, check out and place the order. Starts signed in (run it after loginFlow). */
|
|
13
14
|
export const shopFlow = engine
|
|
@@ -20,5 +21,5 @@ export const shopFlow = engine
|
|
|
20
21
|
.method(FillLastNameBlock)
|
|
21
22
|
.method(FillPostalCodeBlock)
|
|
22
23
|
.method(SubmitCheckoutInfoBlock)
|
|
23
|
-
.method(FinishOrderBlock)
|
|
24
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
24
25
|
.end();
|