waygraph 0.12.46 → 0.12.47
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/dist/cli.js +79 -4
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +3 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +11 -9
- package/examples/saucedemo/src/blocks/saucedemo-web/nav-login.block.ts +11 -5
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +6 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +12 -2
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +26 -9
- package/templates/quickstart/src/blocks/saucedemo-web/nav-login.block.ts +11 -5
package/dist/cli.js
CHANGED
|
@@ -1724,6 +1724,22 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1724
1724
|
// rings stay visible ~2s before the error panel (PIA stubOnError RFC).
|
|
1725
1725
|
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1726
1726
|
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1727
|
+
let dock = opts && opts.todoDock;
|
|
1728
|
+
// Push stubAfter/before dock before the first ring so Method after-panels
|
|
1729
|
+
// show the updated checklist immediately (not only after the ring cycle).
|
|
1730
|
+
if (dock) {
|
|
1731
|
+
await page
|
|
1732
|
+
.evaluate((d) => {
|
|
1733
|
+
if (window.__wgSyncTodos) window.__wgSyncTodos({ sync: "set", dock: d });
|
|
1734
|
+
}, dock)
|
|
1735
|
+
.catch(() => {});
|
|
1736
|
+
} else if (opts && opts.todoSync === "clear") {
|
|
1737
|
+
await page
|
|
1738
|
+
.evaluate(() => {
|
|
1739
|
+
if (window.__wgSyncTodos) window.__wgSyncTodos({ sync: "clear" });
|
|
1740
|
+
})
|
|
1741
|
+
.catch(() => {});
|
|
1742
|
+
}
|
|
1727
1743
|
for (let i = 0; i < list.length; i++) {
|
|
1728
1744
|
const h = list[i];
|
|
1729
1745
|
try {
|
|
@@ -1735,7 +1751,15 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1735
1751
|
size: h.size,
|
|
1736
1752
|
weight: h.weight,
|
|
1737
1753
|
});
|
|
1738
|
-
if (
|
|
1754
|
+
if (dock) {
|
|
1755
|
+
dock = advanceTodoDock(dock, i);
|
|
1756
|
+
if (opts && opts.todoDockRef) opts.todoDockRef.current = dock;
|
|
1757
|
+
await page
|
|
1758
|
+
.evaluate((d) => {
|
|
1759
|
+
if (window.__wgSyncTodos) window.__wgSyncTodos({ sync: "set", dock: d });
|
|
1760
|
+
}, dock)
|
|
1761
|
+
.catch(() => {});
|
|
1762
|
+
} else if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1739
1763
|
const rows =
|
|
1740
1764
|
opts && opts.todos && opts.todos.length
|
|
1741
1765
|
? opts.todos
|
|
@@ -1743,7 +1767,7 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1743
1767
|
await page
|
|
1744
1768
|
.evaluate((todos) => {
|
|
1745
1769
|
if (window.__wgSyncTodos) {
|
|
1746
|
-
window.__wgSyncTodos(todos);
|
|
1770
|
+
window.__wgSyncTodos({ sync: "set", list: todos });
|
|
1747
1771
|
return;
|
|
1748
1772
|
}
|
|
1749
1773
|
// Fallback for pages that never got installOverlay sync helper.
|
|
@@ -1836,6 +1860,9 @@ async function renderAfterStep(page, info) {
|
|
|
1836
1860
|
await cycleHighlightRings(page, highlights, gatesFast, {
|
|
1837
1861
|
pace: info.pace,
|
|
1838
1862
|
stepLabel: miniStepLabel(info),
|
|
1863
|
+
todoDock: info.todoDock,
|
|
1864
|
+
todos: info.todos,
|
|
1865
|
+
todoSync: info.todoSync,
|
|
1839
1866
|
});
|
|
1840
1867
|
const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1841
1868
|
await page
|
|
@@ -2246,6 +2273,46 @@ async function matchStubForLocator(page, locator, stubs) {
|
|
|
2246
2273
|
return null;
|
|
2247
2274
|
}
|
|
2248
2275
|
|
|
2276
|
+
/** 0-based stub index for Method fill/click todo advance; -1 if none. */
|
|
2277
|
+
async function matchStubIndexForLocator(page, locator, stubs) {
|
|
2278
|
+
if (!stubs || stubs.length === 0) return -1;
|
|
2279
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
2280
|
+
if (!box) return -1;
|
|
2281
|
+
for (let i = 0; i < stubs.length; i++) {
|
|
2282
|
+
const s = stubs[i];
|
|
2283
|
+
try {
|
|
2284
|
+
const b = await page.locator(s.selector).first().boundingBox();
|
|
2285
|
+
if (
|
|
2286
|
+
b &&
|
|
2287
|
+
Math.abs(b.x - box.x) < 4 &&
|
|
2288
|
+
Math.abs(b.y - box.y) < 4 &&
|
|
2289
|
+
Math.abs(b.width - box.width) < 8
|
|
2290
|
+
) {
|
|
2291
|
+
return i;
|
|
2292
|
+
}
|
|
2293
|
+
} catch {
|
|
2294
|
+
/* skip */
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return -1;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
/** Advance sequential todo dock to stubIndex and push to the page (Method act). */
|
|
2301
|
+
async function syncTodoDockAdvance(page, todoDockRef, stubIndex) {
|
|
2302
|
+
if (!todoDockRef || stubIndex < 0) return;
|
|
2303
|
+
const prev = todoDockRef.current;
|
|
2304
|
+
if (!prev) return;
|
|
2305
|
+
const advanced = advanceTodoDock(prev, stubIndex);
|
|
2306
|
+
todoDockRef.current = advanced;
|
|
2307
|
+
await page
|
|
2308
|
+
.evaluate((dock) => {
|
|
2309
|
+
if (window.__wgSyncTodos) {
|
|
2310
|
+
window.__wgSyncTodos({ sync: "set", dock });
|
|
2311
|
+
}
|
|
2312
|
+
}, advanced)
|
|
2313
|
+
.catch(() => {});
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2249
2316
|
async function captionForLocator(page, locator, stubs, fallback) {
|
|
2250
2317
|
const matched = await matchStubForLocator(page, locator, stubs);
|
|
2251
2318
|
if (matched) {
|
|
@@ -2619,7 +2686,7 @@ async function wasJustNarrated(page) {
|
|
|
2619
2686
|
.catch(() => false);
|
|
2620
2687
|
}
|
|
2621
2688
|
|
|
2622
|
-
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef) {
|
|
2689
|
+
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef) {
|
|
2623
2690
|
// Playwright's own slowMo ALREADY pauses after every single low-level
|
|
2624
2691
|
// action it dispatches - and pressSequentially() fires one such action
|
|
2625
2692
|
// PER CHARACTER. Also giving pressSequentially its own fixed delay
|
|
@@ -2675,6 +2742,8 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2675
2742
|
: "writing from mem";
|
|
2676
2743
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2677
2744
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2745
|
+
const stubIdx = await matchStubIndexForLocator(page, this, stubs);
|
|
2746
|
+
await syncTodoDockAdvance(page, todoDockRef, stubIdx);
|
|
2678
2747
|
await moveCursorTo(page, box, cursorMs(500));
|
|
2679
2748
|
await showRing(page, box, cap.label, cap.tone, {
|
|
2680
2749
|
size: cap.size,
|
|
@@ -2749,6 +2818,8 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2749
2818
|
}
|
|
2750
2819
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2751
2820
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2821
|
+
const stubIdx = await matchStubIndexForLocator(page, this, stubs);
|
|
2822
|
+
await syncTodoDockAdvance(page, todoDockRef, stubIdx);
|
|
2752
2823
|
clickTone = cap.tone;
|
|
2753
2824
|
clickPoint = await moveCursorTo(page, box, cursorMs(600));
|
|
2754
2825
|
await showRing(page, box, cap.label, cap.tone, {
|
|
@@ -2887,7 +2958,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2887
2958
|
const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
|
|
2888
2959
|
const pacing = { gatesFast: demoFast, gatesSlow: false, skipTheater: false, demoPace: "normal" };
|
|
2889
2960
|
const stubBeforeRef = { current: [] };
|
|
2890
|
-
|
|
2961
|
+
/** Live todo dock for Method fill/click advance (same carry as lastTodoDock). */
|
|
2962
|
+
const todoDockRef = { current: undefined };
|
|
2963
|
+
instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef);
|
|
2891
2964
|
// Force the panel checkbox from this process's flags/env at run start.
|
|
2892
2965
|
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
2893
2966
|
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
@@ -3108,6 +3181,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3108
3181
|
todoPos: stubBeforePhase.todoPos,
|
|
3109
3182
|
});
|
|
3110
3183
|
lastTodoDock = appliedBefore.dock;
|
|
3184
|
+
todoDockRef.current = lastTodoDock;
|
|
3111
3185
|
const stubBeforeTodos =
|
|
3112
3186
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3113
3187
|
stubBeforePhase.todos ||
|
|
@@ -3341,6 +3415,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3341
3415
|
todoPos: afterPhase.todoPos,
|
|
3342
3416
|
});
|
|
3343
3417
|
lastTodoDock = appliedAfter.dock;
|
|
3418
|
+
todoDockRef.current = lastTodoDock;
|
|
3344
3419
|
stubAfterTodos =
|
|
3345
3420
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3346
3421
|
afterPhase.todos ||
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineNavClickBlock, Trait } from "waygraph";
|
|
2
|
+
import type { StubCtx } from "waygraph";
|
|
2
3
|
import type { CartPage } from "../../../states/checkout.states.js";
|
|
3
4
|
import { InventorySel } from "../inventory/methods/inventory-items.js";
|
|
4
5
|
|
|
@@ -15,7 +16,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
|
15
16
|
checkpoint: "CartPage",
|
|
16
17
|
click: ".shopping_cart_link",
|
|
17
18
|
verify: [Trait.url({ pathname: "/cart.html" })],
|
|
18
|
-
stubBefore: (ctx) => {
|
|
19
|
+
stubBefore: (ctx: StubCtx) => {
|
|
19
20
|
ctx.title("Open cart");
|
|
20
21
|
ctx.todoId("saucedemo-cart");
|
|
21
22
|
ctx.todos([
|
|
@@ -40,7 +41,7 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
|
40
41
|
},
|
|
41
42
|
});
|
|
42
43
|
},
|
|
43
|
-
stubAfter: (ctx) => {
|
|
44
|
+
stubAfter: (ctx: StubCtx) => {
|
|
44
45
|
ctx.banner("Your cart");
|
|
45
46
|
ctx.zoomOut(false);
|
|
46
47
|
ctx.ring("items", {
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineEffectBlock, checkpoint, type Checkpoint, type WaygraphInstanceOption } from "waygraph";
|
|
2
|
+
import type { StubCtx } from "waygraph";
|
|
2
3
|
import type { ItemInCart } from "../../../../states/checkout.states.js";
|
|
3
4
|
import { SelectedItem } from "../../../../states/checkout.mem-keys.js";
|
|
4
5
|
import { collectAddableItems, InventorySel } from "./inventory-items.js";
|
|
@@ -34,7 +35,7 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
34
35
|
},
|
|
35
36
|
],
|
|
36
37
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
37
|
-
stubBefore: (ctx) => {
|
|
38
|
+
stubBefore: (ctx: StubCtx) => {
|
|
38
39
|
ctx.title("Pick a product");
|
|
39
40
|
ctx.todoId("saucedemo-cart");
|
|
40
41
|
ctx.todos([
|
|
@@ -66,7 +67,7 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
66
67
|
},
|
|
67
68
|
});
|
|
68
69
|
},
|
|
69
|
-
stubAfter: (ctx) => {
|
|
70
|
+
stubAfter: (ctx: StubCtx) => {
|
|
70
71
|
ctx.banner("In the cart");
|
|
71
72
|
ctx.todoId("saucedemo-cart");
|
|
72
73
|
ctx.todos([
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineMethodBlock, checkpoint, Trait } from "waygraph";
|
|
1
|
+
import { defineMethodBlock, checkpoint, Trait, type StubCtx } from "waygraph";
|
|
2
2
|
import type { LoginPage, LoginSubmitOutcome } from "../../../states/checkout.states.js";
|
|
3
3
|
import { LoginCreds } from "../../../states/checkout.mem-keys.js";
|
|
4
4
|
|
|
@@ -9,8 +9,10 @@ type LoginObserve = "success" | "failure";
|
|
|
9
9
|
* Helper: defineMethodBlock
|
|
10
10
|
* Route: saucedemo-web/methods/ (page URL /)
|
|
11
11
|
*
|
|
12
|
-
* Fills credentials and submits - never page.goto (nav-login owns that).
|
|
12
|
+
* Fills credentials and submits - never page.goto (nav-login Page hub owns that).
|
|
13
13
|
* Branches: good creds -> LoggedIn; bad/locked -> LoginPage with error banner.
|
|
14
|
+
* stubBefore/stubAfter drive the floating todo dock (Method fill/click advances
|
|
15
|
+
* todoIndex mid-act; stubAfter marks complete / fail).
|
|
14
16
|
*/
|
|
15
17
|
export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOutcome>({
|
|
16
18
|
name: "submit-login",
|
|
@@ -47,7 +49,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
47
49
|
: [Trait.visible('[data-test="error"]')],
|
|
48
50
|
// Open lifecycle: banner title, todos, Screen Studio camera zoom,
|
|
49
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
50
|
-
stubBefore: (ctx) => {
|
|
52
|
+
stubBefore: (ctx: StubCtx) => {
|
|
51
53
|
ctx.title("Signing in");
|
|
52
54
|
ctx.todoId("saucedemo-login");
|
|
53
55
|
ctx.todos([
|
|
@@ -82,18 +84,18 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
82
84
|
},
|
|
83
85
|
});
|
|
84
86
|
},
|
|
85
|
-
stubAfter: (ctx) => {
|
|
87
|
+
stubAfter: (ctx: StubCtx) => {
|
|
86
88
|
// Fail branch lands on LoginPage with Epic sadface - do not ring the
|
|
87
89
|
// inventory shelf. Success (LoggedIn) hands off to shopFlow.
|
|
88
90
|
if (ctx.out && ctx.out.__state === "LoginPage") {
|
|
89
91
|
ctx.title("Login blocked");
|
|
90
92
|
ctx.todoId("saucedemo-login");
|
|
91
93
|
ctx.todos([
|
|
92
|
-
{ id: "login-user", text: "Enter username" },
|
|
93
|
-
{ id: "login-pass", text: "Enter password" },
|
|
94
|
-
{ id: "login-submit", text: "Click Login" },
|
|
94
|
+
{ id: "login-user", text: "Enter username", done: true },
|
|
95
|
+
{ id: "login-pass", text: "Enter password", done: true },
|
|
96
|
+
{ id: "login-submit", text: "Click Login", done: true },
|
|
95
97
|
]);
|
|
96
|
-
ctx.
|
|
98
|
+
ctx.todoStyle("checklist");
|
|
97
99
|
ctx.ring("error", {
|
|
98
100
|
selector: '[data-test="error"]',
|
|
99
101
|
label: "Login error banner",
|
|
@@ -123,7 +125,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
123
125
|
duration: true,
|
|
124
126
|
});
|
|
125
127
|
},
|
|
126
|
-
stubOnError: (ctx) => {
|
|
128
|
+
stubOnError: (ctx: StubCtx) => {
|
|
127
129
|
ctx.title("Login failed");
|
|
128
130
|
ctx.ring("error", {
|
|
129
131
|
selector: '[data-test="error"]',
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definePageBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoginPage } from "../../states/checkout.states.js";
|
|
3
|
+
import { SubmitLoginActionBlock } from "./methods/submit-login.method.block.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Kind:
|
|
6
|
-
* Helper:
|
|
6
|
+
* Kind: Page
|
|
7
|
+
* Helper: definePageBlock
|
|
7
8
|
* Route: saucedemo-web/ (= /)
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Login screen hub. Deep-link via url; method submit-login hangs off this
|
|
11
|
+
* page (stubBefore/stubAfter todos live on the Method). Name stays
|
|
12
|
+
* `nav-login` so existing flows / chain specs keep working.
|
|
10
13
|
*/
|
|
11
|
-
export const NavLoginBlock =
|
|
14
|
+
export const NavLoginBlock = definePageBlock<LoginPage>({
|
|
12
15
|
name: "nav-login",
|
|
13
16
|
description: "Goes to the Swag Labs login page.",
|
|
14
17
|
checkpoint: "LoginPage",
|
|
15
18
|
url: "/",
|
|
16
19
|
verify: [Trait.visible("#login-button")],
|
|
20
|
+
methods: {
|
|
21
|
+
submitLogin: () => SubmitLoginActionBlock,
|
|
22
|
+
},
|
|
17
23
|
});
|
package/package.json
CHANGED
|
@@ -17,7 +17,12 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
|
17
17
|
verify: [Trait.url({ pathname: "/cart.html" })],
|
|
18
18
|
stubBefore: (ctx) => {
|
|
19
19
|
ctx.title("Open cart");
|
|
20
|
-
ctx.
|
|
20
|
+
ctx.todoId("saucedemo-cart");
|
|
21
|
+
ctx.todos([
|
|
22
|
+
{ id: "cart-find", text: "Find the product" },
|
|
23
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
24
|
+
{ id: "cart-open", text: "Open cart" },
|
|
25
|
+
]);
|
|
21
26
|
ctx.todoIndex(2);
|
|
22
27
|
ctx.zoom(1.4);
|
|
23
28
|
ctx.zoomOut(false);
|
package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -36,7 +36,12 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
36
36
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
37
37
|
stubBefore: (ctx) => {
|
|
38
38
|
ctx.title("Pick a product");
|
|
39
|
-
ctx.
|
|
39
|
+
ctx.todoId("saucedemo-cart");
|
|
40
|
+
ctx.todos([
|
|
41
|
+
{ id: "cart-find", text: "Find the product" },
|
|
42
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
43
|
+
{ id: "cart-open", text: "Open cart" },
|
|
44
|
+
]);
|
|
40
45
|
ctx.todoIndex(0);
|
|
41
46
|
ctx.zoom(1.35);
|
|
42
47
|
ctx.zoomOut(false);
|
|
@@ -63,7 +68,12 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
63
68
|
},
|
|
64
69
|
stubAfter: (ctx) => {
|
|
65
70
|
ctx.banner("In the cart");
|
|
66
|
-
ctx.
|
|
71
|
+
ctx.todoId("saucedemo-cart");
|
|
72
|
+
ctx.todos([
|
|
73
|
+
{ id: "cart-find", text: "Find the product" },
|
|
74
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
75
|
+
{ id: "cart-open", text: "Open cart" },
|
|
76
|
+
]);
|
|
67
77
|
ctx.todoIndex(1);
|
|
68
78
|
ctx.zoomOut(false);
|
|
69
79
|
ctx.ring("badge", {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineMethodBlock, checkpoint, Trait } from "waygraph";
|
|
1
|
+
import { defineMethodBlock, checkpoint, Trait, type StubCtx } from "waygraph";
|
|
2
2
|
import type { LoginPage, LoginSubmitOutcome } from "../../../states/checkout.states.js";
|
|
3
3
|
import { LoginCreds } from "../../../states/checkout.mem-keys.js";
|
|
4
4
|
|
|
@@ -9,8 +9,10 @@ type LoginObserve = "success" | "failure";
|
|
|
9
9
|
* Helper: defineMethodBlock
|
|
10
10
|
* Route: saucedemo-web/methods/ (page URL /)
|
|
11
11
|
*
|
|
12
|
-
* Fills credentials and submits - never page.goto (nav-login owns that).
|
|
12
|
+
* Fills credentials and submits - never page.goto (nav-login Page hub owns that).
|
|
13
13
|
* Branches: good creds -> LoggedIn; bad/locked -> LoginPage with error banner.
|
|
14
|
+
* stubBefore/stubAfter drive the floating todo dock (Method fill/click advances
|
|
15
|
+
* todoIndex mid-act; stubAfter marks complete / fail).
|
|
14
16
|
*/
|
|
15
17
|
export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOutcome>({
|
|
16
18
|
name: "submit-login",
|
|
@@ -47,9 +49,14 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
47
49
|
: [Trait.visible('[data-test="error"]')],
|
|
48
50
|
// Open lifecycle: banner title, todos, Screen Studio camera zoom,
|
|
49
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
50
|
-
stubBefore: (ctx) => {
|
|
52
|
+
stubBefore: (ctx: StubCtx) => {
|
|
51
53
|
ctx.title("Signing in");
|
|
52
|
-
ctx.
|
|
54
|
+
ctx.todoId("saucedemo-login");
|
|
55
|
+
ctx.todos([
|
|
56
|
+
{ id: "login-user", text: "Enter username" },
|
|
57
|
+
{ id: "login-pass", text: "Enter password" },
|
|
58
|
+
{ id: "login-submit", text: "Click Login" },
|
|
59
|
+
]);
|
|
53
60
|
ctx.todoIndex(0);
|
|
54
61
|
ctx.zoom(1.35);
|
|
55
62
|
ctx.highlights({
|
|
@@ -77,13 +84,18 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
77
84
|
},
|
|
78
85
|
});
|
|
79
86
|
},
|
|
80
|
-
stubAfter: (ctx) => {
|
|
87
|
+
stubAfter: (ctx: StubCtx) => {
|
|
81
88
|
// Fail branch lands on LoginPage with Epic sadface - do not ring the
|
|
82
89
|
// inventory shelf. Success (LoggedIn) hands off to shopFlow.
|
|
83
90
|
if (ctx.out && ctx.out.__state === "LoginPage") {
|
|
84
91
|
ctx.title("Login blocked");
|
|
85
|
-
ctx.
|
|
86
|
-
ctx.
|
|
92
|
+
ctx.todoId("saucedemo-login");
|
|
93
|
+
ctx.todos([
|
|
94
|
+
{ id: "login-user", text: "Enter username", done: true },
|
|
95
|
+
{ id: "login-pass", text: "Enter password", done: true },
|
|
96
|
+
{ id: "login-submit", text: "Click Login", done: true },
|
|
97
|
+
]);
|
|
98
|
+
ctx.todoStyle("checklist");
|
|
87
99
|
ctx.ring("error", {
|
|
88
100
|
selector: '[data-test="error"]',
|
|
89
101
|
label: "Login error banner",
|
|
@@ -95,7 +107,12 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
95
107
|
return;
|
|
96
108
|
}
|
|
97
109
|
ctx.banner("Inventory");
|
|
98
|
-
ctx.
|
|
110
|
+
ctx.todoId("saucedemo-login");
|
|
111
|
+
ctx.todos([
|
|
112
|
+
{ id: "login-user", text: "Enter username" },
|
|
113
|
+
{ id: "login-pass", text: "Enter password" },
|
|
114
|
+
{ id: "login-submit", text: "Click Login" },
|
|
115
|
+
]);
|
|
99
116
|
ctx.todoIndex(2);
|
|
100
117
|
// Landed on inventory - hand off to shopFlow for product -> cart zoom.
|
|
101
118
|
ctx.ring("shelf", {
|
|
@@ -108,7 +125,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
108
125
|
duration: true,
|
|
109
126
|
});
|
|
110
127
|
},
|
|
111
|
-
stubOnError: (ctx) => {
|
|
128
|
+
stubOnError: (ctx: StubCtx) => {
|
|
112
129
|
ctx.title("Login failed");
|
|
113
130
|
ctx.ring("error", {
|
|
114
131
|
selector: '[data-test="error"]',
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definePageBlock, Trait } from "waygraph";
|
|
2
2
|
import type { LoginPage } from "../../states/checkout.states.js";
|
|
3
|
+
import { SubmitLoginActionBlock } from "./methods/submit-login.method.block.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Kind:
|
|
6
|
-
* Helper:
|
|
6
|
+
* Kind: Page
|
|
7
|
+
* Helper: definePageBlock
|
|
7
8
|
* Route: saucedemo-web/ (= /)
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Login screen hub. Deep-link via url; method submit-login hangs off this
|
|
11
|
+
* page (stubBefore/stubAfter todos live on the Method). Name stays
|
|
12
|
+
* `nav-login` so existing flows / chain specs keep working.
|
|
10
13
|
*/
|
|
11
|
-
export const NavLoginBlock =
|
|
14
|
+
export const NavLoginBlock = definePageBlock<LoginPage>({
|
|
12
15
|
name: "nav-login",
|
|
13
16
|
description: "Goes to the Swag Labs login page.",
|
|
14
17
|
checkpoint: "LoginPage",
|
|
15
18
|
url: "/",
|
|
16
19
|
verify: [Trait.visible("#login-button")],
|
|
20
|
+
methods: {
|
|
21
|
+
submitLogin: () => SubmitLoginActionBlock,
|
|
22
|
+
},
|
|
17
23
|
});
|