waygraph 0.15.52 → 0.15.54

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.
Files changed (156) hide show
  1. package/README.md +8 -1
  2. package/dist/analysis/graph.d.ts +3 -0
  3. package/dist/analysis/map-check.d.ts +3 -2
  4. package/dist/auto-session/helpers.d.ts +10 -0
  5. package/dist/blocks-select.d.ts +3 -0
  6. package/dist/blocks-select.js +2 -0
  7. package/dist/cli/flags.js +3 -1
  8. package/dist/cli/usage.js +4 -1
  9. package/dist/cli.js +4 -1
  10. package/dist/commands/agent-dive-scaffold.d.ts +0 -20
  11. package/dist/commands/agent-dive-scaffold.js +3 -3
  12. package/dist/commands/init.js +1 -1
  13. package/dist/commands/upgrade.d.ts +1 -1
  14. package/dist/commands/upgrade.js +7 -1
  15. package/dist/config.d.ts +55 -19
  16. package/dist/config.js +55 -13
  17. package/dist/core/block.d.ts +7 -2
  18. package/dist/core/block.js +7 -3
  19. package/dist/core/blocks/api.d.ts +1 -0
  20. package/dist/core/blocks/method.d.ts +2 -0
  21. package/dist/core/blocks/method.js +1 -0
  22. package/dist/core/blocks/nav.d.ts +1 -0
  23. package/dist/core/blocks/page.d.ts +1 -0
  24. package/dist/core/branch-regression.d.ts +1 -0
  25. package/dist/core/compose.d.ts +1 -0
  26. package/dist/core/engine.d.ts +2 -0
  27. package/dist/core/engine.js +3 -1
  28. package/dist/core/flow.d.ts +11 -0
  29. package/dist/core/flow.js +32 -8
  30. package/dist/core/map-builder.d.ts +34 -1
  31. package/dist/core/map-builder.js +42 -4
  32. package/dist/core/run-graph.d.ts +2 -0
  33. package/dist/core/run-graph.js +1 -0
  34. package/dist/errors.d.ts +1 -0
  35. package/dist/extensions.d.ts +1 -0
  36. package/dist/extensions.js +1 -0
  37. package/dist/highlights/demo-pace.d.ts +4 -1
  38. package/dist/highlights/demo-pace.js +28 -3
  39. package/dist/highlights/device.d.ts +2 -0
  40. package/dist/highlights/device.js +2 -0
  41. package/dist/highlights/types.d.ts +6 -0
  42. package/dist/index.d.ts +2 -2
  43. package/dist/pilot-overlay/action-ring.d.ts +0 -9
  44. package/dist/pilot-overlay/action-ring.js +1 -1
  45. package/dist/pilot-overlay/panel.d.ts +2 -1
  46. package/dist/runner/interaction-patch.js +32 -1
  47. package/dist/runner/main.js +5 -2
  48. package/dist/runner/step-mode.js +10 -4
  49. package/dist/session/auto-explore-run.js +6 -0
  50. package/dist/session/auto-explore.d.ts +2 -1
  51. package/dist/session/auto-session-ipc.d.ts +1 -2
  52. package/dist/session/auto-session-ipc.js +1 -1
  53. package/dist/session/block-inject.d.ts +0 -13
  54. package/dist/session/block-inject.js +3 -3
  55. package/dist/session/overlay-beacon.d.ts +7 -0
  56. package/dist/session/overlay-beacon.js +5 -0
  57. package/dist/session/pilot.d.ts +1 -2
  58. package/dist/session/pilot.js +1 -2
  59. package/dist/traverse/coverage.d.ts +4 -4
  60. package/dist/traverse/coverage.js +4 -1
  61. package/dist/traverse/lease.d.ts +1 -0
  62. package/dist/traverse/lease.js +1 -0
  63. package/dist/traverse/run.d.ts +3 -0
  64. package/dist/types.d.ts +1 -1
  65. package/examples/routed-demo/src/flows/routed-demo-map.flow.ts +1 -1
  66. package/examples/routed-demo/src/flows/routed-demo.flow.ts +2 -2
  67. package/examples/saucedemo/README.md +11 -12
  68. package/examples/saucedemo/config/waygraph.config.ts +17 -0
  69. package/examples/saucedemo/docs/CONVENTION.md +1 -2
  70. package/examples/saucedemo/docs/HELPERS.md +1 -1
  71. package/examples/saucedemo/package-lock.json +10 -3
  72. package/examples/saucedemo/package.json +12 -29
  73. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
  74. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
  75. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
  76. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
  77. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
  78. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
  79. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
  80. package/examples/saucedemo/src/flows/api-demo.flow.ts +17 -11
  81. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -14
  82. package/examples/saucedemo/src/flows/cart-continue.flow.ts +10 -8
  83. package/examples/saucedemo/src/flows/cart-mem.flow.ts +7 -7
  84. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +13 -8
  85. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +26 -48
  86. package/examples/saucedemo/src/flows/checkout.flow.ts +70 -86
  87. package/examples/saucedemo/src/flows/device-showcase.flow.ts +24 -25
  88. package/examples/saucedemo/src/flows/login.flow.ts +11 -18
  89. package/examples/saucedemo/src/flows/mem-stub-demo.flow.ts +9 -20
  90. package/examples/saucedemo/src/flows/shop.flow.ts +15 -20
  91. package/examples/saucedemo/src/flows/smoke.flow.ts +12 -27
  92. package/examples/saucedemo/src/flows/stub-on-error.flow.ts +24 -43
  93. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +26 -34
  94. package/examples/saucedemo/src/states/checkout.mem-stubs.ts +11 -0
  95. package/examples/saucedemo/src/stubs/device-showcase.ts +193 -0
  96. package/examples/saucedemo/src/stubs/login.ts +37 -0
  97. package/examples/saucedemo/tests/chain-flow-episodes.spec.ts +7 -9
  98. package/examples/saucedemo/tests/submit-login-branch.spec.ts +18 -5
  99. package/package.json +12 -5
  100. package/templates/quickstart/README.md +4 -7
  101. package/templates/quickstart/config/waygraph.config.ts +17 -0
  102. package/templates/quickstart/docs/CONVENTION.md +1 -2
  103. package/templates/quickstart/docs/HELPERS.md +1 -1
  104. package/templates/quickstart/package.json +14 -14
  105. package/templates/quickstart/src/blocks/SITE-MAP.md +14 -3
  106. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
  107. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
  108. package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
  109. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
  110. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
  111. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
  112. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
  113. package/templates/quickstart/src/flows/api-demo.flow.ts +43 -0
  114. package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -14
  115. package/templates/quickstart/src/flows/cart-continue.flow.ts +10 -8
  116. package/templates/quickstart/src/flows/cart-mem.flow.ts +7 -7
  117. package/templates/quickstart/src/flows/catalog-logout.flow.ts +13 -8
  118. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +26 -48
  119. package/templates/quickstart/src/flows/checkout.flow.ts +70 -63
  120. package/templates/quickstart/src/flows/device-showcase.flow.ts +24 -25
  121. package/templates/quickstart/src/flows/login.flow.ts +11 -18
  122. package/templates/quickstart/src/flows/mem-stub-demo.flow.ts +12 -0
  123. package/templates/quickstart/src/flows/shop.flow.ts +15 -20
  124. package/templates/quickstart/src/flows/smoke.flow.ts +12 -27
  125. package/templates/quickstart/src/flows/stub-on-error.flow.ts +31 -0
  126. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +26 -34
  127. package/templates/quickstart/src/states/checkout.mem-stubs.ts +11 -0
  128. package/templates/quickstart/src/stubs/device-showcase.ts +193 -0
  129. package/templates/quickstart/src/stubs/login.ts +37 -0
  130. package/templates/quickstart/tests/chain-flow.spec.ts +7 -10
  131. package/templates/scaffold/README.md +1 -1
  132. package/templates/scaffold/STRUCTURE.md +5 -2
  133. package/templates/scaffold/config/waygraph.config.ts +24 -5
  134. package/templates/scaffold/package.json +6 -6
  135. package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
  136. package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
  137. package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
  138. package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
  139. package/templates/scaffold/src/flows/README.md +35 -14
  140. package/templates/scaffold/src/flows/{shop.flow.ts → home/cart.flow.ts} +18 -17
  141. package/templates/scaffold/src/flows/{example.flow.ts → home/hello.flow.ts} +12 -13
  142. package/templates/scaffold/tests/example.spec.ts +5 -5
  143. package/templates/scaffold/tests/hoppscotch.spec.ts +3 -3
  144. package/templates/scaffold/tests/mail-verify.spec.ts +7 -7
  145. package/templates/skills/waygraph-convention.skill.md +14 -6
  146. package/examples/saucedemo/playwright.config.ts +0 -19
  147. package/examples/saucedemo/scripts/drive-auto-cli.mjs +0 -93
  148. package/examples/saucedemo/scripts/prove-add-to-cart-menu.mjs +0 -126
  149. package/examples/saucedemo/scripts/prove-auto-graph.mjs +0 -92
  150. package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +0 -63
  151. package/templates/quickstart/playwright.config.ts +0 -14
  152. package/templates/quickstart/scripts/drive-auto-cli.mjs +0 -93
  153. package/templates/scaffold/src/flows/hoppscotch-auth.flow.ts +0 -35
  154. package/templates/scaffold/src/flows/hoppscotch-get.flow.ts +0 -23
  155. package/templates/scaffold/src/flows/hoppscotch-post.flow.ts +0 -33
  156. package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -46
@@ -1,40 +1,32 @@
1
- import { Engine, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
1
+ import { engine } from "../../config/waygraph.config.js";
2
+ import "../states/checkout.mem-stubs.js";
2
3
  import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
3
4
  import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
4
5
  import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
5
6
  import { SubmitLoginBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
7
+ import { loginRefused } from "../stubs/login.js";
6
8
 
7
- const engine = new Engine();
8
-
9
- // A genuinely DIFFERENT scenario from checkoutFlow - not the same steps
10
- // split in half. Same login blocks, different user, different outcome:
11
- // locked_out_user attempts the exact same login an Owner (standard_user,
12
- // checkoutFlow) just completed successfully, and is blocked -
13
- // saucedemo.com's real response, verified live: stays on "/", shows
14
- // "Epic sadface: Sorry, this user has been locked out."
15
- //
16
- // withSessionReset matters here for real, not cosmetically - entered right
17
- // after checkoutFlow's already-authenticated session, this needs its own
18
- // genuinely fresh cookies/storage, or the previous user's session would
19
- // still be live and this login attempt wouldn't mean anything.
20
- //
21
- // submit-login branches: observe sees no /inventory.html navigation,
22
- // resolve -> LoginPage, verify confirms the error banner. Demo still wraps
23
- // withExpectedFailure so step 9/9 shows stubOnError rings + amber
24
- // "expected outcome" panel (branching no longer throws).
25
- export const viewerBlockedFlow = withExpectedFailure(
26
- withTitle(
27
- withSessionReset(
28
- engine
29
- .map()
30
- .start()
31
- .gotoPage(NavLoginBlock)
32
- .method(FillUsernameBlock)
33
- .method(FillPasswordBlock)
34
- .method(SubmitLoginBlock)
35
- .end(),
9
+ /**
10
+ * locked_out_user attempts the login a standard user just completed and is refused: the app stays on the login page and
11
+ * shows the error banner. Run it after another flow to see it needs a reset: `.flow(viewerBlockedFlow, { reset: true })`.
12
+ */
13
+ export const viewerBlockedFlow = engine
14
+ .map({
15
+ sessionReset: true,
16
+ expectedFailure:
17
+ "locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
18
+ })
19
+ .start()
20
+ .gotoPage(
21
+ NavLoginBlock.stubBefore((ctx) =>
22
+ ctx.title("Viewer: Blocked Login Attempt"),
36
23
  ),
37
- "Viewer: Blocked Login Attempt",
38
- ),
39
- "locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
40
- );
24
+ )
25
+ .method(FillUsernameBlock)
26
+ .method(FillPasswordBlock)
27
+ .method(
28
+ SubmitLoginBlock.stubAfter((ctx) => {
29
+ if (ctx.out?.__state === "LoginPage") loginRefused(ctx);
30
+ }),
31
+ )
32
+ .end();
@@ -0,0 +1,11 @@
1
+ import { registerMemStub } from "waygraph";
2
+ import { LoginCreds, SelectedItem } from "./checkout.mem-keys.js";
3
+
4
+ registerMemStub(LoginCreds.key, () => ({
5
+ username: "standard_user",
6
+ password: "secret_sauce",
7
+ }));
8
+ registerMemStub(SelectedItem.key, () => ({
9
+ id: "sauce-labs-backpack",
10
+ name: "Sauce Labs Backpack",
11
+ }));
@@ -0,0 +1,193 @@
1
+ import type { StubCtx } from "waygraph";
2
+ import { LoginCreds } from "../states/checkout.mem-keys.js";
3
+ import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
4
+ import { CartSel } from "../blocks/saucedemo-web/cart/cart.sel.js";
5
+ import { InventorySel } from "../blocks/saucedemo-web/inventory/methods/inventory-items.js";
6
+
7
+ const PRODUCT_ID = "sauce-labs-backpack";
8
+
9
+ /** The six-step checklist of the device showcase; `done` is how many are ticked, `index` the highlighted one. */
10
+ function checklist(done: number, index: number) {
11
+ const steps = [
12
+ "Enter username",
13
+ "Enter password",
14
+ "Tap Login",
15
+ "Find the product",
16
+ "Add to cart",
17
+ "Open cart",
18
+ ];
19
+ const ids = [
20
+ "login-user",
21
+ "login-pass",
22
+ "login-submit",
23
+ "cart-find",
24
+ "cart-add",
25
+ "cart-open",
26
+ ];
27
+ return [
28
+ ids.map((id, i) => ({
29
+ id,
30
+ text: steps[i]!,
31
+ ...(i < done ? { done: true } : {}),
32
+ })),
33
+ { title: "Device showcase", index },
34
+ ] as const;
35
+ }
36
+
37
+ /** Narration for the device showcase flow: phone login, tablet shelf, landscape cart. Each is a stub to chain on its Block. */
38
+ export const deviceShowcase = {
39
+ username: {
40
+ before: (ctx: StubCtx) => {
41
+ ctx.title("Signing in · mobile");
42
+ ctx.device("mobile");
43
+ ctx.portrait();
44
+ ctx.todos("saucedemo", ...checklist(0, 0));
45
+ ctx.zoom(1.35);
46
+ const fromMem = ctx.mem?.get(LoginCreds.key)?.username;
47
+ ctx.ring("username", {
48
+ selector: LoginSel.username,
49
+ label: "Username",
50
+ detail: fromMem
51
+ ? `tone: info (blue) - mem ${fromMem}`
52
+ : "tone: info (blue)",
53
+ tone: "info",
54
+ todo: "login-user",
55
+ focus: true,
56
+ zoom: 1.4,
57
+ });
58
+ },
59
+ after: (ctx: StubCtx) => {
60
+ ctx.todos("saucedemo", ...checklist(1, 1));
61
+ },
62
+ },
63
+ password: {
64
+ before: (ctx: StubCtx) => {
65
+ ctx.ring("password", {
66
+ selector: LoginSel.password,
67
+ label: "Password",
68
+ detail: "tone: planned (purple)",
69
+ todo: "login-pass",
70
+ tone: "planned",
71
+ focus: true,
72
+ zoom: 1.4,
73
+ });
74
+ },
75
+ after: (ctx: StubCtx) => {
76
+ ctx.todos("saucedemo", ...checklist(2, 2));
77
+ },
78
+ },
79
+ submit: {
80
+ before: (ctx: StubCtx) => {
81
+ ctx.highlights({
82
+ submit: {
83
+ selector: LoginSel.loginButton,
84
+ label: "Login",
85
+ detail: "tone: warning (yellow)",
86
+ todo: "login-submit",
87
+ tone: "warning",
88
+ focus: true,
89
+ zoom: 1.55,
90
+ weight: "bold",
91
+ gesture: "tap",
92
+ },
93
+ });
94
+ },
95
+ after: (ctx: StubCtx) => {
96
+ ctx.banner("Inventory · landscape");
97
+ ctx.landscape();
98
+ ctx.todos("saucedemo", ...checklist(3, 3));
99
+ ctx.ring("shelf", {
100
+ selector: ".inventory_list",
101
+ label: "Product shelf",
102
+ detail: "tone: success (green) - ready to shop",
103
+ tone: "success",
104
+ focus: true,
105
+ zoom: 1.25,
106
+ duration: true,
107
+ });
108
+ },
109
+ },
110
+ addToCart: {
111
+ before: (ctx: StubCtx) => {
112
+ ctx.title("Pick a product · tablet");
113
+ ctx.device("tablet");
114
+ ctx.portrait();
115
+ ctx.todos("saucedemo", ...checklist(3, 3));
116
+ ctx.zoom(1.35);
117
+ ctx.zoomOut(false);
118
+ ctx.highlights({
119
+ product: {
120
+ selector: InventorySel.title(PRODUCT_ID),
121
+ label: "Sauce Labs Backpack",
122
+ detail: "tone: info (blue)",
123
+ tone: "info",
124
+ focus: true,
125
+ zoom: 1.45,
126
+ zoomOut: false,
127
+ },
128
+ add: {
129
+ selector: InventorySel.addBtn(PRODUCT_ID),
130
+ label: "Add to cart",
131
+ detail: "tone: success (green)",
132
+ tone: "success",
133
+ focus: true,
134
+ zoom: 1.5,
135
+ zoomOut: false,
136
+ weight: "bold",
137
+ },
138
+ });
139
+ },
140
+ after: (ctx: StubCtx) => {
141
+ ctx.banner("In the cart");
142
+ ctx.todos("saucedemo", ...checklist(4, 4));
143
+ ctx.zoomOut(false);
144
+ ctx.ring("badge", {
145
+ selector: InventorySel.cartBadge,
146
+ label: "Cart badge",
147
+ detail: "tone: success (green) - count updated",
148
+ tone: "success",
149
+ focus: true,
150
+ zoomOut: false,
151
+ duration: true,
152
+ });
153
+ },
154
+ },
155
+ openCart: {
156
+ before: (ctx: StubCtx) => {
157
+ ctx.title("Open cart · landscape");
158
+ ctx.landscape();
159
+ ctx.todos("saucedemo", ...checklist(5, 5));
160
+ ctx.zoom(1.4);
161
+ ctx.zoomOut(false);
162
+ ctx.highlights({
163
+ cart: {
164
+ selector: CartSel.cartLink,
165
+ label: "Cart",
166
+ detail: "tone: planned (purple) - open cart",
167
+ tone: "planned",
168
+ focus: true,
169
+ zoomOut: false,
170
+ followMouse: false,
171
+ weight: "bold",
172
+ gesture: "tap",
173
+ },
174
+ });
175
+ },
176
+ after: (ctx: StubCtx) => {
177
+ ctx.banner("Your cart · desktop");
178
+ ctx.clearDevice();
179
+ ctx.hideTodos();
180
+ ctx.zoomOut(true);
181
+ ctx.ring("items", {
182
+ selector: InventorySel.cartItem,
183
+ label: "Cart items",
184
+ detail: "tone: success (green) - product in cart",
185
+ tone: "success",
186
+ focus: true,
187
+ zoomOut: true,
188
+ followMouse: false,
189
+ duration: true,
190
+ });
191
+ },
192
+ },
193
+ };
@@ -0,0 +1,37 @@
1
+ import type { StubCtx } from "waygraph";
2
+ import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
3
+
4
+ /** What a refused login looks like on screen: the title, a finished checklist and the error banner. */
5
+ export function loginRefused(ctx: StubCtx): void {
6
+ ctx.title("Login blocked");
7
+ ctx.todos(
8
+ "saucedemo",
9
+ [
10
+ { id: "login-user", text: "Enter username", done: true },
11
+ { id: "login-pass", text: "Enter password", done: true },
12
+ { id: "login-submit", text: "Click Login", done: true },
13
+ ],
14
+ { title: "Sign in", style: "checklist" },
15
+ );
16
+ ctx.ring("error", {
17
+ selector: LoginSel.errorBanner,
18
+ label: "Login error banner",
19
+ detail: "tone: danger (red)",
20
+ tone: "danger",
21
+ focus: true,
22
+ duration: true,
23
+ });
24
+ }
25
+
26
+ /** Rings the error banner when the login step itself throws. */
27
+ export function loginFailed(ctx: StubCtx): void {
28
+ ctx.title("Login failed");
29
+ ctx.ring("error", {
30
+ selector: LoginSel.errorBanner,
31
+ label: "Login error banner",
32
+ detail: "tone: danger (red)",
33
+ tone: "danger",
34
+ focus: true,
35
+ duration: true,
36
+ });
37
+ }
@@ -1,30 +1,28 @@
1
1
  import { test, expect } from "@playwright/test";
2
- import { MemPage, checkpoint, chainFlow } from "waygraph";
2
+ import { MemPage, checkpoint } from "waygraph";
3
+ import { engine } from "../config/waygraph.config.js";
3
4
  import { loginFlow } from "../src/flows/login.flow.js";
4
5
  import { shopFlow } from "../src/flows/shop.flow.js";
5
6
  import { LoginCreds, SelectedItem } from "../src/states/checkout.mem-keys.js";
6
7
 
7
- // Proves the 2-episode chainFlow scenario the step-mode CLI demo shows
8
+ // Proves the 2-episode nested-flow scenario the step-mode CLI demo shows
8
9
  // interactively ("Episode 1: Sign In" -> "Episode 2: Shop & Checkout") also
9
10
  // works as a real, automated, headless test - not just something you have
10
11
  // to click through by hand. loginFlow/shopFlow are the exact same Flow
11
12
  // objects `waygraph chain "loginFlow(...) then shopFlow"` drives.
12
- test("chainFlow sequences 2 titled episodes (loginFlow then shopFlow) against the real live site", async () => {
13
+ test("nested flows run 2 episodes (loginFlow then shopFlow) against the real live site", async () => {
13
14
  const mem = new MemPage();
14
15
  mem.set(LoginCreds({ username: "standard_user", password: "secret_sauce" }));
15
16
  mem.set(SelectedItem({ id: "sauce-labs-backpack", name: "Sauce Labs Backpack" }));
16
17
 
17
- const combined = chainFlow(loginFlow, shopFlow);
18
+ const combined = engine.map().start().flow(loginFlow).flow(shopFlow).end();
18
19
  const result = await combined.run(mem);
19
20
 
20
21
  expect(result).toEqual(checkpoint("OrderComplete"));
21
22
  });
22
23
 
23
- test("each episode's title survives chaining, and blocks() reports the flattened chain in order", () => {
24
- expect(loginFlow.title).toBe("Sign In");
25
- expect(shopFlow.title).toBe("Shop & Checkout");
26
-
27
- const combined = chainFlow(loginFlow, shopFlow);
24
+ test("blocks() reports the flattened chain in order", () => {
25
+ const combined = engine.map().start().flow(loginFlow).flow(shopFlow).end();
28
26
  const blocks = combined.blocks();
29
27
 
30
28
  expect(blocks.map((b) => b.name)).toEqual([
@@ -1,5 +1,6 @@
1
1
  import { test, expect } from "@playwright/test";
2
- import { MemPage, Engine, start, end } from "waygraph";
2
+ import { MemPage } from "waygraph";
3
+ import { engine } from "../config/waygraph.config.js";
3
4
  import { NavLoginBlock } from "../src/blocks/saucedemo-web/nav-login.block.js";
4
5
  import { FillUsernameBlock } from "../src/blocks/saucedemo-web/methods/fill-username.method.block.js";
5
6
  import { FillPasswordBlock } from "../src/blocks/saucedemo-web/methods/fill-password.method.block.js";
@@ -10,8 +11,14 @@ test("submit-login: wrong password branches to LoginPage with error banner", asy
10
11
  const mem = new MemPage();
11
12
  mem.set(LoginCreds({ username: "standard_user", password: "wrong-password" }));
12
13
 
13
- const engine = new Engine();
14
- const flow = engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginBlock, end]);
14
+ const flow = engine
15
+ .map()
16
+ .start()
17
+ .gotoPage(NavLoginBlock)
18
+ .method(FillUsernameBlock)
19
+ .method(FillPasswordBlock)
20
+ .method(SubmitLoginBlock)
21
+ .end();
15
22
  const result = await flow.run(mem);
16
23
 
17
24
  expect(result.__state).toBe("LoginPage");
@@ -21,8 +28,14 @@ test("submit-login: good password branches to LoggedIn", async () => {
21
28
  const mem = new MemPage();
22
29
  mem.set(LoginCreds({ username: "standard_user", password: "secret_sauce" }));
23
30
 
24
- const engine = new Engine();
25
- const flow = engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginBlock, end]);
31
+ const flow = engine
32
+ .map()
33
+ .start()
34
+ .gotoPage(NavLoginBlock)
35
+ .method(FillUsernameBlock)
36
+ .method(FillPasswordBlock)
37
+ .method(SubmitLoginBlock)
38
+ .end();
26
39
  const result = await flow.run(mem);
27
40
 
28
41
  expect(result.__state).toBe("LoggedIn");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.15.52",
3
+ "version": "0.15.54",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -48,16 +48,19 @@
48
48
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
49
49
  "build": "npm run clean && tsc -p tsconfig.build.json && node scripts/copy-runner.mjs && node scripts/copy-ui-css.mjs",
50
50
  "typecheck": "node bin/waygraph typecheck .",
51
- "test": "npm run check:structure && npm run test:unit && playwright test",
51
+ "test": "npm run check:structure && npm run knip && npm run test:unit && playwright test",
52
52
  "test:ui": "playwright test --ui",
53
53
  "test:unit": "node --import tsx/esm --test tests/unit/*.spec.ts",
54
54
  "example:saucedemo:install": "npm --prefix examples/saucedemo install",
55
55
  "example:saucedemo:test": "npm --prefix examples/saucedemo test",
56
56
  "example:saucedemo:auto": "npm --prefix examples/saucedemo run auto",
57
57
  "check:structure": "node scripts/check-structure.mjs",
58
- "docs:dev": "vitepress dev docs",
59
- "docs:build": "vitepress build docs",
60
- "docs:preview": "vitepress preview docs"
58
+ "docs:dev": "npm run docs:api && vitepress dev docs",
59
+ "docs:build": "npm run docs:api && vitepress build docs",
60
+ "docs:preview": "vitepress preview docs",
61
+ "docs:api": "typedoc && node scripts/gen-deprecated.mjs",
62
+ "knip": "knip",
63
+ "sync:quickstart": "node scripts/sync-quickstart.mjs"
61
64
  },
62
65
  "peerDependencies": {
63
66
  "@playwright/test": "^1.40.0"
@@ -70,6 +73,10 @@
70
73
  "devDependencies": {
71
74
  "@playwright/test": "^1.63.0",
72
75
  "@types/node": "^26.0.0",
76
+ "knip": "^6.42.1",
77
+ "typedoc": "^0.28.20",
78
+ "typedoc-plugin-markdown": "^4.14.1",
79
+ "typedoc-vitepress-theme": "^1.1.4",
73
80
  "typescript": "^5.6.0",
74
81
  "vitepress": "^1.6.4"
75
82
  }
@@ -1,10 +1,7 @@
1
1
  # waygraph quickstart (Sauce Demo)
2
2
 
3
- Temp-dir template for `npx waygraph try demo`. Same Blocks as
4
- `examples/saucedemo` (route-folder convention under `src/blocks/saucedemo-web/`).
3
+ Temp-dir template for `npx waygraph try demo`. A copy of `examples/saucedemo` (freeform route-folder layout under
4
+ `src/blocks/saucedemo-web/`, flows in `src/flows/`, narration in `src/stubs/`, settings in `config/waygraph.config.ts`).
5
5
 
6
- Convention walkthrough on Pages:
7
- https://deviate-dv8.github.io/waygraph/saucedemo/
8
-
9
- Do not edit this tree as the long-lived showcase - edit `examples/saucedemo`,
10
- then re-copy Blocks here when shipping a release that bumps `try demo`.
6
+ Do not edit this tree as the long-lived showcase: edit `examples/saucedemo`, then run `npm run sync:quickstart` in the
7
+ waygraph repo before a release.
@@ -0,0 +1,17 @@
1
+ import { Engine, defineConfig } from "waygraph";
2
+
3
+ const config = defineConfig({
4
+ baseUrl: "https://www.saucedemo.com",
5
+ playwright: {
6
+ workers: 1,
7
+ fullyParallel: false,
8
+ timeout: 30_000,
9
+ use: {
10
+ headless: true,
11
+ launchOptions: { args: ["--no-sandbox", "--disable-dev-shm-usage"] },
12
+ },
13
+ },
14
+ });
15
+
16
+ export default config;
17
+ export const engine = new Engine(config);
@@ -82,5 +82,4 @@ waygraph chain "loginFlow" --step --no-autoplay
82
82
 
83
83
  Flags beat env: `--step` / `--no-step`, `--autoplay` / `--no-autoplay`,
84
84
  `--base-url <url>`, `--title <text>`. Default BASE_URL resolution order for
85
- `demo`: `--base-url` > `WAYGRAPH_BASE_URL` > `package.json` `waygraph.baseUrl`
86
- > `playwright.config` `baseURL`.
85
+ `demo`: `--base-url` > `WAYGRAPH_BASE_URL` > `baseUrl` in `config/waygraph.config.ts`.
@@ -99,7 +99,7 @@ branch(SubmitLoginActionBlock, {
99
99
  });
100
100
  ```
101
101
 
102
- Linear `defineFlow([start, …, end])` usually skips `branch()` (one happy path).
102
+ A linear `map()` chain usually skips `branch()` (one happy path).
103
103
 
104
104
  ---
105
105
 
@@ -4,30 +4,30 @@
4
4
  "type": "module",
5
5
  "description": "Self-contained saucedemo quickstart (copied into a temp dir by `waygraph try demo`).",
6
6
  "scripts": {
7
+ "typecheck": "tsc --noEmit",
7
8
  "test": "waygraph test",
8
9
  "test:ui": "waygraph test ui",
9
- "typecheck": "tsc --noEmit",
10
+ "flows": "waygraph list",
10
11
  "check": "waygraph check",
11
- "auto": "waygraph auto",
12
- "auto:cli": "waygraph auto --cli --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\"",
13
- "traverse": "waygraph traverse --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --max-steps 40",
14
- "traverse:parallel": "waygraph traverse --parallel 2 --session clone --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --max-steps 30",
15
- "traverse:coverage": "waygraph traverse --min-edge-coverage 10% --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --max-steps 40",
16
12
  "graph": "waygraph graph",
17
13
  "graph:mermaid": "waygraph graph --mermaid",
18
- "demo": "waygraph demo --blocks \"loginFlow then shopFlow({\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}) then viewerBlockedFlow({\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"locked_out_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}})\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\"",
19
- "demo:auto-next": "waygraph demo --blocks \"loginFlow then shopFlow({\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}) then viewerBlockedFlow({\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"locked_out_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}})\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --auto-next",
20
- "record": "waygraph run --blocks \"loginFlow then shopFlow({\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}) then viewerBlockedFlow({\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"locked_out_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}})\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --video --no-step",
21
- "demo:bulk": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\"",
22
- "demo:bulk:auto-next": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --auto-next --title \"Cart bulk demo\"",
23
- "demo:bulk:video": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --auto-play-video --title \"Cart bulk demo\"",
24
- "run:bulk": "waygraph run --blocks \"loginFlow then add-all-to-cart then remove-all-from-cart\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\""
14
+ "auto": "waygraph auto",
15
+ "auto:cli": "waygraph auto --cli",
16
+ "demo": "waygraph demo --blocks \"loginFlow then shopFlow\"",
17
+ "demo:auto-next": "waygraph demo --blocks \"loginFlow then shopFlow\" --auto-next",
18
+ "demo:checkout": "waygraph demo --blocks checkoutFlow --auto-next",
19
+ "demo:device": "waygraph demo --blocks deviceShowcaseFlow --auto-next",
20
+ "demo:bulk": "waygraph demo --blocks cartBulkFlow --auto-next",
21
+ "demo:api": "waygraph demo --blocks apiDemoFlow --auto-next",
22
+ "demo:video": "waygraph demo --blocks deviceShowcaseFlow --auto-play-video",
23
+ "record": "waygraph run --blocks \"loginFlow then shopFlow\" --video --no-step",
24
+ "traverse": "waygraph traverse --max-steps 40"
25
25
  },
26
26
  "waygraph": {
27
27
  "baseUrl": "https://www.saucedemo.com"
28
28
  },
29
29
  "dependencies": {
30
- "waygraph": "^0.12.20"
30
+ "waygraph": "latest"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@playwright/test": "^1.55.0",
@@ -8,11 +8,15 @@ src/blocks/
8
8
  SITE-MAP.md
9
9
  saucedemo-web/ # URL /
10
10
  NAV.md
11
- nav-login.block.ts # Nav defineNavBlock url:/
11
+ nav-login.block.ts # Page definePageBlock url:/
12
+ ff-owner-auth.block.ts # FFCompose (nav-login + fill-username + fill-password + submit-login)
12
13
  methods/
13
- submit-login.method.block.ts # Method defineMethodBlock
14
+ fill-username.method.block.ts # Method defineMethodBlock (self-loop on LoginPage)
15
+ fill-password.method.block.ts # Method defineMethodBlock (self-loop on LoginPage)
16
+ submit-login.method.block.ts # Method defineMethodBlock (click only; branches)
14
17
  submit-login-for-flow.ts # helper (not a discoverable *.block.ts alone)
15
18
  submit-logout.method.block.ts # Method defineMethodBlock
19
+ login.sel.ts # LoginSel (not a Block)
16
20
  inventory/ # URL /inventory.html
17
21
  inventory.page.block.ts # Page definePageBlock
18
22
  NAV.md
@@ -25,20 +29,27 @@ src/blocks/
25
29
  remove-from-cart.effect.block.ts
26
30
  inventory-item/ # URL /inventory-item.html
27
31
  NAV.md
32
+ item-detail.sel.ts # ItemDetailSel (not a Block)
28
33
  nav-continue-shopping.block.ts
29
34
  nav-back-to-products.block.ts
30
35
  nav-back-to-inventory.block.ts
31
36
  cart/ # URL /cart.html
32
37
  NAV.md
38
+ cart.sel.ts # CartSel (not a Block)
33
39
  nav-cart.block.ts # Nav click header cart
34
40
  nav-checkout-info.block.ts
35
41
  checkout-step-one/ # URL /checkout-step-one.html
36
42
  NAV.md
37
43
  methods/
38
- submit-checkout-info.method.block.ts
44
+ checkout-info.sel.ts # CheckoutInfoSel (not a Block)
45
+ fill-first-name.method.block.ts # Method defineMethodBlock (self-loop on CheckoutInfoPage)
46
+ fill-last-name.method.block.ts # Method defineMethodBlock (self-loop on CheckoutInfoPage)
47
+ fill-postal-code.method.block.ts # Method defineMethodBlock (self-loop on CheckoutInfoPage)
48
+ submit-checkout-info.method.block.ts # Method defineMethodBlock (click only)
39
49
  checkout-step-two/ # URL /checkout-step-two.html
40
50
  NAV.md
41
51
  methods/
52
+ checkout-overview.sel.ts # CheckoutOverviewSel (not a Block)
42
53
  finish-order.method.block.ts
43
54
  ```
44
55
 
@@ -1,7 +1,5 @@
1
1
  import { defineNavClickBlock, Trait } from "waygraph";
2
- import type { StubCtx } from "waygraph";
3
2
  import type { CartPage } from "../../../states/checkout.states.js";
4
- import { InventorySel } from "../inventory/methods/inventory-items.js";
5
3
  import { CartSel } from "./cart.sel.js";
6
4
 
7
5
  /**
@@ -17,51 +15,4 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
17
15
  checkpoint: "CartPage",
18
16
  click: CartSel.cartLink,
19
17
  verify: [Trait.url({ pathname: "/cart.html" })],
20
- stubBefore: (ctx: StubCtx) => {
21
- ctx.title("Open cart · landscape");
22
- ctx.landscape();
23
- ctx.todos(
24
- "saucedemo",
25
- [
26
- { id: "login-user", text: "Enter username", done: true },
27
- { id: "login-pass", text: "Enter password", done: true },
28
- { id: "login-submit", text: "Tap Login", done: true },
29
- { id: "cart-find", text: "Find the product", done: true },
30
- { id: "cart-add", text: "Add to cart", done: true },
31
- { id: "cart-open", text: "Open cart" },
32
- ],
33
- { title: "Device showcase", index: 5 },
34
- );
35
- ctx.zoom(1.4);
36
- ctx.zoomOut(false);
37
- ctx.highlights({
38
- cart: {
39
- selector: CartSel.cartLink,
40
- label: "Cart",
41
- detail: "tone: planned (purple) - open cart",
42
- tone: "planned",
43
- focus: true,
44
- zoomOut: false,
45
- followMouse: false,
46
- weight: "bold",
47
- gesture: "tap",
48
- },
49
- });
50
- },
51
- stubAfter: (ctx: StubCtx) => {
52
- ctx.banner("Your cart · desktop");
53
- ctx.clearDevice();
54
- ctx.hideTodos();
55
- ctx.zoomOut(true);
56
- ctx.ring("items", {
57
- selector: InventorySel.cartItem,
58
- label: "Cart items",
59
- detail: "tone: success (green) - product in cart",
60
- tone: "success",
61
- focus: true,
62
- zoomOut: true,
63
- followMouse: false,
64
- duration: true,
65
- });
66
- },
67
18
  });