waygraph 0.10.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +331 -44
- package/dist/engine.d.ts +23 -0
- package/dist/engine.js +21 -0
- package/dist/highlights.d.ts +76 -0
- package/dist/highlights.js +136 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/types.d.ts +26 -15
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +29 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +8 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +6 -0
- package/examples/saucedemo/src/flows/checkout.flow.ts +61 -22
- package/package.json +1 -1
- package/templates/quickstart/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +29 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +8 -1
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +6 -0
- package/templates/quickstart/src/flows/checkout.flow.ts +61 -22
- package/templates/scaffold/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -180,6 +180,12 @@ export interface Flow<Out extends Checkpoint<string>> {
|
|
|
180
180
|
* alarming one indistinguishable from a real break.
|
|
181
181
|
*/
|
|
182
182
|
readonly expectedFailureReason?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Set once this Flow has been wrapped in {@link withHighlightFixtures} -
|
|
185
|
+
* demo narration overrides (unlimited purposes on `.flow.ts`) keyed by
|
|
186
|
+
* block.name. `run()` never reads this; step/`demo` merges onto block stubs.
|
|
187
|
+
*/
|
|
188
|
+
readonly highlightFixtures?: import("./highlights.js").HighlightFixtureMap;
|
|
183
189
|
/**
|
|
184
190
|
* This Flow's constituent Blocks, in order between `start`/`end`, as plain
|
|
185
191
|
* data - for introspection/visualization tools, without running anything.
|
|
@@ -266,6 +272,14 @@ export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out
|
|
|
266
272
|
* );
|
|
267
273
|
*/
|
|
268
274
|
export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow: Flow<Out>, reason: string): Flow<Out>;
|
|
275
|
+
/**
|
|
276
|
+
* Attaches demo highlight fixtures to a Flow - unlimited purposes on `.flow.ts`
|
|
277
|
+
* (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter
|
|
278
|
+
* slots. Non-destructive, same pattern as {@link withTitle}.
|
|
279
|
+
* Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
|
|
280
|
+
* @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
|
|
281
|
+
*/
|
|
282
|
+
export declare function withHighlightFixtures<Out extends Checkpoint<string>>(flow: Flow<Out>, fixtures: import("./highlights.js").HighlightFixtureMap): Flow<Out>;
|
|
269
283
|
/**
|
|
270
284
|
* Runs several complete, independent Flows one after another, reusing the
|
|
271
285
|
* same page/context/mem in order - each remains its own validated
|
|
@@ -493,6 +507,9 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
493
507
|
checkpoint: Out["__state"];
|
|
494
508
|
requires?: readonly MemKey<any>[];
|
|
495
509
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
510
|
+
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
511
|
+
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
512
|
+
/** @deprecated Prefer stubAfter. */
|
|
496
513
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
497
514
|
/**
|
|
498
515
|
* Live per-instance menu rows for `waygraph auto` (see {@link Block.instanceOptions}).
|
|
@@ -553,6 +570,9 @@ export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
553
570
|
click: string | ((mem: MemPage) => string);
|
|
554
571
|
requires?: readonly MemKey<any>[];
|
|
555
572
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
573
|
+
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
574
|
+
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
575
|
+
/** @deprecated Prefer stubAfter. */
|
|
556
576
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
557
577
|
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
558
578
|
};
|
|
@@ -581,6 +601,9 @@ export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
581
601
|
/** Checkpoint tag for this screen (hub). */
|
|
582
602
|
checkpoint: Out["__state"];
|
|
583
603
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
604
|
+
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
605
|
+
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
606
|
+
/** @deprecated Prefer stubAfter. */
|
|
584
607
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
585
608
|
requires?: readonly MemKey<any>[];
|
|
586
609
|
/**
|
package/dist/engine.js
CHANGED
|
@@ -377,6 +377,21 @@ export function withExpectedFailure(flow, reason) {
|
|
|
377
377
|
modBlockVerify: (block, nameOrIndex, newCheck) => withExpectedFailure(flow.modBlockVerify(block, nameOrIndex, newCheck), reason),
|
|
378
378
|
};
|
|
379
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Attaches demo highlight fixtures to a Flow - unlimited purposes on `.flow.ts`
|
|
382
|
+
* (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter
|
|
383
|
+
* slots. Non-destructive, same pattern as {@link withTitle}.
|
|
384
|
+
* Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
|
|
385
|
+
* @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
|
|
386
|
+
*/
|
|
387
|
+
export function withHighlightFixtures(flow, fixtures) {
|
|
388
|
+
return {
|
|
389
|
+
...flow,
|
|
390
|
+
highlightFixtures: fixtures,
|
|
391
|
+
withBlockVerify: (block, verify) => withHighlightFixtures(flow.withBlockVerify(block, verify), fixtures),
|
|
392
|
+
modBlockVerify: (block, nameOrIndex, newCheck) => withHighlightFixtures(flow.modBlockVerify(block, nameOrIndex, newCheck), fixtures),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
380
395
|
function locateInChain(flows, block) {
|
|
381
396
|
if (typeof block === "number") {
|
|
382
397
|
let remaining = block;
|
|
@@ -659,6 +674,8 @@ export function defineNavBlock(options) {
|
|
|
659
674
|
},
|
|
660
675
|
resolve: () => checkpoint(options.checkpoint),
|
|
661
676
|
...(options.verify ? { verify: options.verify } : {}),
|
|
677
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
678
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
662
679
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
663
680
|
},
|
|
664
681
|
});
|
|
@@ -692,6 +709,8 @@ export function defineNavClickBlock(options) {
|
|
|
692
709
|
...(options.description ? { description: options.description } : {}),
|
|
693
710
|
...(options.requires ? { requires: options.requires } : {}),
|
|
694
711
|
...(options.verify ? { verify: options.verify } : {}),
|
|
712
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
713
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
695
714
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
696
715
|
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
697
716
|
});
|
|
@@ -738,6 +757,8 @@ export function definePageBlock(options) {
|
|
|
738
757
|
},
|
|
739
758
|
resolve: () => checkpoint(options.checkpoint),
|
|
740
759
|
...(options.verify ? { verify: options.verify } : {}),
|
|
760
|
+
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
761
|
+
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
741
762
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
742
763
|
},
|
|
743
764
|
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
|
|
3
|
+
* Demo narration only - never affects runGraph pass/fail.
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
|
|
6
|
+
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
|
+
*/
|
|
8
|
+
import type { Block, Checkpoint } from "./types.js";
|
|
9
|
+
/** One named highlight slot on a block (selector + caption). */
|
|
10
|
+
export interface WaygraphHighlightStub {
|
|
11
|
+
selector: string;
|
|
12
|
+
/** Primary caption line. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Extra caption line (ring / slide body). */
|
|
15
|
+
detail?: string;
|
|
16
|
+
/** Short badge, e.g. AC / BUG / GATE / YAP. */
|
|
17
|
+
tag?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Flow fixture patch: label required; selector optional (inherits from stub). */
|
|
20
|
+
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag">;
|
|
21
|
+
export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
|
|
22
|
+
export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightStubPhase | ((out: Out) => HighlightStubPhase);
|
|
23
|
+
/**
|
|
24
|
+
* One yap/slide in a multi-step demo caption sequence.
|
|
25
|
+
* Not block lifecycle - demo pauses (Next / auto-next) between slides.
|
|
26
|
+
*/
|
|
27
|
+
export interface WaygraphSlide {
|
|
28
|
+
/** Primary caption (required). */
|
|
29
|
+
caption: string;
|
|
30
|
+
detail?: string;
|
|
31
|
+
tag?: string;
|
|
32
|
+
/** Optional ring while this slide is showing. */
|
|
33
|
+
selector?: string;
|
|
34
|
+
}
|
|
35
|
+
export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
|
|
36
|
+
/** Per-block fixture map attached to a Flow via {@link withHighlightFixtures}. */
|
|
37
|
+
export type HighlightFixtureMap = Record<string, {
|
|
38
|
+
stubBefore?: Record<string, WaygraphHighlightFixture>;
|
|
39
|
+
stubAfter?: Record<string, WaygraphHighlightFixture>;
|
|
40
|
+
/** Override / supply multi-step yap slides for this block (demo only). */
|
|
41
|
+
slides?: readonly WaygraphSlide[];
|
|
42
|
+
}>;
|
|
43
|
+
/** Legacy array highlight (shimmed to stubAfter keys "0", "1", ...). */
|
|
44
|
+
export interface WaygraphHighlight {
|
|
45
|
+
selector: string;
|
|
46
|
+
label: string;
|
|
47
|
+
}
|
|
48
|
+
export type ResolvedHighlight = WaygraphHighlightStub;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
|
|
51
|
+
* Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveHighlightSlots(block: Block<any, any>, phase: "stubBefore" | "stubAfter", opts?: {
|
|
54
|
+
out?: Checkpoint<string>;
|
|
55
|
+
fixtures?: HighlightFixtureMap;
|
|
56
|
+
}): ResolvedHighlight[];
|
|
57
|
+
/**
|
|
58
|
+
* Resolve multi-step yap slides: flow fixture `slides` wins when present,
|
|
59
|
+
* else `instruction.slides` (function form allowed).
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveSlides(block: Block<any, any>, opts?: {
|
|
62
|
+
out?: Checkpoint<string>;
|
|
63
|
+
fixtures?: HighlightFixtureMap;
|
|
64
|
+
}): WaygraphSlide[];
|
|
65
|
+
/** Caption for ring / slide UI (label|caption + optional detail/tag). */
|
|
66
|
+
export declare function formatHighlightCaption(h: {
|
|
67
|
+
label?: string;
|
|
68
|
+
caption?: string;
|
|
69
|
+
detail?: string;
|
|
70
|
+
tag?: string;
|
|
71
|
+
}): string;
|
|
72
|
+
/**
|
|
73
|
+
* True when the block authored a non-empty stubAfter (after resolve + shim),
|
|
74
|
+
* including legacy highlights. When true, demo should not use verify-trait fallback.
|
|
75
|
+
*/
|
|
76
|
+
export declare function hasAuthoredStubAfter(block: Block<any, any>, out?: Checkpoint<string>, fixtures?: HighlightFixtureMap): boolean;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
|
|
3
|
+
* Demo narration only - never affects runGraph pass/fail.
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
|
|
6
|
+
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
|
+
*/
|
|
8
|
+
function isStubPhase(v) {
|
|
9
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
10
|
+
}
|
|
11
|
+
function mergeSlot(base, patch) {
|
|
12
|
+
if (!base && !patch)
|
|
13
|
+
return null;
|
|
14
|
+
if (!base && patch) {
|
|
15
|
+
if (!patch.selector)
|
|
16
|
+
return null;
|
|
17
|
+
return {
|
|
18
|
+
selector: patch.selector,
|
|
19
|
+
label: patch.label,
|
|
20
|
+
...(patch.detail ? { detail: patch.detail } : {}),
|
|
21
|
+
...(patch.tag ? { tag: patch.tag } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (base && !patch)
|
|
25
|
+
return { ...base };
|
|
26
|
+
return {
|
|
27
|
+
selector: patch.selector ?? base.selector,
|
|
28
|
+
label: patch.label,
|
|
29
|
+
...(patch.detail !== undefined
|
|
30
|
+
? { detail: patch.detail }
|
|
31
|
+
: base.detail
|
|
32
|
+
? { detail: base.detail }
|
|
33
|
+
: {}),
|
|
34
|
+
...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function phaseFromLegacyHighlights(highlights) {
|
|
38
|
+
const out = {};
|
|
39
|
+
if (!highlights)
|
|
40
|
+
return out;
|
|
41
|
+
highlights.forEach((h, i) => {
|
|
42
|
+
if (h && typeof h.selector === "string" && typeof h.label === "string") {
|
|
43
|
+
out[String(i)] = { selector: h.selector, label: h.label };
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
|
|
50
|
+
* Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveHighlightSlots(block, phase, opts) {
|
|
53
|
+
const instr = block.instruction;
|
|
54
|
+
let raw = phase === "stubBefore" ? instr.stubBefore : instr.stubAfter;
|
|
55
|
+
let phaseMap = {};
|
|
56
|
+
if (typeof raw === "function") {
|
|
57
|
+
try {
|
|
58
|
+
phaseMap = raw(opts?.out ?? { __state: "" }) || {};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
phaseMap = {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (isStubPhase(raw)) {
|
|
65
|
+
phaseMap = { ...raw };
|
|
66
|
+
}
|
|
67
|
+
if (phase === "stubAfter" && Object.keys(phaseMap).length === 0 && instr.highlights) {
|
|
68
|
+
let legacy = instr.highlights;
|
|
69
|
+
if (typeof legacy === "function") {
|
|
70
|
+
try {
|
|
71
|
+
legacy = legacy(opts?.out ?? { __state: "" });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
legacy = [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
phaseMap = phaseFromLegacyHighlights(Array.isArray(legacy) ? legacy : []);
|
|
78
|
+
}
|
|
79
|
+
const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
|
|
80
|
+
const slotIds = new Set([...Object.keys(phaseMap), ...Object.keys(fixturePhase || {})]);
|
|
81
|
+
const ordered = [...slotIds].sort((a, b) => {
|
|
82
|
+
const na = Number(a);
|
|
83
|
+
const nb = Number(b);
|
|
84
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb))
|
|
85
|
+
return na - nb;
|
|
86
|
+
return a.localeCompare(b);
|
|
87
|
+
});
|
|
88
|
+
const resolved = [];
|
|
89
|
+
for (const id of ordered) {
|
|
90
|
+
const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
|
|
91
|
+
if (merged)
|
|
92
|
+
resolved.push(merged);
|
|
93
|
+
}
|
|
94
|
+
return resolved;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve multi-step yap slides: flow fixture `slides` wins when present,
|
|
98
|
+
* else `instruction.slides` (function form allowed).
|
|
99
|
+
*/
|
|
100
|
+
export function resolveSlides(block, opts) {
|
|
101
|
+
const fromFixture = opts?.fixtures?.[block.name]?.slides;
|
|
102
|
+
if (fromFixture && fromFixture.length > 0) {
|
|
103
|
+
return [...fromFixture];
|
|
104
|
+
}
|
|
105
|
+
const instr = block.instruction;
|
|
106
|
+
let raw = instr.slides;
|
|
107
|
+
if (typeof raw === "function") {
|
|
108
|
+
try {
|
|
109
|
+
raw = raw(opts?.out ?? { __state: "" });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
raw = [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(raw))
|
|
116
|
+
return [];
|
|
117
|
+
return raw.filter((s) => s && typeof s.caption === "string" && s.caption.length > 0);
|
|
118
|
+
}
|
|
119
|
+
/** Caption for ring / slide UI (label|caption + optional detail/tag). */
|
|
120
|
+
export function formatHighlightCaption(h) {
|
|
121
|
+
const primary = h.caption ?? h.label ?? "";
|
|
122
|
+
const tag = h.tag ? `[${h.tag}] ` : "";
|
|
123
|
+
if (h.detail)
|
|
124
|
+
return `${tag}${primary} - ${h.detail}`;
|
|
125
|
+
return `${tag}${primary}`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* True when the block authored a non-empty stubAfter (after resolve + shim),
|
|
129
|
+
* including legacy highlights. When true, demo should not use verify-trait fallback.
|
|
130
|
+
*/
|
|
131
|
+
export function hasAuthoredStubAfter(block, out, fixtures) {
|
|
132
|
+
return resolveHighlightSlots(block, "stubAfter", {
|
|
133
|
+
...(out !== undefined ? { out } : {}),
|
|
134
|
+
...(fixtures !== undefined ? { fixtures } : {}),
|
|
135
|
+
}).length > 0;
|
|
136
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInsta
|
|
|
2
2
|
export { connect, checkpoint } from "./types.js";
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
5
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, formatHighlightCaption, hasAuthoredStubAfter, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, } from "./highlights.js";
|
|
6
8
|
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
7
9
|
export type { ActionPage } from "./types.js";
|
|
8
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { connect, checkpoint } from "./types.js";
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
4
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, formatHighlightCaption, hasAuthoredStubAfter, } from "./highlights.js";
|
|
5
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Page } from "@playwright/test";
|
|
2
2
|
import type { MemPage, MemKey } from "./mem-page.js";
|
|
3
3
|
import type { Trait } from "./trait.js";
|
|
4
|
+
import type { HighlightStubPhaseOrFn, WaygraphHighlight, WaygraphSlidesOrFn } from "./highlights.js";
|
|
5
|
+
export type { WaygraphHighlight, WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightStubPhaseOrFn, HighlightFixtureMap, ResolvedHighlight, } from "./highlights.js";
|
|
4
6
|
/**
|
|
5
7
|
* A Checkpoint is identified solely by its string tag - a phantom marker with no
|
|
6
8
|
* embedded data. Two Checkpoints with the same tag are the same state everywhere.
|
|
@@ -88,24 +90,33 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
|
|
|
88
90
|
*/
|
|
89
91
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
90
92
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
*
|
|
93
|
+
* Catch-all demo slots shown **before / during** `act` (fill/click rings).
|
|
94
|
+
* Named map; `{}` is valid. Flow {@link HighlightFixtureMap} overrides labels.
|
|
95
|
+
* @example stubBefore: { email: { selector: "#email", label: "Email" } }
|
|
96
|
+
*/
|
|
97
|
+
stubBefore?: HighlightStubPhaseOrFn<Out>;
|
|
98
|
+
/**
|
|
99
|
+
* Catch-all demo slots shown **after** resolve (step panel rings).
|
|
100
|
+
* Named map; `{}` is valid. Prefer this over legacy {@link Instruction.highlights}.
|
|
101
|
+
* @example stubAfter: { badge: { selector: ".cart", label: "Cart updated" } }
|
|
102
|
+
*/
|
|
103
|
+
stubAfter?: HighlightStubPhaseOrFn<Out>;
|
|
104
|
+
/**
|
|
105
|
+
* Multi-step demo captions ("yap" slides) with Next between each -
|
|
106
|
+
* not block lifecycle. Explains a long process; optional `selector` rings
|
|
107
|
+
* while that slide is up. Flow fixtures may override via `slides`.
|
|
108
|
+
* @example slides: [
|
|
109
|
+
* { caption: "Checkout is three screens", tag: "YAP" },
|
|
110
|
+
* { caption: "Overview confirms totals", selector: ".summary_info" },
|
|
111
|
+
* ]
|
|
112
|
+
*/
|
|
113
|
+
slides?: WaygraphSlidesOrFn<Out>;
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Prefer {@link Instruction.stubAfter}. Shimmed to stubAfter keys
|
|
116
|
+
* `"0"`, `"1"`, … when stubAfter is empty.
|
|
99
117
|
*/
|
|
100
118
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
101
119
|
}
|
|
102
|
-
/** One arbitrary highlight point - see {@link Instruction.highlights}. */
|
|
103
|
-
export interface WaygraphHighlight {
|
|
104
|
-
/** CSS selector for the element to highlight. */
|
|
105
|
-
selector: string;
|
|
106
|
-
/** Caption shown next to the highlight ring. */
|
|
107
|
-
label: string;
|
|
108
|
-
}
|
|
109
120
|
/**
|
|
110
121
|
* One selectable option for a Block whose real choices depend on the live
|
|
111
122
|
* page - "which of these items to add to cart" is only knowable by looking
|
|
@@ -18,6 +18,34 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
18
18
|
},
|
|
19
19
|
resolve: () => checkpoint("OrderComplete"),
|
|
20
20
|
verify: [Trait.text(".complete-header", "Thank you for your order!")],
|
|
21
|
-
|
|
21
|
+
stubBefore: {
|
|
22
|
+
finish: { selector: "#finish", label: "Finish" },
|
|
23
|
+
},
|
|
24
|
+
stubAfter: {
|
|
25
|
+
thanks: {
|
|
26
|
+
selector: ".complete-header",
|
|
27
|
+
label: "Order confirmed",
|
|
28
|
+
detail: "Thank you for your order!",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
slides: [
|
|
32
|
+
{
|
|
33
|
+
caption: "Checkout is a short pipeline",
|
|
34
|
+
detail: "Cart → info → overview → done. Watch the last click place the order.",
|
|
35
|
+
tag: "YAP",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
caption: "Finish submits the overview",
|
|
39
|
+
detail: "This click is the only mutating step on this screen.",
|
|
40
|
+
tag: "YAP",
|
|
41
|
+
selector: "#finish",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
caption: "Completion banner is the proof",
|
|
45
|
+
detail: "The next ring calls out the thank-you header.",
|
|
46
|
+
tag: "YAP",
|
|
47
|
+
selector: ".complete-header",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
22
50
|
},
|
|
23
51
|
});
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
-
|
|
33
|
+
stubBefore: {},
|
|
34
|
+
stubAfter: {
|
|
35
|
+
badge: {
|
|
36
|
+
selector: InventorySel.cartBadge,
|
|
37
|
+
label: "Cart badge",
|
|
38
|
+
detail: "Item is in the cart",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
34
41
|
},
|
|
35
42
|
async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
|
|
36
43
|
const items = await collectAddableItems(page);
|
|
@@ -38,5 +38,11 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
38
38
|
out.__state === "LoggedIn"
|
|
39
39
|
? [Trait.url({ pathname: "/inventory.html" })]
|
|
40
40
|
: [Trait.visible('[data-test="error"]')],
|
|
41
|
+
stubBefore: {
|
|
42
|
+
username: { selector: "#user-name", label: "Username" },
|
|
43
|
+
password: { selector: "#password", label: "Password" },
|
|
44
|
+
submit: { selector: "#login-button", label: "Login" },
|
|
45
|
+
},
|
|
46
|
+
stubAfter: {},
|
|
41
47
|
},
|
|
42
48
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Engine, start, end, withTitle } from "waygraph";
|
|
1
|
+
import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
|
|
2
2
|
import type {
|
|
3
3
|
LoginPage,
|
|
4
4
|
LoggedIn,
|
|
@@ -21,25 +21,64 @@ const engine = new Engine({ headless: false, slowMo: 250 });
|
|
|
21
21
|
// Atomic nav/action per real URL - the shape `waygraph auto` discovers.
|
|
22
22
|
// Explicit type args: AddToCartBlock's In is Checkpoint<string> (wildcard),
|
|
23
23
|
// which trips defineFlow overload inference across a long array.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
24
|
+
//
|
|
25
|
+
// Demo narration showcase (waygraph 0.11+):
|
|
26
|
+
// - Block stubs: submit-login stubBefore, add-to-cart / finish-order stubAfter
|
|
27
|
+
// - Flow fixtures: ticket-style captions (unlimited purposes on .flow.ts)
|
|
28
|
+
// - Slides on finish-order: multi-step yap with Next between slides
|
|
29
|
+
export const checkoutFlow = withHighlightFixtures(
|
|
30
|
+
withTitle(
|
|
31
|
+
engine.defineFlow<
|
|
32
|
+
LoginPage,
|
|
33
|
+
LoggedIn,
|
|
34
|
+
ItemInCart,
|
|
35
|
+
CartPage,
|
|
36
|
+
CheckoutInfoPage,
|
|
37
|
+
CheckoutOverviewPage,
|
|
38
|
+
OrderComplete
|
|
39
|
+
>([
|
|
40
|
+
start,
|
|
41
|
+
NavLoginBlock,
|
|
42
|
+
SubmitLoginForFlow,
|
|
43
|
+
AddToCartBlock,
|
|
44
|
+
NavCartBlock,
|
|
45
|
+
NavCheckoutInfoBlock,
|
|
46
|
+
SubmitCheckoutInfoBlock,
|
|
47
|
+
FinishOrderBlock,
|
|
48
|
+
end,
|
|
49
|
+
]),
|
|
50
|
+
"Owner: Full Checkout",
|
|
51
|
+
),
|
|
52
|
+
{
|
|
53
|
+
"submit-login": {
|
|
54
|
+
stubBefore: {
|
|
55
|
+
username: {
|
|
56
|
+
label: "Demo user",
|
|
57
|
+
detail: "standard_user from Mem — library block stays neutral.",
|
|
58
|
+
tag: "AC",
|
|
59
|
+
},
|
|
60
|
+
password: { label: "Password", detail: "Filled from Mem credentials." },
|
|
61
|
+
submit: { label: "Sign in", tag: "GATE" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"add-to-cart": {
|
|
65
|
+
stubAfter: {
|
|
66
|
+
badge: {
|
|
67
|
+
label: "Cart now has the item",
|
|
68
|
+
detail: "Badge updates after Add to cart.",
|
|
69
|
+
tag: "AC",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
"finish-order": {
|
|
74
|
+
stubAfter: {
|
|
75
|
+
thanks: {
|
|
76
|
+
label: "Order placed",
|
|
77
|
+
detail: "Completion header is the durable proof.",
|
|
78
|
+
tag: "GATE",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
// Optional: fixture can replace block slides; leave unset to use block slides.
|
|
82
|
+
},
|
|
83
|
+
},
|
|
45
84
|
);
|
package/package.json
CHANGED
|
@@ -18,6 +18,34 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
18
18
|
},
|
|
19
19
|
resolve: () => checkpoint("OrderComplete"),
|
|
20
20
|
verify: [Trait.text(".complete-header", "Thank you for your order!")],
|
|
21
|
-
|
|
21
|
+
stubBefore: {
|
|
22
|
+
finish: { selector: "#finish", label: "Finish" },
|
|
23
|
+
},
|
|
24
|
+
stubAfter: {
|
|
25
|
+
thanks: {
|
|
26
|
+
selector: ".complete-header",
|
|
27
|
+
label: "Order confirmed",
|
|
28
|
+
detail: "Thank you for your order!",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
slides: [
|
|
32
|
+
{
|
|
33
|
+
caption: "Checkout is a short pipeline",
|
|
34
|
+
detail: "Cart → info → overview → done. Watch the last click place the order.",
|
|
35
|
+
tag: "YAP",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
caption: "Finish submits the overview",
|
|
39
|
+
detail: "This click is the only mutating step on this screen.",
|
|
40
|
+
tag: "YAP",
|
|
41
|
+
selector: "#finish",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
caption: "Completion banner is the proof",
|
|
45
|
+
detail: "The next ring calls out the thank-you header.",
|
|
46
|
+
tag: "YAP",
|
|
47
|
+
selector: ".complete-header",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
22
50
|
},
|
|
23
51
|
});
|
package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
32
|
],
|
|
33
|
-
|
|
33
|
+
stubBefore: {},
|
|
34
|
+
stubAfter: {
|
|
35
|
+
badge: {
|
|
36
|
+
selector: InventorySel.cartBadge,
|
|
37
|
+
label: "Cart badge",
|
|
38
|
+
detail: "Item is in the cart",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
34
41
|
},
|
|
35
42
|
async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
|
|
36
43
|
const items = await collectAddableItems(page);
|
|
@@ -38,5 +38,11 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
38
38
|
out.__state === "LoggedIn"
|
|
39
39
|
? [Trait.url({ pathname: "/inventory.html" })]
|
|
40
40
|
: [Trait.visible('[data-test="error"]')],
|
|
41
|
+
stubBefore: {
|
|
42
|
+
username: { selector: "#user-name", label: "Username" },
|
|
43
|
+
password: { selector: "#password", label: "Password" },
|
|
44
|
+
submit: { selector: "#login-button", label: "Login" },
|
|
45
|
+
},
|
|
46
|
+
stubAfter: {},
|
|
41
47
|
},
|
|
42
48
|
});
|