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.
- package/README.md +8 -1
- package/dist/analysis/graph.d.ts +3 -0
- package/dist/analysis/map-check.d.ts +3 -2
- package/dist/auto-session/helpers.d.ts +10 -0
- package/dist/blocks-select.d.ts +3 -0
- package/dist/blocks-select.js +2 -0
- package/dist/cli/flags.js +3 -1
- package/dist/cli/usage.js +4 -1
- package/dist/cli.js +4 -1
- package/dist/commands/agent-dive-scaffold.d.ts +0 -20
- package/dist/commands/agent-dive-scaffold.js +3 -3
- package/dist/commands/init.js +1 -1
- package/dist/commands/upgrade.d.ts +1 -1
- package/dist/commands/upgrade.js +7 -1
- package/dist/config.d.ts +55 -19
- package/dist/config.js +55 -13
- package/dist/core/block.d.ts +7 -2
- package/dist/core/block.js +7 -3
- package/dist/core/blocks/api.d.ts +1 -0
- package/dist/core/blocks/method.d.ts +2 -0
- package/dist/core/blocks/method.js +1 -0
- package/dist/core/blocks/nav.d.ts +1 -0
- package/dist/core/blocks/page.d.ts +1 -0
- package/dist/core/branch-regression.d.ts +1 -0
- package/dist/core/compose.d.ts +1 -0
- package/dist/core/engine.d.ts +2 -0
- package/dist/core/engine.js +3 -1
- package/dist/core/flow.d.ts +11 -0
- package/dist/core/flow.js +32 -8
- package/dist/core/map-builder.d.ts +34 -1
- package/dist/core/map-builder.js +42 -4
- package/dist/core/run-graph.d.ts +2 -0
- package/dist/core/run-graph.js +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +1 -0
- package/dist/highlights/demo-pace.d.ts +4 -1
- package/dist/highlights/demo-pace.js +28 -3
- package/dist/highlights/device.d.ts +2 -0
- package/dist/highlights/device.js +2 -0
- package/dist/highlights/types.d.ts +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/pilot-overlay/action-ring.d.ts +0 -9
- package/dist/pilot-overlay/action-ring.js +1 -1
- package/dist/pilot-overlay/panel.d.ts +2 -1
- package/dist/runner/interaction-patch.js +32 -1
- package/dist/runner/main.js +5 -2
- package/dist/runner/step-mode.js +10 -4
- package/dist/session/auto-explore-run.js +6 -0
- package/dist/session/auto-explore.d.ts +2 -1
- package/dist/session/auto-session-ipc.d.ts +1 -2
- package/dist/session/auto-session-ipc.js +1 -1
- package/dist/session/block-inject.d.ts +0 -13
- package/dist/session/block-inject.js +3 -3
- package/dist/session/overlay-beacon.d.ts +7 -0
- package/dist/session/overlay-beacon.js +5 -0
- package/dist/session/pilot.d.ts +1 -2
- package/dist/session/pilot.js +1 -2
- package/dist/traverse/coverage.d.ts +4 -4
- package/dist/traverse/coverage.js +4 -1
- package/dist/traverse/lease.d.ts +1 -0
- package/dist/traverse/lease.js +1 -0
- package/dist/traverse/run.d.ts +3 -0
- package/dist/types.d.ts +1 -1
- package/examples/routed-demo/src/flows/routed-demo-map.flow.ts +1 -1
- package/examples/routed-demo/src/flows/routed-demo.flow.ts +2 -2
- package/examples/saucedemo/README.md +11 -12
- package/examples/saucedemo/config/waygraph.config.ts +17 -0
- package/examples/saucedemo/docs/CONVENTION.md +1 -2
- package/examples/saucedemo/docs/HELPERS.md +1 -1
- package/examples/saucedemo/package-lock.json +10 -3
- package/examples/saucedemo/package.json +12 -29
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
- package/examples/saucedemo/src/flows/api-demo.flow.ts +17 -11
- package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -14
- package/examples/saucedemo/src/flows/cart-continue.flow.ts +10 -8
- package/examples/saucedemo/src/flows/cart-mem.flow.ts +7 -7
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +13 -8
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +26 -48
- package/examples/saucedemo/src/flows/checkout.flow.ts +70 -86
- package/examples/saucedemo/src/flows/device-showcase.flow.ts +24 -25
- package/examples/saucedemo/src/flows/login.flow.ts +11 -18
- package/examples/saucedemo/src/flows/mem-stub-demo.flow.ts +9 -20
- package/examples/saucedemo/src/flows/shop.flow.ts +15 -20
- package/examples/saucedemo/src/flows/smoke.flow.ts +12 -27
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +24 -43
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +26 -34
- package/examples/saucedemo/src/states/checkout.mem-stubs.ts +11 -0
- package/examples/saucedemo/src/stubs/device-showcase.ts +193 -0
- package/examples/saucedemo/src/stubs/login.ts +37 -0
- package/examples/saucedemo/tests/chain-flow-episodes.spec.ts +7 -9
- package/examples/saucedemo/tests/submit-login-branch.spec.ts +18 -5
- package/package.json +12 -5
- package/templates/quickstart/README.md +4 -7
- package/templates/quickstart/config/waygraph.config.ts +17 -0
- package/templates/quickstart/docs/CONVENTION.md +1 -2
- package/templates/quickstart/docs/HELPERS.md +1 -1
- package/templates/quickstart/package.json +14 -14
- package/templates/quickstart/src/blocks/SITE-MAP.md +14 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
- package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
- package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
- package/templates/quickstart/src/flows/api-demo.flow.ts +43 -0
- package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -14
- package/templates/quickstart/src/flows/cart-continue.flow.ts +10 -8
- package/templates/quickstart/src/flows/cart-mem.flow.ts +7 -7
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +13 -8
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +26 -48
- package/templates/quickstart/src/flows/checkout.flow.ts +70 -63
- package/templates/quickstart/src/flows/device-showcase.flow.ts +24 -25
- package/templates/quickstart/src/flows/login.flow.ts +11 -18
- package/templates/quickstart/src/flows/mem-stub-demo.flow.ts +12 -0
- package/templates/quickstart/src/flows/shop.flow.ts +15 -20
- package/templates/quickstart/src/flows/smoke.flow.ts +12 -27
- package/templates/quickstart/src/flows/stub-on-error.flow.ts +31 -0
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +26 -34
- package/templates/quickstart/src/states/checkout.mem-stubs.ts +11 -0
- package/templates/quickstart/src/stubs/device-showcase.ts +193 -0
- package/templates/quickstart/src/stubs/login.ts +37 -0
- package/templates/quickstart/tests/chain-flow.spec.ts +7 -10
- package/templates/scaffold/README.md +1 -1
- package/templates/scaffold/STRUCTURE.md +5 -2
- package/templates/scaffold/config/waygraph.config.ts +24 -5
- package/templates/scaffold/package.json +6 -6
- package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
- package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
- package/templates/scaffold/src/flows/README.md +35 -14
- package/templates/scaffold/src/flows/{shop.flow.ts → home/cart.flow.ts} +18 -17
- package/templates/scaffold/src/flows/{example.flow.ts → home/hello.flow.ts} +12 -13
- package/templates/scaffold/tests/example.spec.ts +5 -5
- package/templates/scaffold/tests/hoppscotch.spec.ts +3 -3
- package/templates/scaffold/tests/mail-verify.spec.ts +7 -7
- package/templates/skills/waygraph-convention.skill.md +14 -6
- package/examples/saucedemo/playwright.config.ts +0 -19
- package/examples/saucedemo/scripts/drive-auto-cli.mjs +0 -93
- package/examples/saucedemo/scripts/prove-add-to-cart-menu.mjs +0 -126
- package/examples/saucedemo/scripts/prove-auto-graph.mjs +0 -92
- package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +0 -63
- package/templates/quickstart/playwright.config.ts +0 -14
- package/templates/quickstart/scripts/drive-auto-cli.mjs +0 -93
- package/templates/scaffold/src/flows/hoppscotch-auth.flow.ts +0 -35
- package/templates/scaffold/src/flows/hoppscotch-get.flow.ts +0 -23
- package/templates/scaffold/src/flows/hoppscotch-post.flow.ts +0 -33
- package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -46
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
38
|
-
)
|
|
39
|
-
|
|
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
|
|
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
|
|
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("
|
|
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 =
|
|
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("
|
|
24
|
-
|
|
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
|
|
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
|
|
14
|
-
|
|
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
|
|
25
|
-
|
|
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.
|
|
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`.
|
|
4
|
-
`
|
|
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
|
-
|
|
7
|
-
|
|
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` > `
|
|
86
|
-
> `playwright.config` `baseURL`.
|
|
85
|
+
`demo`: `--base-url` > `WAYGRAPH_BASE_URL` > `baseUrl` in `config/waygraph.config.ts`.
|
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"demo:
|
|
22
|
-
"demo:
|
|
23
|
-
"demo:
|
|
24
|
-
"
|
|
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": "
|
|
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 #
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|