waygraph 0.7.16 → 0.10.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 +90 -52
- package/dist/auto-explore-run.js +13 -7
- package/dist/auto-explore.js +47 -36
- package/dist/cli.d.ts +8 -12
- package/dist/cli.js +229 -249
- package/dist/engine.d.ts +53 -11
- package/dist/engine.js +81 -8
- package/dist/graph.d.ts +2 -1
- package/dist/graph.js +51 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/README.md +19 -5
- package/examples/saucedemo/docs/CONVENTION.md +22 -18
- package/examples/saucedemo/docs/HELPERS.md +152 -8
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/package.json +11 -5
- package/examples/saucedemo/scripts/capture-wrong-login.mjs +1 -1
- package/examples/saucedemo/scripts/drive-auto-cli.mjs +93 -0
- package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +1 -1
- package/examples/saucedemo/scripts/show-auto-menu.mjs +72 -0
- package/examples/saucedemo/src/blocks/SITE-MAP.md +11 -8
- package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +3 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
- package/{templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
- package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
- package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +4 -4
- package/examples/saucedemo/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
- package/{templates/quickstart/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
- package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -0
- package/examples/saucedemo/src/flows/cart-mem.flow.ts +1 -1
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +4 -4
- package/examples/saucedemo/src/flows/checkout.flow.ts +4 -4
- package/examples/saucedemo/src/flows/login.flow.ts +1 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +3 -3
- package/examples/saucedemo/src/flows/smoke.flow.ts +2 -2
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
- package/examples/saucedemo/tests/flow-block-verify.spec.ts +1 -1
- package/examples/saucedemo/tests/submit-login-branch.spec.ts +1 -1
- package/package.json +1 -1
- package/templates/quickstart/README-saucedemo-commands.md +72 -0
- package/templates/quickstart/docs/CONVENTION.md +5 -5
- package/templates/quickstart/docs/HELPERS.md +152 -8
- package/templates/quickstart/package.json +9 -5
- package/templates/quickstart/scripts/drive-auto-cli.mjs +93 -0
- package/templates/quickstart/src/blocks/SITE-MAP.md +11 -8
- package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
- package/{examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
- package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
- package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
- package/templates/quickstart/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
- package/{examples/saucedemo/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
- package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -0
- package/templates/quickstart/src/flows/cart-mem.flow.ts +1 -1
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +4 -4
- package/templates/quickstart/src/flows/checkout.flow.ts +4 -4
- package/templates/quickstart/src/flows/login.flow.ts +1 -1
- package/templates/quickstart/src/flows/shop.flow.ts +3 -3
- package/templates/quickstart/src/flows/smoke.flow.ts +2 -2
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
- package/templates/scaffold/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -561,12 +561,54 @@ export type NavClickBlock<Out extends Checkpoint<string>> = NavBlock<Out>;
|
|
|
561
561
|
export declare function defineNavClickBlock<Out extends Checkpoint<string>>(options: NavClickBlockOptions<Out>): NavClickBlock<Out>;
|
|
562
562
|
/**
|
|
563
563
|
* TypeScript salt over {@link defineBlock} for one-shot non-nav app steps
|
|
564
|
-
* (submit, finish, logout). Same runtime Block;
|
|
565
|
-
* so auto/docs can tell
|
|
566
|
-
* Does not force `instanceOptions` - use
|
|
564
|
+
* (submit, upload, finish, logout) - **methods** on a page. Same runtime Block;
|
|
565
|
+
* marks `__waygraphSalt = "method"` so auto/docs can tell Method from bare
|
|
566
|
+
* {@link defineBlock}. Does not force `instanceOptions` - use
|
|
567
|
+
* {@link defineEffectBlock} when the auto menu should spawn one row per live
|
|
568
|
+
* DOM instance. Prefer hanging methods off a {@link definePageBlock} hub.
|
|
567
569
|
*/
|
|
568
|
-
export type
|
|
569
|
-
|
|
570
|
+
export type MethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = DefinedBlock<In, Out>;
|
|
571
|
+
/** @deprecated Prefer {@link MethodBlock} - same type; Action was the 0.7 name. */
|
|
572
|
+
export type ActionBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> = MethodBlock<In, Out>;
|
|
573
|
+
export declare function defineMethodBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: Parameters<typeof defineBlock<In, Out>>[0]): MethodBlock<In, Out>;
|
|
574
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
575
|
+
export declare const defineActionBlock: typeof defineMethodBlock;
|
|
576
|
+
/** One Block (or lazy factory) registered on a {@link PageBlock}. */
|
|
577
|
+
export type PageMethodEntry = DefinedBlock<any, any> | (() => DefinedBlock<any, any>);
|
|
578
|
+
export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
579
|
+
name: string;
|
|
580
|
+
description?: string;
|
|
581
|
+
/** Checkpoint tag for this screen (hub). */
|
|
582
|
+
checkpoint: Out["__state"];
|
|
583
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
584
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
585
|
+
requires?: readonly MemKey<any>[];
|
|
586
|
+
/**
|
|
587
|
+
* Methods available on this page. Array, or a record of Blocks / `() => Block`
|
|
588
|
+
* factories (lazy so the page object can colocate method definitions).
|
|
589
|
+
*/
|
|
590
|
+
methods?: readonly PageMethodEntry[] | Record<string, PageMethodEntry>;
|
|
591
|
+
/**
|
|
592
|
+
* Optional deep-link onto this page (`goto`). XOR with `click`. Omit when the
|
|
593
|
+
* hub is arrival-only (previous Block already resolved here).
|
|
594
|
+
*/
|
|
595
|
+
url?: string | ((mem: MemPage) => string);
|
|
596
|
+
/** Optional click-nav onto this page. XOR with `url`. */
|
|
597
|
+
click?: string | ((mem: MemPage) => string);
|
|
598
|
+
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
601
|
+
* Page hub Block: one Checkpoint = this screen; methods hang off the hub for
|
|
602
|
+
* readability / auto grouping. Runtime is still a Block (`__waygraphKind = "page"`).
|
|
603
|
+
* `act` is goto/click when `url`/`click` is set, otherwise a no-op (already here).
|
|
604
|
+
* Child methods stay ordinary Blocks for graph edges - the page only registers them.
|
|
605
|
+
*/
|
|
606
|
+
export type PageBlock<Out extends Checkpoint<string>> = NavBlock<Out> & {
|
|
607
|
+
/** Resolved method Blocks registered at define time. */
|
|
608
|
+
readonly methods: readonly DefinedBlock<any, any>[];
|
|
609
|
+
};
|
|
610
|
+
/** @see {@link PageBlockOptions} */
|
|
611
|
+
export declare function definePageBlock<Out extends Checkpoint<string>>(options: PageBlockOptions<Out>): PageBlock<Out>;
|
|
570
612
|
/**
|
|
571
613
|
* TypeScript salt over {@link defineBlock}: same runtime Block, but the type
|
|
572
614
|
* forces `requires` + `instanceOptions` so `waygraph auto` can list one menu
|
|
@@ -610,14 +652,14 @@ export declare function defineMemNavBlock<Out extends Checkpoint<string>>(option
|
|
|
610
652
|
instanceOptions: NonNullable<Block<Checkpoint<string>, Out>["instanceOptions"]>;
|
|
611
653
|
}): MemNavBlock<Out>;
|
|
612
654
|
/**
|
|
613
|
-
* "Where am I" - reverse-matches a live `page` against every NavBlock
|
|
614
|
-
* `library`, in order, returning the first one whose own `verify`
|
|
615
|
-
* pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
616
|
-
* only:
|
|
617
|
-
* whose whole job is already "confirm arrival here," so it's a reliable
|
|
655
|
+
* "Where am I" - reverse-matches a live `page` against every NavBlock /
|
|
656
|
+
* PageBlock in `library`, in order, returning the first one whose own `verify`
|
|
657
|
+
* Traits all pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
658
|
+
* Nav/Page hubs only: each has exactly one fixed `checkpoint` and a `verify`
|
|
659
|
+
* list whose whole job is already "confirm arrival here," so it's a reliable
|
|
618
660
|
* fingerprint - a regular `defineBlock` can branch to different `Out` tags
|
|
619
661
|
* depending on runtime evidence, so its `verify` alone doesn't identify a
|
|
620
|
-
* single state the way a
|
|
662
|
+
* single state the way a hub's does.
|
|
621
663
|
*
|
|
622
664
|
* The primitive `waygraph auto`'s discovered graph is *for* - the graph says
|
|
623
665
|
* what pages exist and how to reach them; `locate()` is how an autonomous
|
package/dist/engine.js
CHANGED
|
@@ -696,15 +696,88 @@ export function defineNavClickBlock(options) {
|
|
|
696
696
|
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
697
697
|
});
|
|
698
698
|
}
|
|
699
|
-
export function
|
|
699
|
+
export function defineMethodBlock(base) {
|
|
700
700
|
const built = defineBlock(base);
|
|
701
701
|
Object.defineProperty(built, "__waygraphSalt", {
|
|
702
|
-
value: "
|
|
702
|
+
value: "method",
|
|
703
703
|
enumerable: false,
|
|
704
704
|
configurable: false,
|
|
705
705
|
});
|
|
706
706
|
return built;
|
|
707
707
|
}
|
|
708
|
+
/** @deprecated Prefer {@link defineMethodBlock} - identical helper (0.7 name). */
|
|
709
|
+
export const defineActionBlock = defineMethodBlock;
|
|
710
|
+
function resolvePageMethods(methods) {
|
|
711
|
+
if (!methods)
|
|
712
|
+
return [];
|
|
713
|
+
const entries = Array.isArray(methods) ? methods : Object.values(methods);
|
|
714
|
+
return entries.map((entry) => (typeof entry === "function" ? entry() : entry));
|
|
715
|
+
}
|
|
716
|
+
/** @see {@link PageBlockOptions} */
|
|
717
|
+
export function definePageBlock(options) {
|
|
718
|
+
if (options.url !== undefined && options.click !== undefined) {
|
|
719
|
+
throw new Error(`definePageBlock("${options.name}"): pass url XOR click (or neither for arrival-only hub)`);
|
|
720
|
+
}
|
|
721
|
+
const methodList = resolvePageMethods(options.methods);
|
|
722
|
+
const built = defineBlock({
|
|
723
|
+
name: options.name,
|
|
724
|
+
...(options.description ? { description: options.description } : {}),
|
|
725
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
726
|
+
...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
|
|
727
|
+
instruction: {
|
|
728
|
+
async act(page, _input, mem) {
|
|
729
|
+
if (options.url !== undefined) {
|
|
730
|
+
const url = typeof options.url === "function" ? options.url(mem) : options.url;
|
|
731
|
+
await page.goto(url);
|
|
732
|
+
}
|
|
733
|
+
else if (options.click !== undefined) {
|
|
734
|
+
const selector = typeof options.click === "function" ? options.click(mem) : options.click;
|
|
735
|
+
await page.locator(selector).click();
|
|
736
|
+
}
|
|
737
|
+
// else: arrival-only hub — previous Block already landed here
|
|
738
|
+
},
|
|
739
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
740
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
741
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
745
|
+
value: "page",
|
|
746
|
+
enumerable: false,
|
|
747
|
+
configurable: false,
|
|
748
|
+
});
|
|
749
|
+
Object.defineProperty(built, "__waygraphSalt", {
|
|
750
|
+
value: "page",
|
|
751
|
+
enumerable: false,
|
|
752
|
+
configurable: false,
|
|
753
|
+
});
|
|
754
|
+
Object.defineProperty(built, "methods", {
|
|
755
|
+
value: Object.freeze([...methodList]),
|
|
756
|
+
enumerable: true,
|
|
757
|
+
configurable: false,
|
|
758
|
+
writable: false,
|
|
759
|
+
});
|
|
760
|
+
Object.defineProperty(built, "__waygraphMethods", {
|
|
761
|
+
value: methodList.map((m) => m.name),
|
|
762
|
+
enumerable: false,
|
|
763
|
+
configurable: false,
|
|
764
|
+
});
|
|
765
|
+
if (options.url !== undefined || options.click !== undefined) {
|
|
766
|
+
Object.defineProperty(built, "__waygraphPageDeepLink", {
|
|
767
|
+
value: true,
|
|
768
|
+
enumerable: false,
|
|
769
|
+
configurable: false,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (options.click !== undefined) {
|
|
773
|
+
Object.defineProperty(built, "__waygraphNavClick", {
|
|
774
|
+
value: options.click,
|
|
775
|
+
enumerable: false,
|
|
776
|
+
configurable: false,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return built;
|
|
780
|
+
}
|
|
708
781
|
/**
|
|
709
782
|
* {@link defineBlock} constrained for effect-style instance menus: `requires` +
|
|
710
783
|
* `instanceOptions` are mandatory. Prefer this when the Block adds / removes /
|
|
@@ -743,14 +816,14 @@ export function defineMemNavBlock(options) {
|
|
|
743
816
|
return built;
|
|
744
817
|
}
|
|
745
818
|
/**
|
|
746
|
-
* "Where am I" - reverse-matches a live `page` against every NavBlock
|
|
747
|
-
* `library`, in order, returning the first one whose own `verify`
|
|
748
|
-
* pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
749
|
-
* only:
|
|
750
|
-
* whose whole job is already "confirm arrival here," so it's a reliable
|
|
819
|
+
* "Where am I" - reverse-matches a live `page` against every NavBlock /
|
|
820
|
+
* PageBlock in `library`, in order, returning the first one whose own `verify`
|
|
821
|
+
* Traits all pass (its `checkpoint`), or `null` if none match. Deliberately
|
|
822
|
+
* Nav/Page hubs only: each has exactly one fixed `checkpoint` and a `verify`
|
|
823
|
+
* list whose whole job is already "confirm arrival here," so it's a reliable
|
|
751
824
|
* fingerprint - a regular `defineBlock` can branch to different `Out` tags
|
|
752
825
|
* depending on runtime evidence, so its `verify` alone doesn't identify a
|
|
753
|
-
* single state the way a
|
|
826
|
+
* single state the way a hub's does.
|
|
754
827
|
*
|
|
755
828
|
* The primitive `waygraph auto`'s discovered graph is *for* - the graph says
|
|
756
829
|
* what pages exist and how to reach them; `locate()` is how an autonomous
|
package/dist/graph.d.ts
CHANGED
|
@@ -48,7 +48,8 @@ export declare function toMermaid(graph: WaygraphGraph): string;
|
|
|
48
48
|
/**
|
|
49
49
|
* Blocks present on disk but never wired into a `.flow.ts` defineFlow array.
|
|
50
50
|
* Required before `chain auto` can build a path from the discovered graph -
|
|
51
|
-
* orphan Blocks are intentionally excluded from auto shorthand.
|
|
51
|
+
* orphan Blocks are intentionally excluded from auto shorthand. Arrival-only
|
|
52
|
+
* Page hubs and Blocks listed on a Page's `methods` are not orphans.
|
|
52
53
|
*/
|
|
53
54
|
export declare function findOrphanBlocks(projectDir: string): Promise<OrphanBlock[]>;
|
|
54
55
|
/**
|
package/dist/graph.js
CHANGED
|
@@ -77,7 +77,8 @@ function exportNameToBlockName(exportName) {
|
|
|
77
77
|
/**
|
|
78
78
|
* Blocks present on disk but never wired into a `.flow.ts` defineFlow array.
|
|
79
79
|
* Required before `chain auto` can build a path from the discovered graph -
|
|
80
|
-
* orphan Blocks are intentionally excluded from auto shorthand.
|
|
80
|
+
* orphan Blocks are intentionally excluded from auto shorthand. Arrival-only
|
|
81
|
+
* Page hubs and Blocks listed on a Page's `methods` are not orphans.
|
|
81
82
|
*/
|
|
82
83
|
export async function findOrphanBlocks(projectDir) {
|
|
83
84
|
const blockFiles = discoverBlocks(projectDir);
|
|
@@ -88,6 +89,27 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
88
89
|
referenced.add(ref);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
const pageMethodNames = new Set();
|
|
93
|
+
for (const file of blockFiles) {
|
|
94
|
+
let mod;
|
|
95
|
+
try {
|
|
96
|
+
mod = await importModule(file);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (const exported of Object.values(mod)) {
|
|
102
|
+
if (!exported || typeof exported !== "object")
|
|
103
|
+
continue;
|
|
104
|
+
if (exported.__waygraphKind !== "page")
|
|
105
|
+
continue;
|
|
106
|
+
const names = exported.__waygraphMethods;
|
|
107
|
+
if (Array.isArray(names)) {
|
|
108
|
+
for (const n of names)
|
|
109
|
+
pageMethodNames.add(n);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
91
113
|
const orphans = [];
|
|
92
114
|
for (const file of blockFiles) {
|
|
93
115
|
const relFile = relative(projectDir, file);
|
|
@@ -98,7 +120,11 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
98
120
|
const exportName = m[1];
|
|
99
121
|
if (referenced.has(exportName))
|
|
100
122
|
continue;
|
|
101
|
-
if (!/defineBlock|defineActionBlock|defineEffectBlock|defineMemEffectBlock|defineNavBlock|defineNavClickBlock|defineMemNavBlock/.test(src))
|
|
123
|
+
if (!/defineBlock|defineMethodBlock|defineActionBlock|definePageBlock|defineEffectBlock|defineMemEffectBlock|defineNavBlock|defineNavClickBlock|defineMemNavBlock/.test(src))
|
|
124
|
+
continue;
|
|
125
|
+
// Arrival-only Page hubs are never orphans (even if import fails without tsx).
|
|
126
|
+
const assignSlice = src.slice(m.index, m.index + exportName.length + 80);
|
|
127
|
+
if (/=\s*definePageBlock\b/.test(assignSlice))
|
|
102
128
|
continue;
|
|
103
129
|
let block = exportNameToBlockName(exportName);
|
|
104
130
|
try {
|
|
@@ -106,6 +132,13 @@ export async function findOrphanBlocks(projectDir) {
|
|
|
106
132
|
const exported = mod[exportName];
|
|
107
133
|
if (isBlockLike(exported))
|
|
108
134
|
block = exported.name;
|
|
135
|
+
if (exported &&
|
|
136
|
+
typeof exported === "object" &&
|
|
137
|
+
exported.__waygraphKind === "page") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (pageMethodNames.has(block))
|
|
141
|
+
continue;
|
|
109
142
|
}
|
|
110
143
|
catch {
|
|
111
144
|
// import may fail in partial projects - keep derived name
|
|
@@ -155,7 +188,8 @@ function isBlockLike(val) {
|
|
|
155
188
|
}
|
|
156
189
|
/** Set by `defineNavBlock` (non-enumerable) - see src/engine.ts. */
|
|
157
190
|
function isNavBlockMarked(val) {
|
|
158
|
-
|
|
191
|
+
const kind = val.__waygraphKind;
|
|
192
|
+
return kind === "nav" || kind === "page";
|
|
159
193
|
}
|
|
160
194
|
/**
|
|
161
195
|
* Resolves `typeName` to every literal Checkpoint tag it can mean, within
|
|
@@ -242,9 +276,9 @@ function extractDefineBlockGenerics(src) {
|
|
|
242
276
|
// alternation so both shapes come out of the same two capture groups.
|
|
243
277
|
// Effect helpers are TypeScript salt over defineBlock - same In/Out edges.
|
|
244
278
|
const arg = String.raw `(Checkpoint<[^>]*>|[A-Za-z_$][\w]*)`;
|
|
245
|
-
const re = new RegExp(String.raw `define(?:(?:Mem)?Effect|Action)?Block<\s*${arg}\s*,\s*${arg}\s*>`, "g");
|
|
246
|
-
// Matches: defineBlock | defineActionBlock | defineEffectBlock |
|
|
247
|
-
// (Nav / NavClick use runtime markers, not this regex.)
|
|
279
|
+
const re = new RegExp(String.raw `define(?:(?:Mem)?Effect|Method|Action)?Block<\s*${arg}\s*,\s*${arg}\s*>`, "g");
|
|
280
|
+
// Matches: defineBlock | defineMethodBlock | defineActionBlock | defineEffectBlock |
|
|
281
|
+
// defineMemEffectBlock (Nav / NavClick / Page use runtime markers, not this regex.)
|
|
248
282
|
const calls = [];
|
|
249
283
|
let m;
|
|
250
284
|
while ((m = re.exec(src)) !== null) {
|
|
@@ -289,16 +323,22 @@ export async function discoverGraph(projectDir) {
|
|
|
289
323
|
const blockName = exported.name;
|
|
290
324
|
if (isNavBlockMarked(exported)) {
|
|
291
325
|
try {
|
|
292
|
-
// NavBlock
|
|
293
|
-
// always exactly `checkpoint(options.checkpoint)`.
|
|
326
|
+
// NavBlock / PageBlock resolve() ignores input - always checkpoint(tag).
|
|
294
327
|
const resolveFn = exported.instruction.resolve;
|
|
295
328
|
const resolved = await resolveFn(undefined);
|
|
296
329
|
const to = resolved.__state;
|
|
297
330
|
nodeTags.add(to);
|
|
298
|
-
|
|
331
|
+
// Arrival-only Page hubs register the Checkpoint node but do not emit
|
|
332
|
+
// a Start-here edge (no deep link). Methods own the action edges.
|
|
333
|
+
const isPage = exported.__waygraphKind === "page";
|
|
334
|
+
const deepLink = !isPage ||
|
|
335
|
+
exported.__waygraphPageDeepLink === true;
|
|
336
|
+
if (deepLink) {
|
|
337
|
+
edges.push({ block: blockName, file: relFile, from: "*", to, kind: "nav" });
|
|
338
|
+
}
|
|
299
339
|
}
|
|
300
340
|
catch {
|
|
301
|
-
skipped.push({ block: blockName, file: relFile, reason: "NavBlock
|
|
341
|
+
skipped.push({ block: blockName, file: relFile, reason: "NavBlock/PageBlock resolve() threw" });
|
|
302
342
|
}
|
|
303
343
|
continue;
|
|
304
344
|
}
|
|
@@ -307,7 +347,7 @@ export async function discoverGraph(projectDir) {
|
|
|
307
347
|
skipped.push({
|
|
308
348
|
block: blockName,
|
|
309
349
|
file: relFile,
|
|
310
|
-
reason: "no defineBlock/defineEffectBlock<In, Out> generic call found",
|
|
350
|
+
reason: "no defineBlock/defineMethodBlock/defineEffectBlock<In, Out> generic call found",
|
|
311
351
|
});
|
|
312
352
|
continue;
|
|
313
353
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ 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, defineActionBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
-
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, ActionBlock, 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, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
|
|
6
|
+
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
7
7
|
export type { ActionPage } from "./types.js";
|
|
8
8
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
9
9
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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, defineActionBlock, 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, chainFlow, } from "./engine.js";
|
|
5
5
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
@@ -32,11 +32,25 @@ npx playwright install chromium
|
|
|
32
32
|
| Want | Command |
|
|
33
33
|
|------|---------|
|
|
34
34
|
| Headless suite | `npm test` |
|
|
35
|
-
| Interactive explore | `npm run auto` |
|
|
36
|
-
|
|
|
37
|
-
|
|
|
35
|
+
| Interactive explore (headed panel) | `npm run auto` |
|
|
36
|
+
| Interactive explore (terminal menu) | `npm run auto:cli` |
|
|
37
|
+
| Shop demo stepper | `npm run demo:step` |
|
|
38
|
+
| Shop demo autoplay | `npm run demo:autoplay` |
|
|
39
|
+
| **Cart bulk** demo (add-all → remove-all) | `npm run demo:bulk` |
|
|
40
|
+
| Cart bulk stepper | `npm run demo:bulk:step` |
|
|
38
41
|
| List flows | `npm run flows` |
|
|
39
42
|
|
|
43
|
+
**Demo vs auto vs run for cart bulk**
|
|
44
|
+
|
|
45
|
+
| | CLI | Needs `.flow.ts`? |
|
|
46
|
+
|--|-----|-------------------|
|
|
47
|
+
| **`run --blocks`** | `npm run run:bulk` — Block names + `--data` | No |
|
|
48
|
+
| **`demo --blocks`** | `npm run demo:bulk` — named flow + `--data` | Optional (`cartBulkFlow`) |
|
|
49
|
+
| **`auto`** | `npm run auto` / `auto:cli` | No — live menu |
|
|
50
|
+
| **`auto --blocks From To`** | path-find on graph | No — checkpoints only |
|
|
51
|
+
|
|
52
|
+
QA watch+record: `npm run demo:bulk:video` (`--auto-play-video`).
|
|
53
|
+
|
|
40
54
|
## Layout (short)
|
|
41
55
|
|
|
42
56
|
```text
|
|
@@ -45,10 +59,10 @@ src/blocks/
|
|
|
45
59
|
saucedemo-web/ # URL /
|
|
46
60
|
NAV.md
|
|
47
61
|
nav-login.block.ts
|
|
48
|
-
|
|
62
|
+
methods/ # submit-login, logout, …
|
|
49
63
|
inventory/ # URL /inventory.html
|
|
50
64
|
nav-item-detail.block.ts # MemNav
|
|
51
|
-
|
|
65
|
+
methods/*.effect.block.ts # Add / Remove
|
|
52
66
|
cart/ # URL /cart.html
|
|
53
67
|
checkout-step-one/
|
|
54
68
|
checkout-step-two/
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# saucedemo waygraph conventions
|
|
2
2
|
|
|
3
|
-
Atomic Blocks - one nav / effect /
|
|
3
|
+
Atomic Blocks - one nav / effect / method per real URL or on-page mutation so
|
|
4
4
|
`waygraph auto` can discover a real state graph (not a fat multi-page Block).
|
|
5
5
|
|
|
6
|
-
**TypeScript salt only.**
|
|
7
|
-
`
|
|
8
|
-
still a `Block` - there is no separate Effect or Nav engine.
|
|
6
|
+
**TypeScript salt only.** Helpers force a useful shape. At runtime everything is
|
|
7
|
+
still a `Block` - there is no separate Page / Effect / Nav engine.
|
|
9
8
|
|
|
10
9
|
**Multi-route consumers:** folder layout must mirror Next.js `app/` page routes -
|
|
11
10
|
see package docs `docs/consumer.html` (no invented `root/`, `landing/`, or
|
|
@@ -15,14 +14,18 @@ see package docs `docs/consumer.html` (no invented `root/`, `landing/`, or
|
|
|
15
14
|
|
|
16
15
|
| Kind | File | Helper | `page.goto`? |
|
|
17
16
|
|------|------|--------|--------------|
|
|
17
|
+
| Page | `*.page.block.ts` | `definePageBlock` | Optional deep-link; usually arrival-only hub |
|
|
18
18
|
| Nav | `nav-*.block.ts` | `defineNavBlock` / `defineMemNavBlock` | Yes (`url` XOR `click`) |
|
|
19
|
-
| Effect | `*.effect.block.ts` | `defineEffectBlock` | No - mutates a live instance
|
|
20
|
-
|
|
|
19
|
+
| Effect | `*.effect.block.ts` in `methods/` | `defineEffectBlock` | No - mutates a live instance via mem + `instanceOptions` |
|
|
20
|
+
| Method | `*.method.block.ts` in `methods/` | `defineMethodBlock` | No - forms, submit, logout, predefined queues |
|
|
21
21
|
|
|
22
|
-
Only an explicit `page.goto()` (or NavBlock `click`) counts as navigation -
|
|
23
|
-
|
|
22
|
+
Only an explicit `page.goto()` (or NavBlock `click`) counts as navigation - a
|
|
23
|
+
method/effect that fills a form or clicks a button stays non-nav even when the
|
|
24
24
|
app changes URL as a side effect of `#continue`.
|
|
25
25
|
|
|
26
|
+
**Selectors:** put DOM strings in `*Sel` next to the page (static + `(id) => …`).
|
|
27
|
+
Mem keys store values, not selectors.
|
|
28
|
+
|
|
26
29
|
## Layout
|
|
27
30
|
|
|
28
31
|
```text
|
|
@@ -31,15 +34,16 @@ src/blocks/
|
|
|
31
34
|
saucedemo-web/ # THIS FOLDER IS URL "/"
|
|
32
35
|
NAV.md
|
|
33
36
|
nav-login.block.ts
|
|
34
|
-
|
|
35
|
-
submit-login.
|
|
37
|
+
methods/
|
|
38
|
+
submit-login.method.block.ts
|
|
36
39
|
submit-login-for-flow.ts
|
|
37
|
-
submit-logout.
|
|
40
|
+
submit-logout.method.block.ts
|
|
38
41
|
inventory/ # /inventory.html
|
|
39
42
|
NAV.md
|
|
43
|
+
inventory.page.block.ts # Page hub + methods registry
|
|
40
44
|
nav-item-detail.block.ts
|
|
41
|
-
|
|
42
|
-
inventory-items.ts
|
|
45
|
+
methods/
|
|
46
|
+
inventory-items.ts # InventorySel + collectors
|
|
43
47
|
add-to-cart.effect.block.ts
|
|
44
48
|
remove-from-cart.effect.block.ts
|
|
45
49
|
inventory-item/ # /inventory-item.html
|
|
@@ -53,18 +57,18 @@ src/blocks/
|
|
|
53
57
|
nav-checkout-info.block.ts
|
|
54
58
|
checkout-step-one/ # /checkout-step-one.html
|
|
55
59
|
NAV.md
|
|
56
|
-
|
|
57
|
-
submit-checkout-info.
|
|
60
|
+
methods/
|
|
61
|
+
submit-checkout-info.method.block.ts
|
|
58
62
|
checkout-step-two/ # /checkout-step-two.html
|
|
59
63
|
NAV.md
|
|
60
|
-
|
|
61
|
-
finish-order.
|
|
64
|
+
methods/
|
|
65
|
+
finish-order.method.block.ts
|
|
62
66
|
src/states/
|
|
63
67
|
src/flows/
|
|
64
68
|
```
|
|
65
69
|
|
|
66
70
|
`waygraph check` should report zero warnings - every navigation goes through
|
|
67
|
-
a `defineNavBlock
|
|
71
|
+
a `defineNavBlock` / Page deep-link, nothing else calls `page.goto`.
|
|
68
72
|
|
|
69
73
|
`waygraph auto` should discover Checkpoints as nodes and these Blocks as
|
|
70
74
|
edges (including cycles via logout / continue-shopping / back-to-products).
|
|
@@ -4,23 +4,76 @@ Runtime is always a `Block`. Helpers are TypeScript salt + clear intent.
|
|
|
4
4
|
|
|
5
5
|
| Helper | Use when | File |
|
|
6
6
|
|--------|----------|------|
|
|
7
|
+
| `definePageBlock` | Screen hub (checkpoint + methods + optional Sel) | `*.page.block.ts` |
|
|
7
8
|
| `defineNavBlock` | URL / `goto` only | `nav-*.block.ts` |
|
|
8
9
|
| `defineNavClickBlock` | Click-to-navigate only | `nav-*.block.ts` |
|
|
9
10
|
| `defineMemNavBlock` | Nav + per-row `instanceOptions` | `nav-*.block.ts` |
|
|
10
|
-
| `
|
|
11
|
-
| `defineEffectBlock` | Instance mutate + auto menu rows | `*.effect.block.ts` |
|
|
11
|
+
| `defineMethodBlock` | One-shot non-nav app step | `*.method.block.ts` under `methods/` |
|
|
12
|
+
| `defineEffectBlock` | Instance mutate + auto menu rows | `*.effect.block.ts` under `methods/` |
|
|
12
13
|
| `defineBlock` | Escape hatch: tests, unsure, probes | any |
|
|
14
|
+
| `defineActionBlock` | **Deprecated** alias of `defineMethodBlock` | — |
|
|
13
15
|
|
|
14
16
|
`defineNavBlock({ click })` still works (compat) - prefer `defineNavClickBlock` in app code.
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
---
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
## Page hub + methods + Sel
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
One **PageBlock** = this screen. Methods hang off it. DOM strings live in `*Sel` (not in mem).
|
|
21
23
|
|
|
22
24
|
```ts
|
|
23
|
-
|
|
25
|
+
export const InventorySel = {
|
|
26
|
+
list: ".inventory_list",
|
|
27
|
+
addBtn: (id: string) => `[data-test="add-to-cart-${id}"]`,
|
|
28
|
+
// …
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const InventoryPageBlock = definePageBlock({
|
|
32
|
+
name: "page-inventory",
|
|
33
|
+
checkpoint: "LoggedIn",
|
|
34
|
+
verify: [Trait.url({ pathname: "/inventory.html" }), Trait.visible(InventorySel.list)],
|
|
35
|
+
methods: {
|
|
36
|
+
addToCart: () => AddToCartBlock,
|
|
37
|
+
removeFromCart: () => RemoveFromCartBlock,
|
|
38
|
+
addAllToCart: () => AddAllToCartBlock,
|
|
39
|
+
removeAllFromCart: () => RemoveAllFromCartBlock,
|
|
40
|
+
openDetail: () => NavItemDetailBlock,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Piece | Stores |
|
|
46
|
+
|-------|--------|
|
|
47
|
+
| `*Sel` | DOM selectors only (static + `(id) => …`) |
|
|
48
|
+
| Mem keys | Typed values (which item / queue) — not selectors |
|
|
49
|
+
| `instanceOptions` | Live DOM → menu rows → `mem.set` on pick |
|
|
50
|
+
| Predefined method | No `instanceOptions`; flow seeds mem first |
|
|
51
|
+
|
|
52
|
+
Live: `inventory/inventory.page.block.ts`, `inventory/methods/`.
|
|
53
|
+
|
|
54
|
+
**Round-trip (not forward-only):** cart Methods/Effects use `In = Checkpoint<string>`
|
|
55
|
+
so the graph edge is `from: "*"`. Auto lists them whenever the live page has matching
|
|
56
|
+
buttons (Add vs Remove), including after continue-shopping / back to inventory. Bulk
|
|
57
|
+
`add-all-to-cart` / `remove-all-from-cart` are one menu row each (no `instanceOptions`);
|
|
58
|
+
Out is `ItemInCart` vs `LoggedIn` from cart emptiness.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Checkpoint vs `branch()`
|
|
63
|
+
|
|
64
|
+
| | **Checkpoint** | **`branch()`** |
|
|
65
|
+
|--|----------------|----------------|
|
|
66
|
+
| Answers | *Where* are you? (`LoggedIn`, `LoginPage`) | *Which Block runs next?* |
|
|
67
|
+
| How | `observe` + `resolve` -> union `Out` | `branch(block, { Tag: nextBlock \| null })` |
|
|
68
|
+
| Alone can | Label state; graph edges; verify | Route a `runGraph` walk |
|
|
69
|
+
| Alone cannot | Pick the next Block | Decide the state |
|
|
70
|
+
|
|
71
|
+
Checkpoint = place on the map. `branch()` = which road to take from that place.
|
|
72
|
+
|
|
73
|
+
Conditional Out (no `branch()` needed for auto/graph):
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
defineMethodBlock<LoginPage, LoggedIn | LoginPage>({
|
|
24
77
|
name: "submit-login",
|
|
25
78
|
instruction: {
|
|
26
79
|
async act(page, _in, mem) { /* fill + click */ },
|
|
@@ -37,6 +90,97 @@ defineActionBlock<LoginPage, LoggedIn | LoginPage>({
|
|
|
37
90
|
});
|
|
38
91
|
```
|
|
39
92
|
|
|
40
|
-
|
|
93
|
+
Optional routing for `runGraph`:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
branch(SubmitLoginActionBlock, {
|
|
97
|
+
LoggedIn: AddToCartBlock,
|
|
98
|
+
LoginPage: null, // stop
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Linear `defineFlow([start, …, end])` usually skips `branch()` (one happy path).
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Mem / `instanceOptions` (spawns menu rows)
|
|
107
|
+
|
|
108
|
+
Not a separate runtime type. One Block + `requires` + `instanceOptions(page)` ->
|
|
109
|
+
`waygraph auto` lists **one row per live DOM match**. On pick: `mem.set(key, value)`
|
|
110
|
+
then run the Block (act reads mem as usual).
|
|
111
|
+
|
|
112
|
+
### Effect - spawn Method-like rows (Add / Remove)
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
defineEffectBlock<Checkpoint<string>, ItemInCart>({
|
|
116
|
+
name: "add-to-cart",
|
|
117
|
+
requires: [SelectedItem.key],
|
|
118
|
+
instruction: {
|
|
119
|
+
async act(page, _in, mem) {
|
|
120
|
+
const { id } = mem.get(SelectedItem.key);
|
|
121
|
+
await page.locator(InventorySel.addBtn(id)).click();
|
|
122
|
+
},
|
|
123
|
+
resolve: () => checkpoint("ItemInCart"),
|
|
124
|
+
},
|
|
125
|
+
async instanceOptions(page) {
|
|
126
|
+
const items = await collectAddableItems(page);
|
|
127
|
+
return items.map((item) => ({
|
|
128
|
+
id: item.id,
|
|
129
|
+
label: `Add "${item.name}" to cart`,
|
|
130
|
+
key: SelectedItem.key,
|
|
131
|
+
value: item,
|
|
132
|
+
highlight: InventorySel.addBtn(item.id),
|
|
133
|
+
}));
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Auto shows: `Add "Sauce Labs Backpack" to cart`, `Add "Bike Light"…`, …
|
|
139
|
+
|
|
140
|
+
### MemNav - spawn Nav rows (Open details)
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
defineMemNavBlock<ItemDetailPage>({
|
|
144
|
+
name: "nav-item-detail",
|
|
145
|
+
checkpoint: "ItemDetailPage",
|
|
146
|
+
requires: [SelectedItem.key],
|
|
147
|
+
click: (mem) => InventorySel.title(mem.get(SelectedItem.key).id),
|
|
148
|
+
verify: [Trait.url({ pathname: "/inventory-item.html" })],
|
|
149
|
+
async instanceOptions(page) {
|
|
150
|
+
const items = await collectInventoryItems(page);
|
|
151
|
+
return items.map((item) => ({
|
|
152
|
+
id: item.id,
|
|
153
|
+
label: `Open "${item.name}" details`,
|
|
154
|
+
key: SelectedItem.key,
|
|
155
|
+
value: item,
|
|
156
|
+
highlight: InventorySel.title(item.id),
|
|
157
|
+
}));
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Auto shows: `Open "Backpack" details`, … - still a NavBlock (click nav), salt only.
|
|
163
|
+
|
|
164
|
+
| | Effect | MemNav |
|
|
165
|
+
|--|--------|--------|
|
|
166
|
+
| Helper | `defineEffectBlock` | `defineMemNavBlock` |
|
|
167
|
+
| Spawns | mutate rows | navigate rows |
|
|
168
|
+
| `act` / nav | click Add/Remove | `click: (mem) => …` title link |
|
|
169
|
+
| Mem | same pattern: `requires` + row `key`/`value` | same |
|
|
170
|
+
|
|
171
|
+
Live source: `inventory/methods/add-to-cart.effect.block.ts`,
|
|
172
|
+
`inventory/nav-item-detail.block.ts`.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Cart bulk: demo / run / auto
|
|
177
|
+
|
|
178
|
+
| | Command |
|
|
179
|
+
|--|---------|
|
|
180
|
+
| **Run (Block names)** | `npm run run:bulk` — `run --blocks "loginFlow then add-all…" --data '{…}'` |
|
|
181
|
+
| **Demo (named flow)** | `npm run demo:bulk` — `--blocks cartBulkFlow --data '{…}'` |
|
|
182
|
+
| **Demo QA video** | `npm run demo:bulk:video` — `--auto-play-video` |
|
|
183
|
+
| **Auto headed / CLI** | `npm run auto` / `auto:cli` |
|
|
184
|
+
| **Auto path-find** | `waygraph auto --blocks LoginPage OrderComplete` |
|
|
185
|
+
|
|
41
186
|
|
|
42
|
-
No breaking change for atomics - document observe/resolve (+ optional `branch`) as the Branch pattern.
|