waygraph 0.10.9 → 0.11.1

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.
@@ -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/trait.d.ts CHANGED
@@ -33,7 +33,12 @@ export interface Trait {
33
33
  export declare function urlMatches(pattern: URLPatternInit): Trait;
34
34
  /** Passes when `selector`'s text content is exactly `expected`. */
35
35
  export declare function textEquals(selector: string, expected: string): Trait;
36
- /** Passes once `selector` is visible on the page, waiting for it to appear. */
36
+ /**
37
+ * Passes once at least one element matching `selector` is visible on the page,
38
+ * waiting for it to appear. Uses `.first()` so list selectors (e.g. one row per
39
+ * message/document) don't trip Playwright strict mode - the check means
40
+ * "something matching is visible", not "the selector is unambiguous".
41
+ */
37
42
  export declare function visible(selector: string): Trait;
38
43
  /**
39
44
  * Discoverable entry point for the built-in Trait factories - type `Trait.` in
package/dist/trait.js CHANGED
@@ -36,12 +36,17 @@ export function textEquals(selector, expected) {
36
36
  },
37
37
  };
38
38
  }
39
- /** Passes once `selector` is visible on the page, waiting for it to appear. */
39
+ /**
40
+ * Passes once at least one element matching `selector` is visible on the page,
41
+ * waiting for it to appear. Uses `.first()` so list selectors (e.g. one row per
42
+ * message/document) don't trip Playwright strict mode - the check means
43
+ * "something matching is visible", not "the selector is unambiguous".
44
+ */
40
45
  export function visible(selector) {
41
46
  return {
42
47
  name: `visible(${selector})`,
43
48
  async check(page) {
44
- await page.locator(selector).waitFor({ state: "visible" });
49
+ await page.locator(selector).first().waitFor({ state: "visible" });
45
50
  return true;
46
51
  },
47
52
  };
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
- * Optional. Arbitrary highlight points for step-mode tooling to show, in
92
- * order, after this Block resolves - independent of `verify`, for
93
- * calling out something worth a human's attention regardless of whether
94
- * it's also being asserted ("remember this signature ID" isn't a
95
- * pass/fail check). A function form picks different highlights per
96
- * resolved `Out`, same as `verify`. Purely additive UI narration - never
97
- * consulted by `runGraph`/`connect`/anything that decides behavior.
98
- * @example highlights: (out) => [{ selector: "#signature-id", label: "remember this ID" }]
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,36 @@ 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
- highlights: () => [{ selector: ".complete-header", label: "remember this: order confirmed" }],
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
+ // Shown AFTER act (order already placed) - selectors must match the
32
+ // completion page, not #finish on the overview.
33
+ slides: [
34
+ {
35
+ caption: "Checkout is a short pipeline",
36
+ detail: "Cart -> info -> overview -> done. You just placed the order.",
37
+ tag: "YAP",
38
+ },
39
+ {
40
+ caption: "Completion page is the durable proof",
41
+ detail: "The thank-you header is what verify asserts on.",
42
+ tag: "YAP",
43
+ selector: ".complete-header",
44
+ },
45
+ {
46
+ caption: "Episode continues after this yap",
47
+ detail: "Next step rings the same banner, then you move on.",
48
+ tag: "YAP",
49
+ selector: ".complete-header",
50
+ },
51
+ ],
22
52
  },
23
53
  });
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
30
30
  },
31
31
  },
32
32
  ],
33
- highlights: () => [{ selector: InventorySel.cartBadge, label: "remember this: cart now has the item" }],
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
- export const checkoutFlow = withTitle(
25
- engine.defineFlow<
26
- LoginPage,
27
- LoggedIn,
28
- ItemInCart,
29
- CartPage,
30
- CheckoutInfoPage,
31
- CheckoutOverviewPage,
32
- OrderComplete
33
- >([
34
- start,
35
- NavLoginBlock,
36
- SubmitLoginForFlow,
37
- AddToCartBlock,
38
- NavCartBlock,
39
- NavCheckoutInfoBlock,
40
- SubmitCheckoutInfoBlock,
41
- FinishOrderBlock,
42
- end,
43
- ]),
44
- "Owner: Full Checkout",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.10.9",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "baseUrl": "https://www.saucedemo.com"
24
24
  },
25
25
  "dependencies": {
26
- "waygraph": "^0.10.9"
26
+ "waygraph": "^0.11.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@playwright/test": "^1.55.0",
@@ -18,6 +18,36 @@ 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
- highlights: () => [{ selector: ".complete-header", label: "remember this: order confirmed" }],
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
+ // Shown AFTER act (order already placed) - selectors must match the
32
+ // completion page, not #finish on the overview.
33
+ slides: [
34
+ {
35
+ caption: "Checkout is a short pipeline",
36
+ detail: "Cart -> info -> overview -> done. You just placed the order.",
37
+ tag: "YAP",
38
+ },
39
+ {
40
+ caption: "Completion page is the durable proof",
41
+ detail: "The thank-you header is what verify asserts on.",
42
+ tag: "YAP",
43
+ selector: ".complete-header",
44
+ },
45
+ {
46
+ caption: "Episode continues after this yap",
47
+ detail: "Next step rings the same banner, then you move on.",
48
+ tag: "YAP",
49
+ selector: ".complete-header",
50
+ },
51
+ ],
22
52
  },
23
53
  });
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
30
30
  },
31
31
  },
32
32
  ],
33
- highlights: () => [{ selector: InventorySel.cartBadge, label: "remember this: cart now has the item" }],
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
  });