waygraph 0.7.10 → 0.7.12
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 +2 -0
- package/dist/auto-explore-run.js +15 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +55 -14
- package/dist/step-overlay.js +29 -20
- package/examples/saucedemo/README.md +35 -8
- package/examples/saucedemo/docs/CONVENTION.md +32 -14
- package/examples/saucedemo/package-lock.json +118 -0
- package/examples/saucedemo/scripts/capture-wrong-login.mjs +2 -2
- package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +2 -2
- package/examples/saucedemo/src/blocks/SITE-MAP.md +43 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +26 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/actions/submit-login-for-flow.ts +13 -0
- package/examples/saucedemo/src/blocks/{actions → saucedemo-web/actions}/submit-login.action.block.ts +14 -10
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web}/actions/submit-logout.action.block.ts +8 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +22 -0
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/cart}/nav-cart.block.ts +8 -3
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/cart}/nav-checkout-info.block.ts +8 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +21 -0
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one}/actions/submit-checkout-info.action.block.ts +9 -6
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +21 -0
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two}/actions/finish-order.action.block.ts +8 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/NAV.md +26 -0
- package/examples/saucedemo/src/blocks/{actions → saucedemo-web/inventory/actions}/add-to-cart.effect.block.ts +10 -4
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/inventory}/actions/inventory-items.ts +8 -0
- package/{templates/quickstart/src/blocks → examples/saucedemo/src/blocks/saucedemo-web/inventory}/actions/remove-from-cart.effect.block.ts +10 -4
- package/examples/saucedemo/src/blocks/{nav-item-detail.block.ts → saucedemo-web/inventory/nav-item-detail.block.ts} +10 -4
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/NAV.md +25 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +18 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +18 -0
- package/examples/saucedemo/src/blocks/{nav-continue-shopping.block.ts → saucedemo-web/inventory-item/nav-continue-shopping.block.ts} +8 -2
- package/examples/saucedemo/src/blocks/{nav-login.block.ts → saucedemo-web/nav-login.block.ts} +8 -3
- package/examples/saucedemo/src/flows/cart-continue.flow.ts +2 -2
- package/examples/saucedemo/src/flows/cart-mem.flow.ts +1 -1
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +3 -3
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +8 -8
- package/examples/saucedemo/src/flows/checkout.flow.ts +7 -7
- package/examples/saucedemo/src/flows/login.flow.ts +2 -2
- package/examples/saucedemo/src/flows/shop.flow.ts +5 -5
- package/examples/saucedemo/src/flows/smoke.flow.ts +3 -3
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +2 -2
- package/examples/saucedemo/tests/flow-block-verify.spec.ts +1 -1
- package/examples/saucedemo/tests/submit-login-branch.spec.ts +2 -2
- package/package.json +1 -1
- package/templates/quickstart/README.md +10 -0
- package/templates/quickstart/docs/CONVENTION.md +48 -26
- package/templates/quickstart/package.json +1 -1
- package/templates/quickstart/src/blocks/SITE-MAP.md +43 -0
- package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +26 -0
- package/templates/quickstart/src/blocks/saucedemo-web/actions/submit-login-for-flow.ts +13 -0
- package/templates/quickstart/src/blocks/{actions → saucedemo-web/actions}/submit-login.action.block.ts +14 -10
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web}/actions/submit-logout.action.block.ts +8 -3
- package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +22 -0
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/cart}/nav-cart.block.ts +8 -3
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/cart}/nav-checkout-info.block.ts +8 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +21 -0
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/checkout-step-one}/actions/submit-checkout-info.action.block.ts +9 -6
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +21 -0
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/checkout-step-two}/actions/finish-order.action.block.ts +8 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/NAV.md +26 -0
- package/templates/quickstart/src/blocks/{actions → saucedemo-web/inventory/actions}/add-to-cart.effect.block.ts +10 -4
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/inventory}/actions/inventory-items.ts +8 -0
- package/{examples/saucedemo/src/blocks → templates/quickstart/src/blocks/saucedemo-web/inventory}/actions/remove-from-cart.effect.block.ts +10 -4
- package/templates/quickstart/src/blocks/{nav-item-detail.block.ts → saucedemo-web/inventory/nav-item-detail.block.ts} +10 -4
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/NAV.md +25 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +18 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +18 -0
- package/templates/quickstart/src/blocks/{nav-continue-shopping.block.ts → saucedemo-web/inventory-item/nav-continue-shopping.block.ts} +8 -2
- package/templates/quickstart/src/blocks/{nav-login.block.ts → saucedemo-web/nav-login.block.ts} +8 -3
- package/templates/quickstart/src/flows/cart-continue.flow.ts +2 -2
- package/templates/quickstart/src/flows/cart-mem.flow.ts +1 -1
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +3 -3
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +8 -8
- package/templates/quickstart/src/flows/checkout.flow.ts +7 -7
- package/templates/quickstart/src/flows/login.flow.ts +2 -2
- package/templates/quickstart/src/flows/shop.flow.ts +5 -5
- package/templates/quickstart/src/flows/smoke.flow.ts +3 -3
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +2 -2
- package/examples/saucedemo/src/blocks/actions/submit-login-for-flow.ts +0 -10
- package/examples/saucedemo/src/blocks/nav-back-to-inventory.block.ts +0 -15
- package/examples/saucedemo/src/blocks/nav-back-to-products.block.ts +0 -14
- package/templates/quickstart/src/blocks/actions/submit-login-for-flow.ts +0 -10
- package/templates/quickstart/src/blocks/nav-back-to-inventory.block.ts +0 -15
- package/templates/quickstart/src/blocks/nav-back-to-products.block.ts +0 -14
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Browser docs (quick start, **demo / step / autoplay**, **auto explore**, engine
|
|
|
23
23
|
- **Demo contract:** [`docs/demo.html`](./docs/demo.html) — `--step` vs `--autoplay`
|
|
24
24
|
- **Auto explore:** [`docs/auto.html`](./docs/auto.html) — Effect / MemNav picker (TS salt over Blocks)
|
|
25
25
|
- **In-repo example:** [`examples/saucedemo`](./examples/saucedemo) — full Sauce Demo project
|
|
26
|
+
- **Convention showcase (Pages):** [`docs/saucedemo/`](./docs/saucedemo/) —
|
|
27
|
+
https://deviate-dv8.github.io/waygraph/saucedemo/
|
|
26
28
|
|
|
27
29
|
This README stays the in-repo API narrative; Pages is the same material for eyeballing and cross-mesh handoff.
|
|
28
30
|
|
package/dist/auto-explore-run.js
CHANGED
|
@@ -182,7 +182,21 @@ async function runOneBlock(engine, entry, context, page, mem, headful) {
|
|
|
182
182
|
if (headful)
|
|
183
183
|
await setAutoPanelRunning(page, entry.block.name);
|
|
184
184
|
const flow = engine.defineFlow([start, entry.block, end]);
|
|
185
|
-
|
|
185
|
+
const BLOCK_TIMEOUT_MS = 45_000;
|
|
186
|
+
let timer;
|
|
187
|
+
try {
|
|
188
|
+
await Promise.race([
|
|
189
|
+
flow.run(context, mem, { page, closeOnFinish: false }),
|
|
190
|
+
new Promise((_, reject) => {
|
|
191
|
+
timer = setTimeout(() => reject(new Error(`Block "${entry.block.name}" timed out after ${BLOCK_TIMEOUT_MS / 1000}s - ` +
|
|
192
|
+
"click Quit and retry, or check network / mem credentials")), BLOCK_TIMEOUT_MS);
|
|
193
|
+
}),
|
|
194
|
+
]);
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
if (timer)
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
}
|
|
186
200
|
await page.evaluate("delete window.__wgPendingNavClickLabel").catch(() => { });
|
|
187
201
|
}
|
|
188
202
|
const PANEL_CSS = "#wg-auto-panel{position:fixed;z-index:2147483647;left:50%;bottom:12px;transform:translateX(-50%);" +
|
package/dist/cli.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Commands (watch first, then run, then inspect, then scaffold):
|
|
6
6
|
* demo <flow|spec> [project] Watch a flow/chain with step overlay (default STEP+headed)
|
|
7
|
-
* try [demo]
|
|
7
|
+
* try [demo|auto] One-shot saucedemo in an OS temp dir (demo=stepper, auto=explore)
|
|
8
8
|
* chain <spec> [project] Ad-hoc Blocks by name (+ --step/--autoplay like demo)
|
|
9
9
|
* run <flow> [project] Run a named flow (no overlay unless --step)
|
|
10
10
|
* list / nav / validate Discover and inspect flows
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Commands (watch first, then run, then inspect, then scaffold):
|
|
6
6
|
* demo <flow|spec> [project] Watch a flow/chain with step overlay (default STEP+headed)
|
|
7
|
-
* try [demo]
|
|
7
|
+
* try [demo|auto] One-shot saucedemo in an OS temp dir (demo=stepper, auto=explore)
|
|
8
8
|
* chain <spec> [project] Ad-hoc Blocks by name (+ --step/--autoplay like demo)
|
|
9
9
|
* run <flow> [project] Run a named flow (no overlay unless --step)
|
|
10
10
|
* list / nav / validate Discover and inspect flows
|
|
@@ -2459,15 +2459,12 @@ async function wireQuickstartToPackageRoot(destDir) {
|
|
|
2459
2459
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2460
2460
|
}
|
|
2461
2461
|
/**
|
|
2462
|
-
*
|
|
2463
|
-
*
|
|
2464
|
-
* blocked Viewer), then run the headless Playwright test, and print where
|
|
2465
|
-
* everything lives.
|
|
2462
|
+
* Copy templates/quickstart into a fresh OS temp dir and wire it to this
|
|
2463
|
+
* package build. Returns destDir, or null after setting exitCode on failure.
|
|
2466
2464
|
*/
|
|
2467
|
-
async function
|
|
2468
|
-
const destDir = mkdtempSync(join(tmpdir(),
|
|
2465
|
+
async function prepareTryQuickstart(label) {
|
|
2466
|
+
const destDir = mkdtempSync(join(tmpdir(), `${label}-`));
|
|
2469
2467
|
cpSync(join(packageRoot(), "templates", "quickstart"), destDir, { recursive: true });
|
|
2470
|
-
// Never copy dev node_modules / test output into the temp demo (Playwright double-load).
|
|
2471
2468
|
rmSync(join(destDir, "node_modules"), { recursive: true, force: true });
|
|
2472
2469
|
rmSync(join(destDir, "test-results"), { recursive: true, force: true });
|
|
2473
2470
|
rmSync(join(destDir, "package-lock.json"), { force: true });
|
|
@@ -2477,14 +2474,26 @@ async function runTryDemo() {
|
|
|
2477
2474
|
catch (err) {
|
|
2478
2475
|
console.error(err instanceof Error ? err.message : String(err));
|
|
2479
2476
|
process.exitCode = 1;
|
|
2480
|
-
return;
|
|
2477
|
+
return null;
|
|
2481
2478
|
}
|
|
2482
|
-
const problem = await ensureDirPrereqs(destDir,
|
|
2479
|
+
const problem = await ensureDirPrereqs(destDir, label);
|
|
2483
2480
|
if (problem) {
|
|
2484
|
-
console.error(
|
|
2481
|
+
console.error(`${label}: couldn't get ready.\n${problem}`);
|
|
2485
2482
|
process.exitCode = 1;
|
|
2486
|
-
return;
|
|
2483
|
+
return null;
|
|
2487
2484
|
}
|
|
2485
|
+
return destDir;
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* `waygraph try demo` - copy a self-contained saucedemo project into a temp
|
|
2489
|
+
* dir (never the caller's cwd), run a chainFlow step demo (Sign In + Shop +
|
|
2490
|
+
* blocked Viewer), then run the headless Playwright test, and print where
|
|
2491
|
+
* everything lives.
|
|
2492
|
+
*/
|
|
2493
|
+
async function runTryDemo() {
|
|
2494
|
+
const destDir = await prepareTryQuickstart("waygraph-try-demo");
|
|
2495
|
+
if (!destDir)
|
|
2496
|
+
return;
|
|
2488
2497
|
process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
|
|
2489
2498
|
// Default: headed stepper, manual Next. --video records that session (not headless rush).
|
|
2490
2499
|
process.env.WAYGRAPH_HEADED ??= "1";
|
|
@@ -2529,6 +2538,27 @@ async function runTryDemo() {
|
|
|
2529
2538
|
"Permanent full example in this package: examples/saucedemo\n" +
|
|
2530
2539
|
"Keep a permanent scaffold in your tree: waygraph init my-app\n");
|
|
2531
2540
|
}
|
|
2541
|
+
/**
|
|
2542
|
+
* `waygraph try auto` - same temp Sauce Demo as try demo, but opens the
|
|
2543
|
+
* headed `waygraph auto` explorer (Effect Add/Remove, MemNav Open details).
|
|
2544
|
+
*/
|
|
2545
|
+
async function runTryAuto() {
|
|
2546
|
+
const destDir = await prepareTryQuickstart("waygraph-try-auto");
|
|
2547
|
+
if (!destDir)
|
|
2548
|
+
return;
|
|
2549
|
+
process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
|
|
2550
|
+
console.log("waygraph try auto: headed explore on Sauce Demo (temp dir).\n" +
|
|
2551
|
+
" Pick nav-login, then submit-login (creds are pre-seeded).\n" +
|
|
2552
|
+
" On inventory: Add/Remove Effect rows + Open details MemNav.\n" +
|
|
2553
|
+
" Quit from the panel when done.\n");
|
|
2554
|
+
await runAutoExplore(destDir, { baseURL: process.env.WAYGRAPH_BASE_URL });
|
|
2555
|
+
console.log("\nwaygraph try auto: explorer closed.\n\n" +
|
|
2556
|
+
`Temp project:\n ${destDir}\n\n` +
|
|
2557
|
+
"Run again:\n" +
|
|
2558
|
+
` cd ${destDir} && npm run auto\n\n` +
|
|
2559
|
+
"In-package: examples/saucedemo\n" +
|
|
2560
|
+
"Docs: https://deviate-dv8.github.io/waygraph/saucedemo/\n");
|
|
2561
|
+
}
|
|
2532
2562
|
async function validateFlows(projectDir) {
|
|
2533
2563
|
const files = discoverFlows(projectDir);
|
|
2534
2564
|
const results = [];
|
|
@@ -2858,7 +2888,7 @@ Usage (watch -> run -> inspect -> scaffold):
|
|
|
2858
2888
|
--base-url, else WAYGRAPH_BASE_URL, else
|
|
2859
2889
|
package.json waygraph.baseUrl, else
|
|
2860
2890
|
playwright.config baseURL.
|
|
2861
|
-
waygraph try [demo]
|
|
2891
|
+
waygraph try [demo|auto] One-shot saucedemo (temp dir): demo=stepper+test, auto=explore
|
|
2862
2892
|
waygraph chain <spec> [project] Ad-hoc Blocks by name ("a then b"); add
|
|
2863
2893
|
--step / --autoplay for the same overlay
|
|
2864
2894
|
waygraph run <flow> [project] Run a named flow (no overlay unless --step)
|
|
@@ -2885,6 +2915,7 @@ Demo / step / autoplay (read this - agents get this wrong):
|
|
|
2885
2915
|
Same overlay on a chain: waygraph chain "login then nav-cart" . --step
|
|
2886
2916
|
Unattended (no overlay): waygraph demo shopFlow . --no-step
|
|
2887
2917
|
Onboarding (temp dir): npx waygraph try demo
|
|
2918
|
+
Explore (temp dir): npx waygraph try auto
|
|
2888
2919
|
|
|
2889
2920
|
Other run flags (chain / demo / try) - flags beat WAYGRAPH_* env:
|
|
2890
2921
|
--base-url <url> base URL for relative page.goto()
|
|
@@ -3157,7 +3188,17 @@ async function main() {
|
|
|
3157
3188
|
case "try": {
|
|
3158
3189
|
const flags = parseRunFlags(args.slice(1));
|
|
3159
3190
|
applyRunFlags(flags);
|
|
3160
|
-
|
|
3191
|
+
const mode = (flags.positionals[0] ?? "demo").toLowerCase();
|
|
3192
|
+
if (mode === "auto") {
|
|
3193
|
+
await runTryAuto();
|
|
3194
|
+
}
|
|
3195
|
+
else if (mode === "demo" || mode === "") {
|
|
3196
|
+
await runTryDemo();
|
|
3197
|
+
}
|
|
3198
|
+
else {
|
|
3199
|
+
console.error(`waygraph try: unknown mode "${mode}" - use "demo" or "auto"`);
|
|
3200
|
+
process.exitCode = 1;
|
|
3201
|
+
}
|
|
3161
3202
|
break;
|
|
3162
3203
|
}
|
|
3163
3204
|
case "check": {
|
package/dist/step-overlay.js
CHANGED
|
@@ -140,47 +140,56 @@ export function instrumentInteractionHighlighting(page, mem, slowMo) {
|
|
|
140
140
|
// Keep unbound — originalFill.call(this, …) must receive the Locator as `this`, not the prototype.
|
|
141
141
|
const originalFill = proto.fill;
|
|
142
142
|
proto.fill = async function (value, options) {
|
|
143
|
+
const livePage = (typeof this.page === "function" ? this.page() : page);
|
|
143
144
|
try {
|
|
144
|
-
await installDemoChrome(
|
|
145
|
+
await installDemoChrome(livePage);
|
|
145
146
|
const box = await this.boundingBox();
|
|
146
147
|
if (box) {
|
|
147
148
|
const label = memTrack.lastKeyName && Date.now() - memTrack.at < 3000
|
|
148
149
|
? "from mem: " + memTrack.lastKeyName
|
|
149
150
|
: "writing";
|
|
150
|
-
await moveCursorTo(
|
|
151
|
-
await showRing(
|
|
151
|
+
await moveCursorTo(livePage, box, cursorMs(500));
|
|
152
|
+
await showRing(livePage, box, label);
|
|
152
153
|
await new Promise((r) => setTimeout(r, 200));
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
catch {
|
|
156
157
|
/* best-effort */
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (options?.timeout !== undefined)
|
|
162
|
-
seqOpts.timeout = options.timeout;
|
|
163
|
-
await this.pressSequentially(String(value), seqOpts);
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
159
|
+
// Prefer a single fill - clear+pressSequentially can hang when the auto
|
|
160
|
+
// panel steals focus mid-type (looks stuck on "Running: submit-login").
|
|
161
|
+
if (slowMo || typeDelay() === 0) {
|
|
166
162
|
await originalFill.call(this, value, options);
|
|
167
163
|
}
|
|
168
|
-
|
|
164
|
+
else {
|
|
165
|
+
try {
|
|
166
|
+
await originalFill.call(this, "", options);
|
|
167
|
+
const seqOpts = { delay: typeDelay() };
|
|
168
|
+
if (options?.timeout !== undefined)
|
|
169
|
+
seqOpts.timeout = options.timeout;
|
|
170
|
+
await this.pressSequentially(String(value), seqOpts);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
await originalFill.call(this, value, options);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await hideRing(livePage);
|
|
169
177
|
};
|
|
170
178
|
}
|
|
171
179
|
if (!proto.__wgClickPatched) {
|
|
172
180
|
proto.__wgClickPatched = true;
|
|
173
181
|
const originalClick = proto.click;
|
|
174
182
|
proto.click = async function (options) {
|
|
183
|
+
const livePage = (typeof this.page === "function" ? this.page() : page);
|
|
175
184
|
let clickPoint = null;
|
|
176
185
|
try {
|
|
177
|
-
await installDemoChrome(
|
|
178
|
-
await this.waitFor({ state: "visible", timeout: options?.timeout ??
|
|
186
|
+
await installDemoChrome(livePage);
|
|
187
|
+
await this.waitFor({ state: "visible", timeout: options?.timeout ?? 15000 }).catch(() => { });
|
|
179
188
|
const box = await this.boundingBox();
|
|
180
189
|
if (box) {
|
|
181
190
|
let label = "click";
|
|
182
191
|
try {
|
|
183
|
-
const navLabel = await
|
|
192
|
+
const navLabel = await livePage.evaluate(() => window.__wgPendingNavClickLabel ?? null);
|
|
184
193
|
if (navLabel)
|
|
185
194
|
label = String(navLabel);
|
|
186
195
|
else {
|
|
@@ -192,8 +201,8 @@ export function instrumentInteractionHighlighting(page, mem, slowMo) {
|
|
|
192
201
|
catch {
|
|
193
202
|
/* ignore */
|
|
194
203
|
}
|
|
195
|
-
clickPoint = await moveCursorTo(
|
|
196
|
-
await showRing(
|
|
204
|
+
clickPoint = await moveCursorTo(livePage, box, cursorMs(600));
|
|
205
|
+
await showRing(livePage, box, label);
|
|
197
206
|
await new Promise((r) => setTimeout(r, clickPrePop()));
|
|
198
207
|
}
|
|
199
208
|
}
|
|
@@ -201,12 +210,12 @@ export function instrumentInteractionHighlighting(page, mem, slowMo) {
|
|
|
201
210
|
/* best-effort */
|
|
202
211
|
}
|
|
203
212
|
if (clickPoint) {
|
|
204
|
-
await clickPulseAt(
|
|
205
|
-
await new Promise((r) => setTimeout(r,
|
|
213
|
+
await clickPulseAt(livePage, clickPoint.x, clickPoint.y);
|
|
214
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
206
215
|
}
|
|
207
216
|
const result = await originalClick.call(this, options);
|
|
208
217
|
await new Promise((r) => setTimeout(r, clickPostPop()));
|
|
209
|
-
await hideRing(
|
|
218
|
+
await hideRing(livePage).catch(() => { });
|
|
210
219
|
return result;
|
|
211
220
|
};
|
|
212
221
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
# Sauce Demo
|
|
1
|
+
# Sauce Demo - convention showcase
|
|
2
2
|
|
|
3
|
-
Full live consumer for [saucedemo.com](https://www.saucedemo.com)
|
|
4
|
-
|
|
5
|
-
`waygraph auto`, and Playwright flows. Helpers are TypeScript salt: runtime is
|
|
6
|
-
still just Blocks.
|
|
3
|
+
Full live consumer for [saucedemo.com](https://www.saucedemo.com). This tree is
|
|
4
|
+
the **reference** for waygraph consumer conventions:
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
- Route folders under `src/blocks/saucedemo-web/` = real Swag Labs URLs
|
|
7
|
+
- **Nav** / **Effect** / **Action** file kinds (helpers are TypeScript salt;
|
|
8
|
+
runtime is still `Block`)
|
|
9
|
+
- `instanceOptions` menus for `waygraph auto` (Add/Remove + Open details)
|
|
10
|
+
- Detailed Kind / Helper / Route comments on every Block file
|
|
11
|
+
- Per-route `NAV.md` + root `src/blocks/SITE-MAP.md`
|
|
12
|
+
|
|
13
|
+
**GitHub Pages walkthrough:**
|
|
14
|
+
[docs/saucedemo](https://deviate-dv8.github.io/waygraph/saucedemo/)
|
|
15
|
+
(`docs/saucedemo/` in this repo - README, SITE-MAP, kinds, layout, auto, comments).
|
|
16
|
+
|
|
17
|
+
`waygraph try demo` copies the sibling template at `templates/quickstart`
|
|
18
|
+
(same Blocks) into an OS temp dir.
|
|
10
19
|
|
|
11
20
|
## Setup
|
|
12
21
|
|
|
@@ -28,4 +37,22 @@ npx playwright install chromium
|
|
|
28
37
|
| Stepper (Auto-advance) | `npm run demo:autoplay` |
|
|
29
38
|
| List flows | `npm run flows` |
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
## Layout (short)
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
src/blocks/
|
|
44
|
+
SITE-MAP.md
|
|
45
|
+
saucedemo-web/ # URL /
|
|
46
|
+
NAV.md
|
|
47
|
+
nav-login.block.ts
|
|
48
|
+
actions/ # submit-login, logout, …
|
|
49
|
+
inventory/ # URL /inventory.html
|
|
50
|
+
nav-item-detail.block.ts # MemNav
|
|
51
|
+
actions/*.effect.block.ts # Add / Remove
|
|
52
|
+
cart/ # URL /cart.html
|
|
53
|
+
checkout-step-one/
|
|
54
|
+
checkout-step-two/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Docs: [Sauce Demo showcase](../../docs/saucedemo/) ·
|
|
58
|
+
[Auto explore](../../docs/auto.html) · [Consumer layout](../../docs/consumer.html)
|
|
@@ -27,20 +27,38 @@ app changes URL as a side effect of `#continue`.
|
|
|
27
27
|
|
|
28
28
|
```text
|
|
29
29
|
src/blocks/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
SITE-MAP.md
|
|
31
|
+
saucedemo-web/ # THIS FOLDER IS URL "/"
|
|
32
|
+
NAV.md
|
|
33
|
+
nav-login.block.ts
|
|
34
|
+
actions/
|
|
35
|
+
submit-login.action.block.ts
|
|
36
|
+
submit-login-for-flow.ts
|
|
37
|
+
submit-logout.action.block.ts
|
|
38
|
+
inventory/ # /inventory.html
|
|
39
|
+
NAV.md
|
|
40
|
+
nav-item-detail.block.ts
|
|
41
|
+
actions/
|
|
42
|
+
inventory-items.ts
|
|
43
|
+
add-to-cart.effect.block.ts
|
|
44
|
+
remove-from-cart.effect.block.ts
|
|
45
|
+
inventory-item/ # /inventory-item.html
|
|
46
|
+
NAV.md
|
|
47
|
+
nav-continue-shopping.block.ts
|
|
48
|
+
nav-back-to-products.block.ts
|
|
49
|
+
nav-back-to-inventory.block.ts
|
|
50
|
+
cart/ # /cart.html
|
|
51
|
+
NAV.md
|
|
52
|
+
nav-cart.block.ts
|
|
53
|
+
nav-checkout-info.block.ts
|
|
54
|
+
checkout-step-one/ # /checkout-step-one.html
|
|
55
|
+
NAV.md
|
|
56
|
+
actions/
|
|
57
|
+
submit-checkout-info.action.block.ts
|
|
58
|
+
checkout-step-two/ # /checkout-step-two.html
|
|
59
|
+
NAV.md
|
|
60
|
+
actions/
|
|
61
|
+
finish-order.action.block.ts
|
|
44
62
|
src/states/
|
|
45
63
|
src/flows/
|
|
46
64
|
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "waygraph-example-saucedemo",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "waygraph-example-saucedemo",
|
|
9
|
+
"version": "0.0.1",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"waygraph": "file:../.."
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@playwright/test": "^1.55.0",
|
|
15
|
+
"@types/node": "^26.0.0",
|
|
16
|
+
"typescript": "^5.6.0"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"../..": {
|
|
20
|
+
"version": "0.7.11",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"tsx": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"waygraph": "bin/waygraph"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@playwright/test": "^1.55.0",
|
|
30
|
+
"@types/node": "^26.0.0",
|
|
31
|
+
"typescript": "^5.6.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@playwright/test": "^1.40.0"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"node_modules/@playwright/test": {
|
|
38
|
+
"version": "1.63.0",
|
|
39
|
+
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
|
|
40
|
+
"integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
|
|
41
|
+
"dev": true,
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"playwright": "1.63.0"
|
|
45
|
+
},
|
|
46
|
+
"bin": {
|
|
47
|
+
"playwright": "cli.js"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"node_modules/@types/node": {
|
|
54
|
+
"version": "26.6.1",
|
|
55
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.1.tgz",
|
|
56
|
+
"integrity": "sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==",
|
|
57
|
+
"dev": true,
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"undici-types": "~8.9.0"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"node_modules/playwright": {
|
|
64
|
+
"version": "1.63.0",
|
|
65
|
+
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
|
|
66
|
+
"integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
|
|
67
|
+
"dev": true,
|
|
68
|
+
"license": "Apache-2.0",
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"playwright-core": "1.63.0"
|
|
71
|
+
},
|
|
72
|
+
"bin": {
|
|
73
|
+
"playwright": "cli.js"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=20"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"node_modules/playwright-core": {
|
|
80
|
+
"version": "1.63.0",
|
|
81
|
+
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
|
|
82
|
+
"integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
|
|
83
|
+
"dev": true,
|
|
84
|
+
"license": "Apache-2.0",
|
|
85
|
+
"bin": {
|
|
86
|
+
"playwright-core": "cli.js"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=20"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"node_modules/typescript": {
|
|
93
|
+
"version": "5.9.3",
|
|
94
|
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
95
|
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
96
|
+
"dev": true,
|
|
97
|
+
"license": "Apache-2.0",
|
|
98
|
+
"bin": {
|
|
99
|
+
"tsc": "bin/tsc",
|
|
100
|
+
"tsserver": "bin/tsserver"
|
|
101
|
+
},
|
|
102
|
+
"engines": {
|
|
103
|
+
"node": ">=14.17"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"node_modules/undici-types": {
|
|
107
|
+
"version": "8.9.0",
|
|
108
|
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
|
109
|
+
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
|
110
|
+
"dev": true,
|
|
111
|
+
"license": "MIT"
|
|
112
|
+
},
|
|
113
|
+
"node_modules/waygraph": {
|
|
114
|
+
"resolved": "../..",
|
|
115
|
+
"link": true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -86,9 +86,9 @@ async function snap(page, name) {
|
|
|
86
86
|
const { graph, library } = await buildExploreContext(root);
|
|
87
87
|
const nodeNames = graph.nodes.map((n) => n.checkpoint);
|
|
88
88
|
|
|
89
|
-
const { NavLoginBlock } = await import(pathToFileURL(join(root, "src/blocks/nav-login.block.js")).href);
|
|
89
|
+
const { NavLoginBlock } = await import(pathToFileURL(join(root, "src/blocks/saucedemo-web/nav-login.block.js")).href);
|
|
90
90
|
const { SubmitLoginActionBlock } = await import(
|
|
91
|
-
pathToFileURL(join(root, "src/blocks/actions/submit-login.action.block.js")).href
|
|
91
|
+
pathToFileURL(join(root, "src/blocks/saucedemo-web/actions/submit-login.action.block.js")).href
|
|
92
92
|
);
|
|
93
93
|
const { LoginCreds } = await import(pathToFileURL(join(root, "src/states/checkout.mem-keys.js")).href);
|
|
94
94
|
|
|
@@ -13,9 +13,9 @@ await import(require.resolve("tsx/esm"));
|
|
|
13
13
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
14
|
const waygraphDist = join(root, "../../dist");
|
|
15
15
|
const { Engine, start, end, MemPage } = await import(pathToFileURL(join(waygraphDist, "index.js")).href);
|
|
16
|
-
const { NavLoginBlock } = await import(pathToFileURL(join(root, "src/blocks/nav-login.block.js")).href);
|
|
16
|
+
const { NavLoginBlock } = await import(pathToFileURL(join(root, "src/blocks/saucedemo-web/nav-login.block.js")).href);
|
|
17
17
|
const { SubmitLoginActionBlock } = await import(
|
|
18
|
-
pathToFileURL(join(root, "src/blocks/actions/submit-login.action.block.js")).href
|
|
18
|
+
pathToFileURL(join(root, "src/blocks/saucedemo-web/actions/submit-login.action.block.js")).href
|
|
19
19
|
);
|
|
20
20
|
const { LoginCreds } = await import(pathToFileURL(join(root, "src/states/checkout.mem-keys.js")).href);
|
|
21
21
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SITE-MAP - Sauce Demo blocks
|
|
2
|
+
|
|
3
|
+
Namespace `saucedemo-web/` is URL `/` for [saucedemo.com](https://www.saucedemo.com).
|
|
4
|
+
Route folders mirror real Swag Labs paths (consumer convention).
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
src/blocks/
|
|
8
|
+
SITE-MAP.md
|
|
9
|
+
saucedemo-web/ # URL /
|
|
10
|
+
NAV.md
|
|
11
|
+
nav-login.block.ts # Nav defineNavBlock url:/
|
|
12
|
+
actions/
|
|
13
|
+
submit-login.action.block.ts # Action defineBlock
|
|
14
|
+
submit-login-for-flow.ts # helper (not a discoverable *.block.ts alone)
|
|
15
|
+
submit-logout.action.block.ts # Action
|
|
16
|
+
inventory/ # URL /inventory.html
|
|
17
|
+
NAV.md
|
|
18
|
+
nav-item-detail.block.ts # Nav defineMemNavBlock (instance row)
|
|
19
|
+
actions/
|
|
20
|
+
inventory-items.ts # DOM helpers for Effects (not a Block)
|
|
21
|
+
add-to-cart.effect.block.ts # Effect defineEffectBlock
|
|
22
|
+
remove-from-cart.effect.block.ts
|
|
23
|
+
inventory-item/ # URL /inventory-item.html
|
|
24
|
+
NAV.md
|
|
25
|
+
nav-continue-shopping.block.ts
|
|
26
|
+
nav-back-to-products.block.ts
|
|
27
|
+
nav-back-to-inventory.block.ts
|
|
28
|
+
cart/ # URL /cart.html
|
|
29
|
+
NAV.md
|
|
30
|
+
nav-cart.block.ts # Nav click header cart
|
|
31
|
+
nav-checkout-info.block.ts
|
|
32
|
+
checkout-step-one/ # URL /checkout-step-one.html
|
|
33
|
+
NAV.md
|
|
34
|
+
actions/
|
|
35
|
+
submit-checkout-info.action.block.ts
|
|
36
|
+
checkout-step-two/ # URL /checkout-step-two.html
|
|
37
|
+
NAV.md
|
|
38
|
+
actions/
|
|
39
|
+
finish-order.action.block.ts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Checkpoints and mem keys live under `src/states/`. Flows under `src/flows/`
|
|
43
|
+
compose Blocks into episodes for `waygraph demo` / Playwright.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# saucedemo-web/ - URL `/`
|
|
2
|
+
|
|
3
|
+
Login page for Swag Labs. This folder is the namespace root (`/` in the browser).
|
|
4
|
+
|
|
5
|
+
## URL
|
|
6
|
+
|
|
7
|
+
- `/` (login form)
|
|
8
|
+
|
|
9
|
+
## Nav blocks
|
|
10
|
+
|
|
11
|
+
| File | Helper | Notes |
|
|
12
|
+
|------|--------|-------|
|
|
13
|
+
| `nav-login.block.ts` | `defineNavBlock` | `url: "/"` - only goto onto login |
|
|
14
|
+
|
|
15
|
+
## Effects / actions
|
|
16
|
+
|
|
17
|
+
| File | Kind | Notes |
|
|
18
|
+
|------|------|-------|
|
|
19
|
+
| `actions/submit-login.action.block.ts` | Action | Form submit; branches LoggedIn vs LoginPage |
|
|
20
|
+
| `actions/submit-login-for-flow.ts` | helper | Typed alias for linear Flows |
|
|
21
|
+
| `actions/submit-logout.action.block.ts` | Action | Burger menu logout -> LoginPage |
|
|
22
|
+
|
|
23
|
+
## Related mem keys / checkpoints
|
|
24
|
+
|
|
25
|
+
- Mem: `saucedemo.credentials` (`LoginCreds`)
|
|
26
|
+
- Checkpoints: `LoginPage`, `LoggedIn` (success path of submit-login)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DefinedBlock } from "waygraph";
|
|
2
|
+
import type { LoginPage, LoggedIn } from "../../../states/checkout.states.js";
|
|
3
|
+
import { SubmitLoginActionBlock } from "./submit-login.action.block.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kind: helper (not a discoverable Block file)
|
|
7
|
+
* Helper: typed alias over SubmitLoginActionBlock
|
|
8
|
+
* Route: saucedemo-web/actions/ (page URL /)
|
|
9
|
+
*
|
|
10
|
+
* DefinedBlock cast so linear Flows can chain .withVerify on Out=LoggedIn;
|
|
11
|
+
* runtime still branches on bad credentials via SubmitLoginActionBlock.
|
|
12
|
+
*/
|
|
13
|
+
export const SubmitLoginForFlow = SubmitLoginActionBlock as unknown as DefinedBlock<LoginPage, LoggedIn>;
|
package/examples/saucedemo/src/blocks/{actions → saucedemo-web/actions}/submit-login.action.block.ts
RENAMED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { defineBlock, checkpoint, Trait } from "waygraph";
|
|
2
|
-
import type { LoginPage, LoginSubmitOutcome } from "
|
|
3
|
-
import { LoginCreds } from "
|
|
2
|
+
import type { LoginPage, LoginSubmitOutcome } from "../../../states/checkout.states.js";
|
|
3
|
+
import { LoginCreds } from "../../../states/checkout.mem-keys.js";
|
|
4
4
|
|
|
5
5
|
type LoginObserve = "success" | "failure";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Kind: Action
|
|
9
|
+
* Helper: defineBlock
|
|
10
|
+
* Route: saucedemo-web/actions/ (page URL /)
|
|
11
|
+
*
|
|
12
|
+
* Fills credentials and submits - never page.goto (nav-login owns that).
|
|
13
|
+
* Branches: good creds -> LoggedIn; bad/locked -> LoginPage with error banner.
|
|
14
|
+
*/
|
|
13
15
|
export const SubmitLoginActionBlock = defineBlock<LoginPage, LoginSubmitOutcome>({
|
|
14
16
|
name: "submit-login",
|
|
15
17
|
description: "Logs a user in when credentials are valid; stays on LoginPage when auth fails.",
|
|
@@ -19,11 +21,13 @@ export const SubmitLoginActionBlock = defineBlock<LoginPage, LoginSubmitOutcome>
|
|
|
19
21
|
const { username, password } = mem.get(LoginCreds.key);
|
|
20
22
|
await page.locator("#user-name").fill(username);
|
|
21
23
|
await page.locator("#password").fill(password);
|
|
22
|
-
|
|
24
|
+
// noWaitAfter: observe owns the inventory URL wait. Default click
|
|
25
|
+
// navigation-wait can hang under waygraph auto's demo cursor patch.
|
|
26
|
+
await page.locator("#login-button").click({ noWaitAfter: true });
|
|
23
27
|
},
|
|
24
28
|
async observe(page): Promise<LoginObserve> {
|
|
25
29
|
try {
|
|
26
|
-
await page.waitForURL(/\/inventory\.html/, { timeout:
|
|
30
|
+
await page.waitForURL(/\/inventory\.html/, { timeout: 8_000 });
|
|
27
31
|
return "success";
|
|
28
32
|
} catch {
|
|
29
33
|
return "failure";
|