waygraph 0.11.2 → 0.12.4
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 +6 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +192 -68
- package/dist/engine.d.ts +24 -2
- package/dist/engine.js +56 -2
- package/dist/highlights.d.ts +53 -6
- package/dist/highlights.js +65 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/trait.js +3 -1
- package/dist/types.d.ts +7 -0
- package/examples/saucedemo/.waygraph-videos/page@35b5c5324773dbfa5232d17a283e1ef8.webm +0 -0
- package/examples/saucedemo/.waygraph-videos/page@78092e8c89a2e7b54444ba6d108c8893.webm +0 -0
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/package.json +1 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +6 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/ff-owner-auth.block.ts +17 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +18 -2
- package/examples/saucedemo/src/flows/checkout.flow.ts +27 -16
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +48 -0
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +6 -0
- package/templates/quickstart/src/blocks/saucedemo-web/ff-owner-auth.block.ts +17 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -2
- package/templates/quickstart/src/flows/checkout.flow.ts +20 -18
package/dist/engine.js
CHANGED
|
@@ -379,8 +379,8 @@ export function withExpectedFailure(flow, reason) {
|
|
|
379
379
|
}
|
|
380
380
|
/**
|
|
381
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}.
|
|
382
|
+
* (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter/
|
|
383
|
+
* stubOnError slots. Non-destructive, same pattern as {@link withTitle}.
|
|
384
384
|
* Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
|
|
385
385
|
* @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
|
|
386
386
|
*/
|
|
@@ -542,6 +542,57 @@ export function composeBlock(name, steps) {
|
|
|
542
542
|
},
|
|
543
543
|
};
|
|
544
544
|
}
|
|
545
|
+
/** True when `block` came from {@link fastForwardComposeBlock}. */
|
|
546
|
+
export function isFastForwardBlock(block) {
|
|
547
|
+
return (block.fastForward === true &&
|
|
548
|
+
typeof block.steps === "function");
|
|
549
|
+
}
|
|
550
|
+
function wrapFfError(ffName, err) {
|
|
551
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
552
|
+
if (msg.startsWith(`${ffName} > `)) {
|
|
553
|
+
return err instanceof Error ? err : new Error(msg);
|
|
554
|
+
}
|
|
555
|
+
const wrapped = new Error(`${ffName} > ${msg}`);
|
|
556
|
+
if (err instanceof Error && err.stack) {
|
|
557
|
+
wrapped.stack = err.stack;
|
|
558
|
+
}
|
|
559
|
+
return wrapped;
|
|
560
|
+
}
|
|
561
|
+
export function fastForwardComposeBlock(name, steps) {
|
|
562
|
+
if (steps.length === 0) {
|
|
563
|
+
throw new Error(`fastForwardComposeBlock: "${name}" needs at least one step`);
|
|
564
|
+
}
|
|
565
|
+
const base = composeBlock(name, steps);
|
|
566
|
+
const origAct = base.instruction.act.bind(base.instruction);
|
|
567
|
+
const ff = {
|
|
568
|
+
...base,
|
|
569
|
+
fastForward: true,
|
|
570
|
+
instruction: {
|
|
571
|
+
...base.instruction,
|
|
572
|
+
async act(page, input, mem) {
|
|
573
|
+
try {
|
|
574
|
+
await origAct(page, input, mem);
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
throw wrapFfError(name, e);
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
withStepVerify(step, verify) {
|
|
582
|
+
const index = findBlockIndex(steps, step);
|
|
583
|
+
const patched = [...steps];
|
|
584
|
+
patched[index] = withVerify(steps[index], verify);
|
|
585
|
+
return fastForwardComposeBlock(name, patched);
|
|
586
|
+
},
|
|
587
|
+
modStepVerify(step, nameOrIndex, newCheck) {
|
|
588
|
+
const index = findBlockIndex(steps, step);
|
|
589
|
+
const patched = [...steps];
|
|
590
|
+
patched[index] = modVerify(steps[index], nameOrIndex, newCheck);
|
|
591
|
+
return fastForwardComposeBlock(name, patched);
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
return ff;
|
|
595
|
+
}
|
|
545
596
|
/**
|
|
546
597
|
* Holds engine-level configuration shared across every flow defined from it -
|
|
547
598
|
* for now, just how to launch a browser when a Flow owns its own
|
|
@@ -676,6 +727,7 @@ export function defineNavBlock(options) {
|
|
|
676
727
|
...(options.verify ? { verify: options.verify } : {}),
|
|
677
728
|
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
678
729
|
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
730
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
679
731
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
680
732
|
},
|
|
681
733
|
});
|
|
@@ -711,6 +763,7 @@ export function defineNavClickBlock(options) {
|
|
|
711
763
|
...(options.verify ? { verify: options.verify } : {}),
|
|
712
764
|
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
713
765
|
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
766
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
714
767
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
715
768
|
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
716
769
|
});
|
|
@@ -759,6 +812,7 @@ export function definePageBlock(options) {
|
|
|
759
812
|
...(options.verify ? { verify: options.verify } : {}),
|
|
760
813
|
...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
|
|
761
814
|
...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
|
|
815
|
+
...(options.stubOnError ? { stubOnError: options.stubOnError } : {}),
|
|
762
816
|
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
763
817
|
},
|
|
764
818
|
});
|
package/dist/highlights.d.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
|
|
2
|
+
* Block stubBefore / stubAfter / stubOnError + flow highlightFixtures + demo slides.
|
|
3
3
|
* Demo narration only - never affects runGraph pass/fail.
|
|
4
4
|
*
|
|
5
|
-
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
|
|
5
|
+
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after/error rings tied to the block;
|
|
6
6
|
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
|
+
*
|
|
8
|
+
* All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
|
|
9
|
+
* before advancing is authored the same way on stubs, flow fixtures, and slides.
|
|
7
10
|
*/
|
|
8
11
|
import type { Block, Checkpoint } from "./types.js";
|
|
12
|
+
/** Shared dwell fields on stubs, fixtures, and yap slides. */
|
|
13
|
+
export type FixtureDurationFields = {
|
|
14
|
+
/**
|
|
15
|
+
* Min time this fixture stays visible before the demo advances.
|
|
16
|
+
* - `true` - defaults (2000ms normal, {@link FixtureDurationFields.fastMode} or 600ms when --fast)
|
|
17
|
+
* - `number` - override normal dwell ms
|
|
18
|
+
* - omit / false - no authored dwell (caller keeps legacy timing)
|
|
19
|
+
*/
|
|
20
|
+
duration?: boolean | number;
|
|
21
|
+
/**
|
|
22
|
+
* Dwell ms when demo `--fast` / gatesFast (only when {@link duration} is set).
|
|
23
|
+
* Default 600.
|
|
24
|
+
*/
|
|
25
|
+
fastMode?: number;
|
|
26
|
+
};
|
|
9
27
|
/** One named highlight slot on a block (selector + caption). */
|
|
10
|
-
export interface WaygraphHighlightStub {
|
|
28
|
+
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
11
29
|
selector: string;
|
|
12
30
|
/** Primary caption line. */
|
|
13
31
|
label: string;
|
|
@@ -17,14 +35,14 @@ export interface WaygraphHighlightStub {
|
|
|
17
35
|
tag?: string;
|
|
18
36
|
}
|
|
19
37
|
/** 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"
|
|
38
|
+
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag"> & FixtureDurationFields;
|
|
21
39
|
export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
|
|
22
40
|
export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightStubPhase | ((out: Out) => HighlightStubPhase);
|
|
23
41
|
/**
|
|
24
42
|
* One yap/slide in a multi-step demo caption sequence.
|
|
25
43
|
* Not block lifecycle - demo pauses (Next / auto-next) between slides.
|
|
26
44
|
*/
|
|
27
|
-
export interface WaygraphSlide {
|
|
45
|
+
export interface WaygraphSlide extends FixtureDurationFields {
|
|
28
46
|
/** Primary caption (required). */
|
|
29
47
|
caption: string;
|
|
30
48
|
detail?: string;
|
|
@@ -32,11 +50,34 @@ export interface WaygraphSlide {
|
|
|
32
50
|
/** Optional ring while this slide is showing. */
|
|
33
51
|
selector?: string;
|
|
34
52
|
}
|
|
53
|
+
/** Default fixture dwell when `duration: true`. */
|
|
54
|
+
export declare const FIXTURE_DURATION_MS_DEFAULT = 2000;
|
|
55
|
+
/** Default fixture dwell under `--fast` when duration is enabled. */
|
|
56
|
+
export declare const FIXTURE_DURATION_FAST_MS_DEFAULT = 600;
|
|
57
|
+
/** @deprecated Prefer FIXTURE_DURATION_MS_DEFAULT */
|
|
58
|
+
export declare const SLIDE_DURATION_MS_DEFAULT = 2000;
|
|
59
|
+
/** @deprecated Prefer FIXTURE_DURATION_FAST_MS_DEFAULT */
|
|
60
|
+
export declare const SLIDE_DURATION_FAST_MS_DEFAULT = 600;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve min dwell ms for any fixture (stub / flow fixture / yap slide),
|
|
63
|
+
* or `null` when duration is unset (caller keeps legacy timing).
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveFixtureDwellMs(fixture: FixtureDurationFields, opts?: {
|
|
66
|
+
gatesFast?: boolean;
|
|
67
|
+
}): number | null;
|
|
68
|
+
/** @deprecated Prefer {@link resolveFixtureDwellMs} */
|
|
69
|
+
export declare function resolveSlideDwellMs(slide: FixtureDurationFields, opts?: {
|
|
70
|
+
gatesFast?: boolean;
|
|
71
|
+
}): number | null;
|
|
35
72
|
export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
|
|
73
|
+
/** Demo stub phase names (block instruction + flow fixtures). */
|
|
74
|
+
export type HighlightStubPhaseName = "stubBefore" | "stubAfter" | "stubOnError";
|
|
36
75
|
/** Per-block fixture map attached to a Flow via {@link withHighlightFixtures}. */
|
|
37
76
|
export type HighlightFixtureMap = Record<string, {
|
|
38
77
|
stubBefore?: Record<string, WaygraphHighlightFixture>;
|
|
39
78
|
stubAfter?: Record<string, WaygraphHighlightFixture>;
|
|
79
|
+
/** Fail-path rings (demo step throw) - distinct copy from success stubAfter. */
|
|
80
|
+
stubOnError?: Record<string, WaygraphHighlightFixture>;
|
|
40
81
|
/** Override / supply multi-step yap slides for this block (demo only). */
|
|
41
82
|
slides?: readonly WaygraphSlide[];
|
|
42
83
|
}>;
|
|
@@ -49,8 +90,9 @@ export type ResolvedHighlight = WaygraphHighlightStub;
|
|
|
49
90
|
/**
|
|
50
91
|
* Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
|
|
51
92
|
* Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
|
|
93
|
+
* `stubOnError` is fail-path only (demo step catch) - never run on success.
|
|
52
94
|
*/
|
|
53
|
-
export declare function resolveHighlightSlots(block: Block<any, any>, phase:
|
|
95
|
+
export declare function resolveHighlightSlots(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
|
|
54
96
|
out?: Checkpoint<string>;
|
|
55
97
|
fixtures?: HighlightFixtureMap;
|
|
56
98
|
}): ResolvedHighlight[];
|
|
@@ -74,3 +116,8 @@ export declare function formatHighlightCaption(h: {
|
|
|
74
116
|
* including legacy highlights. When true, demo should not use verify-trait fallback.
|
|
75
117
|
*/
|
|
76
118
|
export declare function hasAuthoredStubAfter(block: Block<any, any>, out?: Checkpoint<string>, fixtures?: HighlightFixtureMap): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* True when stubOnError resolves to at least one ring (block and/or flow fixture).
|
|
121
|
+
* Demo fail path only - success never consults this.
|
|
122
|
+
*/
|
|
123
|
+
export declare function hasAuthoredStubOnError(block: Block<any, any>, fixtures?: HighlightFixtureMap): boolean;
|
package/dist/highlights.js
CHANGED
|
@@ -1,16 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
|
|
2
|
+
* Block stubBefore / stubAfter / stubOnError + flow highlightFixtures + demo slides.
|
|
3
3
|
* Demo narration only - never affects runGraph pass/fail.
|
|
4
4
|
*
|
|
5
|
-
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
|
|
5
|
+
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after/error rings tied to the block;
|
|
6
6
|
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
|
+
*
|
|
8
|
+
* All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
|
|
9
|
+
* before advancing is authored the same way on stubs, flow fixtures, and slides.
|
|
10
|
+
*/
|
|
11
|
+
/** Default fixture dwell when `duration: true`. */
|
|
12
|
+
export const FIXTURE_DURATION_MS_DEFAULT = 2000;
|
|
13
|
+
/** Default fixture dwell under `--fast` when duration is enabled. */
|
|
14
|
+
export const FIXTURE_DURATION_FAST_MS_DEFAULT = 600;
|
|
15
|
+
/** @deprecated Prefer FIXTURE_DURATION_MS_DEFAULT */
|
|
16
|
+
export const SLIDE_DURATION_MS_DEFAULT = FIXTURE_DURATION_MS_DEFAULT;
|
|
17
|
+
/** @deprecated Prefer FIXTURE_DURATION_FAST_MS_DEFAULT */
|
|
18
|
+
export const SLIDE_DURATION_FAST_MS_DEFAULT = FIXTURE_DURATION_FAST_MS_DEFAULT;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve min dwell ms for any fixture (stub / flow fixture / yap slide),
|
|
21
|
+
* or `null` when duration is unset (caller keeps legacy timing).
|
|
7
22
|
*/
|
|
23
|
+
export function resolveFixtureDwellMs(fixture, opts) {
|
|
24
|
+
if (fixture.duration === undefined || fixture.duration === false)
|
|
25
|
+
return null;
|
|
26
|
+
const fast = !!opts?.gatesFast;
|
|
27
|
+
const fastMs = typeof fixture.fastMode === "number" && Number.isFinite(fixture.fastMode) && fixture.fastMode >= 0
|
|
28
|
+
? fixture.fastMode
|
|
29
|
+
: FIXTURE_DURATION_FAST_MS_DEFAULT;
|
|
30
|
+
if (fast)
|
|
31
|
+
return fastMs;
|
|
32
|
+
if (fixture.duration === true)
|
|
33
|
+
return FIXTURE_DURATION_MS_DEFAULT;
|
|
34
|
+
if (typeof fixture.duration === "number" && Number.isFinite(fixture.duration) && fixture.duration >= 0) {
|
|
35
|
+
return fixture.duration;
|
|
36
|
+
}
|
|
37
|
+
return FIXTURE_DURATION_MS_DEFAULT;
|
|
38
|
+
}
|
|
39
|
+
/** @deprecated Prefer {@link resolveFixtureDwellMs} */
|
|
40
|
+
export function resolveSlideDwellMs(slide, opts) {
|
|
41
|
+
return resolveFixtureDwellMs(slide, opts);
|
|
42
|
+
}
|
|
8
43
|
function isStubPhase(v) {
|
|
9
44
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
10
45
|
}
|
|
46
|
+
function pickDurationFields(base, patch) {
|
|
47
|
+
const out = {};
|
|
48
|
+
const duration = patch?.duration !== undefined ? patch.duration : base?.duration;
|
|
49
|
+
const fastMode = patch?.fastMode !== undefined ? patch.fastMode : base?.fastMode;
|
|
50
|
+
if (duration !== undefined)
|
|
51
|
+
out.duration = duration;
|
|
52
|
+
if (fastMode !== undefined)
|
|
53
|
+
out.fastMode = fastMode;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
11
56
|
function mergeSlot(base, patch) {
|
|
12
57
|
if (!base && !patch)
|
|
13
58
|
return null;
|
|
59
|
+
const dwell = pickDurationFields(base, patch);
|
|
14
60
|
if (!base && patch) {
|
|
15
61
|
if (!patch.selector)
|
|
16
62
|
return null;
|
|
@@ -19,6 +65,7 @@ function mergeSlot(base, patch) {
|
|
|
19
65
|
label: patch.label,
|
|
20
66
|
...(patch.detail ? { detail: patch.detail } : {}),
|
|
21
67
|
...(patch.tag ? { tag: patch.tag } : {}),
|
|
68
|
+
...dwell,
|
|
22
69
|
};
|
|
23
70
|
}
|
|
24
71
|
if (base && !patch)
|
|
@@ -32,6 +79,7 @@ function mergeSlot(base, patch) {
|
|
|
32
79
|
? { detail: base.detail }
|
|
33
80
|
: {}),
|
|
34
81
|
...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
|
|
82
|
+
...dwell,
|
|
35
83
|
};
|
|
36
84
|
}
|
|
37
85
|
function phaseFromLegacyHighlights(highlights) {
|
|
@@ -48,10 +96,15 @@ function phaseFromLegacyHighlights(highlights) {
|
|
|
48
96
|
/**
|
|
49
97
|
* Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
|
|
50
98
|
* Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
|
|
99
|
+
* `stubOnError` is fail-path only (demo step catch) - never run on success.
|
|
51
100
|
*/
|
|
52
101
|
export function resolveHighlightSlots(block, phase, opts) {
|
|
53
102
|
const instr = block.instruction;
|
|
54
|
-
let raw = phase === "stubBefore"
|
|
103
|
+
let raw = phase === "stubBefore"
|
|
104
|
+
? instr.stubBefore
|
|
105
|
+
: phase === "stubAfter"
|
|
106
|
+
? instr.stubAfter
|
|
107
|
+
: instr.stubOnError;
|
|
55
108
|
let phaseMap = {};
|
|
56
109
|
if (typeof raw === "function") {
|
|
57
110
|
try {
|
|
@@ -134,3 +187,12 @@ export function hasAuthoredStubAfter(block, out, fixtures) {
|
|
|
134
187
|
...(fixtures !== undefined ? { fixtures } : {}),
|
|
135
188
|
}).length > 0;
|
|
136
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* True when stubOnError resolves to at least one ring (block and/or flow fixture).
|
|
192
|
+
* Demo fail path only - success never consults this.
|
|
193
|
+
*/
|
|
194
|
+
export function hasAuthoredStubOnError(block, fixtures) {
|
|
195
|
+
return (resolveHighlightSlots(block, "stubOnError", {
|
|
196
|
+
...(fixtures !== undefined ? { fixtures } : {}),
|
|
197
|
+
}).length > 0);
|
|
198
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ 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, 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";
|
|
8
|
-
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } 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, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, } from "./highlights.js";
|
|
8
|
+
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
10
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
11
11
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +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, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, formatHighlightCaption, hasAuthoredStubAfter, } from "./highlights.js";
|
|
4
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/trait.js
CHANGED
|
@@ -22,7 +22,9 @@ export function urlMatches(pattern) {
|
|
|
22
22
|
return {
|
|
23
23
|
name: `url-matches(${JSON.stringify(pattern)})`,
|
|
24
24
|
async check(page) {
|
|
25
|
-
|
|
25
|
+
// Bound wait - fail loud instead of hanging forever when navigation never comes
|
|
26
|
+
// (e.g. locked_out login still on / while verify demands /inventory.html).
|
|
27
|
+
await page.waitForURL(urlPattern, { timeout: 8_000 });
|
|
26
28
|
return true;
|
|
27
29
|
},
|
|
28
30
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -101,6 +101,13 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
|
|
|
101
101
|
* @example stubAfter: { badge: { selector: ".cart", label: "Cart updated" } }
|
|
102
102
|
*/
|
|
103
103
|
stubAfter?: HighlightStubPhaseOrFn<Out>;
|
|
104
|
+
/**
|
|
105
|
+
* Demo slots shown when a **step throws** (failed verify / act error) -
|
|
106
|
+
* before the error panel. Success-only {@link Instruction.stubAfter} never runs
|
|
107
|
+
* here; authors keep distinct BUG/FAIL copy. Named map; `{}` is valid.
|
|
108
|
+
* @example stubOnError: { card: { selector: ".alt-1", label: "Card at failure" } }
|
|
109
|
+
*/
|
|
110
|
+
stubOnError?: HighlightStubPhaseOrFn<Out>;
|
|
104
111
|
/**
|
|
105
112
|
* Multi-step demo captions ("yap" slides) with Next between each -
|
|
106
113
|
* not block lifecycle. Explains a long process; optional `selector` rings
|
|
Binary file
|
|
Binary file
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"prove:auto-cli": "node scripts/drive-auto-cli.mjs",
|
|
17
17
|
"graph": "waygraph graph",
|
|
18
18
|
"graph:mermaid": "waygraph graph --mermaid",
|
|
19
|
+
"demo:stub-on-error": "waygraph demo --blocks stubOnErrorFlow --auto-next --fast --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"locked_out_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --title \"stubOnError prove\"",
|
|
19
20
|
"prove:login-branch": "node scripts/prove-wrong-login-runtime.mjs",
|
|
20
21
|
"prove:graph": "node scripts/prove-auto-graph.mjs",
|
|
21
22
|
"prove:add-to-cart-menu": "node scripts/prove-add-to-cart-menu.mjs",
|
|
@@ -26,6 +26,7 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
26
26
|
selector: ".complete-header",
|
|
27
27
|
label: "Order confirmed",
|
|
28
28
|
detail: "Thank you for your order!",
|
|
29
|
+
duration: true,
|
|
29
30
|
},
|
|
30
31
|
},
|
|
31
32
|
// Shown AFTER act (order already placed) - selectors must match the
|
|
@@ -35,18 +36,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
35
36
|
caption: "Checkout is a short pipeline",
|
|
36
37
|
detail: "Cart -> info -> overview -> done. You just placed the order.",
|
|
37
38
|
tag: "YAP",
|
|
39
|
+
duration: true,
|
|
38
40
|
},
|
|
39
41
|
{
|
|
40
42
|
caption: "Completion page is the durable proof",
|
|
41
43
|
detail: "The thank-you header is what verify asserts on.",
|
|
42
44
|
tag: "YAP",
|
|
43
45
|
selector: ".complete-header",
|
|
46
|
+
duration: 2500,
|
|
47
|
+
fastMode: 700,
|
|
44
48
|
},
|
|
45
49
|
{
|
|
46
50
|
caption: "Episode continues after this yap",
|
|
47
51
|
detail: "Next step rings the same banner, then you move on.",
|
|
48
52
|
tag: "YAP",
|
|
49
53
|
selector: ".complete-header",
|
|
54
|
+
duration: true,
|
|
55
|
+
fastMode: 500,
|
|
50
56
|
},
|
|
51
57
|
],
|
|
52
58
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fastForwardComposeBlock } from "waygraph";
|
|
2
|
+
import { NavLoginBlock } from "./nav-login.block.js";
|
|
3
|
+
import { SubmitLoginForFlow } from "./methods/submit-login-for-flow.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kind: FFCompose (fast-forward)
|
|
7
|
+
* Helper: fastForwardComposeBlock
|
|
8
|
+
* Route: saucedemo-web/ (auth prefix)
|
|
9
|
+
*
|
|
10
|
+
* Collapses nav-login + submit-login into one opaque demo/run step so
|
|
11
|
+
* checkout demos skip watching auth. Use --ff-expand to gate inners again.
|
|
12
|
+
* loginFlow stays expanded for Sign In episode narration.
|
|
13
|
+
*/
|
|
14
|
+
export const FfOwnerAuthBlock = fastForwardComposeBlock("ff-owner-auth", [
|
|
15
|
+
NavLoginBlock,
|
|
16
|
+
SubmitLoginForFlow,
|
|
17
|
+
]);
|
|
@@ -26,9 +26,16 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
26
26
|
await page.locator("#login-button").click({ noWaitAfter: true });
|
|
27
27
|
},
|
|
28
28
|
async observe(page): Promise<LoginObserve> {
|
|
29
|
+
// Race inventory nav vs error banner - locked_out / wrong password show
|
|
30
|
+
// Epic sadface immediately; do not burn the full inventory timeout.
|
|
31
|
+
const inventory = page.waitForURL(/\/inventory\.html/, { timeout: 8_000 }).then(() => "success" as const);
|
|
32
|
+
const errorBanner = page
|
|
33
|
+
.locator('[data-test="error"]')
|
|
34
|
+
.first()
|
|
35
|
+
.waitFor({ state: "visible", timeout: 8_000 })
|
|
36
|
+
.then(() => "failure" as const);
|
|
29
37
|
try {
|
|
30
|
-
await
|
|
31
|
-
return "success";
|
|
38
|
+
return await Promise.race([inventory, errorBanner]);
|
|
32
39
|
} catch {
|
|
33
40
|
return "failure";
|
|
34
41
|
}
|
|
@@ -44,5 +51,14 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
44
51
|
submit: { selector: "#login-button", label: "Login" },
|
|
45
52
|
},
|
|
46
53
|
stubAfter: {},
|
|
54
|
+
// Fail-path rings (demo step throw only) - locked-out / wrong-password
|
|
55
|
+
// bug-repro narration; success never shows these.
|
|
56
|
+
stubOnError: {
|
|
57
|
+
error: {
|
|
58
|
+
selector: '[data-test="error"]',
|
|
59
|
+
label: "Login error banner",
|
|
60
|
+
duration: true,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
47
63
|
},
|
|
48
64
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
|
|
2
2
|
import type {
|
|
3
|
-
LoginPage,
|
|
4
3
|
LoggedIn,
|
|
5
4
|
ItemInCart,
|
|
6
5
|
CartPage,
|
|
@@ -8,8 +7,7 @@ import type {
|
|
|
8
7
|
CheckoutOverviewPage,
|
|
9
8
|
OrderComplete,
|
|
10
9
|
} from "../states/checkout.states.js";
|
|
11
|
-
import {
|
|
12
|
-
import { SubmitLoginForFlow } from "../blocks/saucedemo-web/methods/submit-login-for-flow.js";
|
|
10
|
+
import { FfOwnerAuthBlock } from "../blocks/saucedemo-web/ff-owner-auth.block.js";
|
|
13
11
|
import { AddToCartBlock } from "../blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.js";
|
|
14
12
|
import { NavCartBlock } from "../blocks/saucedemo-web/cart/nav-cart.block.js";
|
|
15
13
|
import { NavCheckoutInfoBlock } from "../blocks/saucedemo-web/cart/nav-checkout-info.block.js";
|
|
@@ -18,18 +16,18 @@ import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/meth
|
|
|
18
16
|
|
|
19
17
|
const engine = new Engine({ headless: false, slowMo: 250 });
|
|
20
18
|
|
|
21
|
-
// Atomic nav/action per real URL - the shape `waygraph auto` discovers.
|
|
22
|
-
//
|
|
23
|
-
//
|
|
19
|
+
// Atomic nav/action per real URL after auth - the shape `waygraph auto` discovers.
|
|
20
|
+
// Auth is one FFCompose step (ff-owner-auth) so demo pacing skips login yap;
|
|
21
|
+
// use --ff-expand (or loginFlow) when you need per-step auth narration.
|
|
24
22
|
//
|
|
25
|
-
// Demo narration showcase (waygraph 0.11+):
|
|
26
|
-
// -
|
|
27
|
-
// -
|
|
23
|
+
// Demo narration showcase (waygraph 0.11+ / FFCompose 0.12):
|
|
24
|
+
// - FF opaque: one gate for auth; stubs on interesting post-login blocks
|
|
25
|
+
// - Block stubs: add-to-cart / finish-order stubAfter
|
|
26
|
+
// - Flow fixtures: ticket-style captions
|
|
28
27
|
// - Slides on finish-order: multi-step yap with Next between slides
|
|
29
28
|
export const checkoutFlow = withHighlightFixtures(
|
|
30
29
|
withTitle(
|
|
31
30
|
engine.defineFlow<
|
|
32
|
-
LoginPage,
|
|
33
31
|
LoggedIn,
|
|
34
32
|
ItemInCart,
|
|
35
33
|
CartPage,
|
|
@@ -38,8 +36,7 @@ export const checkoutFlow = withHighlightFixtures(
|
|
|
38
36
|
OrderComplete
|
|
39
37
|
>([
|
|
40
38
|
start,
|
|
41
|
-
|
|
42
|
-
SubmitLoginForFlow,
|
|
39
|
+
FfOwnerAuthBlock,
|
|
43
40
|
AddToCartBlock,
|
|
44
41
|
NavCartBlock,
|
|
45
42
|
NavCheckoutInfoBlock,
|
|
@@ -50,15 +47,27 @@ export const checkoutFlow = withHighlightFixtures(
|
|
|
50
47
|
"Owner: Full Checkout",
|
|
51
48
|
),
|
|
52
49
|
{
|
|
50
|
+
"ff-owner-auth": {
|
|
51
|
+
stubAfter: {
|
|
52
|
+
inventory: {
|
|
53
|
+
label: "Signed in (fast-forward)",
|
|
54
|
+
detail: "Auth collapsed via FFCompose - interesting work starts here.",
|
|
55
|
+
tag: "AC",
|
|
56
|
+
duration: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
// Kept for --ff-expand (inners become separate steps again).
|
|
53
61
|
"submit-login": {
|
|
54
62
|
stubBefore: {
|
|
55
63
|
username: {
|
|
56
64
|
label: "Demo user",
|
|
57
|
-
detail: "standard_user from Mem
|
|
65
|
+
detail: "standard_user from Mem - library block stays neutral.",
|
|
58
66
|
tag: "AC",
|
|
67
|
+
duration: true,
|
|
59
68
|
},
|
|
60
|
-
password: { label: "Password", detail: "Filled from Mem credentials." },
|
|
61
|
-
submit: { label: "Sign in", tag: "GATE" },
|
|
69
|
+
password: { label: "Password", detail: "Filled from Mem credentials.", duration: 1500 },
|
|
70
|
+
submit: { label: "Sign in", tag: "GATE", duration: true, fastMode: 500 },
|
|
62
71
|
},
|
|
63
72
|
},
|
|
64
73
|
"add-to-cart": {
|
|
@@ -67,6 +76,7 @@ export const checkoutFlow = withHighlightFixtures(
|
|
|
67
76
|
label: "Cart now has the item",
|
|
68
77
|
detail: "Badge updates after Add to cart.",
|
|
69
78
|
tag: "AC",
|
|
79
|
+
duration: true,
|
|
70
80
|
},
|
|
71
81
|
},
|
|
72
82
|
},
|
|
@@ -76,9 +86,10 @@ export const checkoutFlow = withHighlightFixtures(
|
|
|
76
86
|
label: "Order placed",
|
|
77
87
|
detail: "Completion header is the durable proof.",
|
|
78
88
|
tag: "GATE",
|
|
89
|
+
duration: 2500,
|
|
90
|
+
fastMode: 700,
|
|
79
91
|
},
|
|
80
92
|
},
|
|
81
|
-
// Optional: fixture can replace block slides; leave unset to use block slides.
|
|
82
93
|
},
|
|
83
94
|
},
|
|
84
95
|
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Engine,
|
|
3
|
+
start,
|
|
4
|
+
end,
|
|
5
|
+
withTitle,
|
|
6
|
+
withSessionReset,
|
|
7
|
+
withExpectedFailure,
|
|
8
|
+
withHighlightFixtures,
|
|
9
|
+
Trait,
|
|
10
|
+
} from "waygraph";
|
|
11
|
+
import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
|
|
12
|
+
import { SubmitLoginActionBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
|
|
13
|
+
|
|
14
|
+
const engine = new Engine();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prove stubOnError (waygraph 0.12.4+): force verify to demand /inventory.html
|
|
18
|
+
* while mem is locked_out_user — step throws, demo rings the error banner
|
|
19
|
+
* before the error panel, then expected-failure styling.
|
|
20
|
+
*
|
|
21
|
+
* waygraph demo --blocks stubOnErrorFlow --auto-next --fast \\
|
|
22
|
+
* --data '{"saucedemo.credentials":{"username":"locked_out_user","password":"secret_sauce"}}'
|
|
23
|
+
*/
|
|
24
|
+
const forcedInventoryVerify = withSessionReset(
|
|
25
|
+
engine
|
|
26
|
+
.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end])
|
|
27
|
+
.withBlockVerify("submit-login", [Trait.url({ pathname: "/inventory.html" })]),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const stubOnErrorFlow = withHighlightFixtures(
|
|
31
|
+
withExpectedFailure(
|
|
32
|
+
withTitle(forcedInventoryVerify, "stubOnError: locked_out fail rings"),
|
|
33
|
+
"Intentional verify fail - stubOnError rings the error banner before the panel",
|
|
34
|
+
),
|
|
35
|
+
{
|
|
36
|
+
"submit-login": {
|
|
37
|
+
stubOnError: {
|
|
38
|
+
error: {
|
|
39
|
+
label: "BUG · locked out still on login",
|
|
40
|
+
detail: "Verify demanded inventory; banner is the proof point.",
|
|
41
|
+
tag: "FAIL",
|
|
42
|
+
duration: true,
|
|
43
|
+
fastMode: 500,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|