waygraph 0.12.26 → 0.12.28
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 +311 -154
- package/dist/highlights.d.ts +30 -6
- package/dist/highlights.js +26 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +36 -9
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +36 -6
package/dist/cli.js
CHANGED
|
@@ -412,7 +412,14 @@ function seedMemForFlow(mem, flowBlocks, json, label) {
|
|
|
412
412
|
const RING_CSS =
|
|
413
413
|
"#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
414
414
|
"border:2.5px solid #7C3AED;border-radius:10px;" +
|
|
415
|
-
"box-shadow:0 0 0 4px rgba(124,58,237,.16);
|
|
415
|
+
"box-shadow:0 0 0 4px rgba(124,58,237,.16);" +
|
|
416
|
+
"transition:opacity .32s ease,left .28s ease,top .28s ease,width .28s ease,height .28s ease,border-color .15s,box-shadow .32s ease;}" +
|
|
417
|
+
"#wg-ring[data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(124,58,237,.2),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
418
|
+
"#wg-ring[data-tone=auto][data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(156,163,175,.28),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
419
|
+
"#wg-ring[data-tone=info][data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(59,130,246,.22),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
420
|
+
"#wg-ring[data-tone=warning][data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(234,179,8,.22),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
421
|
+
"#wg-ring[data-tone=danger][data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(239,68,68,.22),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
422
|
+
"#wg-ring[data-tone=success][data-focus=\"1\"]{box-shadow:0 0 0 4px rgba(34,197,94,.22),0 0 0 9999px rgba(15,6,32,.55);}" +
|
|
416
423
|
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
417
424
|
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
418
425
|
// can ignore engine checks and watch purple + semantic tones.
|
|
@@ -431,7 +438,7 @@ const RING_CSS =
|
|
|
431
438
|
// window.__wgPositionRing in installOverlay.
|
|
432
439
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
433
440
|
"max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
434
|
-
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .
|
|
441
|
+
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .32s ease,left .28s ease,top .28s ease,background .15s,color .15s;}" +
|
|
435
442
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
436
443
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
437
444
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -685,7 +692,8 @@ async function installOverlay(page, title) {
|
|
|
685
692
|
window.__wgMoveCursorTo = (x, y, ms, instant) => {
|
|
686
693
|
const cursor = document.getElementById("wg-cursor");
|
|
687
694
|
if (!cursor) return;
|
|
688
|
-
|
|
695
|
+
const travel = ms || 600;
|
|
696
|
+
cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
|
|
689
697
|
if (instant) {
|
|
690
698
|
const prev = cursor.style.transition;
|
|
691
699
|
cursor.style.transition = "none";
|
|
@@ -696,6 +704,11 @@ async function installOverlay(page, title) {
|
|
|
696
704
|
cursor.style.transform = "translate(" + x + "px," + y + "px)";
|
|
697
705
|
}
|
|
698
706
|
cursor.style.opacity = "1";
|
|
707
|
+
// Screen Studio: pan the zoomed camera with the cursor.
|
|
708
|
+
const cam = window.__wgCamera;
|
|
709
|
+
if (cam && cam.scale > 1.001 && window.__wgSetCamera) {
|
|
710
|
+
window.__wgSetCamera(x, y, cam.scale, instant ? 0 : travel);
|
|
711
|
+
}
|
|
699
712
|
};
|
|
700
713
|
window.__wgHideCursor = () => {
|
|
701
714
|
const cursor = document.getElementById("wg-cursor");
|
|
@@ -721,6 +734,7 @@ async function installOverlay(page, title) {
|
|
|
721
734
|
ring.dataset.size = size;
|
|
722
735
|
ringLabel.dataset.size = size;
|
|
723
736
|
ringLabel.dataset.weight = weight;
|
|
737
|
+
ring.dataset.focus = st.focus ? "1" : "0";
|
|
724
738
|
const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
|
|
725
739
|
const margin = 6;
|
|
726
740
|
const vw = window.innerWidth;
|
|
@@ -740,6 +754,9 @@ async function installOverlay(page, title) {
|
|
|
740
754
|
ring.style.opacity = "1";
|
|
741
755
|
ringLabel.textContent = label;
|
|
742
756
|
ringLabel.style.opacity = "1";
|
|
757
|
+
const capColor = st.color || st.labelColor || st.textColor;
|
|
758
|
+
if (capColor) ringLabel.style.color = String(capColor);
|
|
759
|
+
else ringLabel.style.removeProperty("color");
|
|
743
760
|
// Measure the label's own natural width/height (its real
|
|
744
761
|
// rendered size, unchanged) before deciding where it fits.
|
|
745
762
|
const lw = ringLabel.offsetWidth;
|
|
@@ -779,29 +796,44 @@ async function installOverlay(page, title) {
|
|
|
779
796
|
if (ringLabel) ringLabel.style.opacity = "0";
|
|
780
797
|
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
781
798
|
};
|
|
799
|
+
// Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
|
|
800
|
+
window.__wgCamera = { scale: 1, cx: 0, cy: 0 };
|
|
801
|
+
window.__wgSetCamera = (cx, cy, scale, ms) => {
|
|
802
|
+
const body = document.body;
|
|
803
|
+
if (!body) return;
|
|
804
|
+
const s = Math.max(1, Math.min(Number(scale) || 1, 2.75));
|
|
805
|
+
window.__wgCamera = { scale: s, cx, cy };
|
|
806
|
+
const dur = ms != null && Number(ms) >= 0 ? Number(ms) : 500;
|
|
807
|
+
body.style.transition =
|
|
808
|
+
dur <= 0
|
|
809
|
+
? "none"
|
|
810
|
+
: "transform " + dur / 1000 + "s cubic-bezier(.22,1,.36,1)";
|
|
811
|
+
body.style.transformOrigin = "0 0";
|
|
812
|
+
if (s <= 1.001) {
|
|
813
|
+
body.style.transform = "";
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
body.style.transform =
|
|
817
|
+
"translate(" + cx + "px," + cy + "px) scale(" + s + ") translate(" + -cx + "px," + -cy + "px)";
|
|
818
|
+
};
|
|
819
|
+
window.__wgResetCamera = () => {
|
|
820
|
+
window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
|
|
821
|
+
};
|
|
782
822
|
window.__wgClearZoom = () => {
|
|
783
|
-
|
|
784
|
-
el.style.removeProperty("transform");
|
|
785
|
-
el.style.removeProperty("transform-origin");
|
|
786
|
-
el.style.removeProperty("transition");
|
|
787
|
-
el.style.removeProperty("z-index");
|
|
788
|
-
el.style.removeProperty("position");
|
|
789
|
-
delete el.dataset.wgZoomed;
|
|
790
|
-
});
|
|
823
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
791
824
|
};
|
|
792
|
-
window.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
el.dataset.wgZoomed = "1";
|
|
825
|
+
window.__wgSetBannerTitle = (title) => {
|
|
826
|
+
const banner = document.getElementById("wg-banner");
|
|
827
|
+
if (!banner) return;
|
|
828
|
+
let textEl = null;
|
|
829
|
+
for (const sp of banner.querySelectorAll("span")) {
|
|
830
|
+
if (!sp.classList.contains("wg-banner-tag")) { textEl = sp; break; }
|
|
831
|
+
}
|
|
832
|
+
if (!textEl) {
|
|
833
|
+
textEl = document.createElement("span");
|
|
834
|
+
banner.appendChild(textEl);
|
|
835
|
+
}
|
|
836
|
+
textEl.textContent = String(title || "");
|
|
805
837
|
};
|
|
806
838
|
const POSITIONS = ["left", "center", "right"];
|
|
807
839
|
const applyPos = (el, pos) => {
|
|
@@ -1202,153 +1234,233 @@ async function ensureSelectorInView(page, selector) {
|
|
|
1202
1234
|
}
|
|
1203
1235
|
}
|
|
1204
1236
|
|
|
1205
|
-
async function
|
|
1206
|
-
const z = Number(zoom);
|
|
1207
|
-
if (!
|
|
1237
|
+
async function applyCameraZoom(page, box, zoom) {
|
|
1238
|
+
const z = zoom != null && Number(zoom) > 1 ? Number(zoom) : 1;
|
|
1239
|
+
if (!box) {
|
|
1208
1240
|
await page.evaluate(() => {
|
|
1209
|
-
if (window.
|
|
1241
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
1210
1242
|
}).catch(() => {});
|
|
1211
1243
|
return;
|
|
1212
1244
|
}
|
|
1245
|
+
const cx = box.x + box.width / 2;
|
|
1246
|
+
const cy = box.y + box.height / 2;
|
|
1213
1247
|
await page
|
|
1214
|
-
.evaluate(
|
|
1215
|
-
(
|
|
1216
|
-
|
|
1217
|
-
},
|
|
1218
|
-
{ sel: selector, scale: z },
|
|
1219
|
-
)
|
|
1248
|
+
.evaluate(({ cx, cy, scale }) => {
|
|
1249
|
+
if (window.__wgSetCamera) window.__wgSetCamera(cx, cy, scale);
|
|
1250
|
+
}, { cx, cy, scale: z })
|
|
1220
1251
|
.catch(() => {});
|
|
1221
1252
|
}
|
|
1222
1253
|
|
|
1223
|
-
function
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
.map((t) => {
|
|
1230
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1231
|
-
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1232
|
-
return (
|
|
1233
|
-
"<li class=\\"" +
|
|
1234
|
-
cls +
|
|
1235
|
-
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1236
|
-
mark +
|
|
1237
|
-
"</span><span>" +
|
|
1238
|
-
escFn(t.text || "") +
|
|
1239
|
-
"</span></li>"
|
|
1240
|
-
);
|
|
1241
|
-
})
|
|
1242
|
-
.join("") +
|
|
1243
|
-
"</ul>"
|
|
1244
|
-
);
|
|
1254
|
+
async function resetCameraZoom(page) {
|
|
1255
|
+
await page
|
|
1256
|
+
.evaluate(() => {
|
|
1257
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
1258
|
+
})
|
|
1259
|
+
.catch(() => {});
|
|
1245
1260
|
}
|
|
1246
1261
|
|
|
1247
|
-
function
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
(info.index + 1) +
|
|
1255
|
-
" / " +
|
|
1256
|
-
info.total +
|
|
1257
|
-
(info.blockName ? " \\u00b7 " + info.blockName : "") +
|
|
1258
|
-
(info.paceBadge ? " \\u00b7 " + info.paceBadge : "")
|
|
1259
|
-
);
|
|
1262
|
+
async function setDemoBannerTitle(page, title) {
|
|
1263
|
+
if (title == null || title === "") return;
|
|
1264
|
+
await page
|
|
1265
|
+
.evaluate((t) => {
|
|
1266
|
+
if (window.__wgSetBannerTitle) window.__wgSetBannerTitle(t);
|
|
1267
|
+
}, String(title))
|
|
1268
|
+
.catch(() => {});
|
|
1260
1269
|
}
|
|
1261
1270
|
|
|
1271
|
+
|
|
1262
1272
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1263
1273
|
const list = highlights || [];
|
|
1264
|
-
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
1265
|
-
// rings stay visible ~2s before the error panel (PIA stubOnError RFC).
|
|
1266
1274
|
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1267
1275
|
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1276
|
+
const fadeMs = 320;
|
|
1277
|
+
const n = list.length;
|
|
1278
|
+
const baseStepLabel = (opts && opts.stepLabel) || "";
|
|
1279
|
+
|
|
1280
|
+
const updateHlProgress = async (i) => {
|
|
1281
|
+
if (n <= 0) return;
|
|
1282
|
+
const tag = "[" + (i + 1) + "/" + n + "]";
|
|
1283
|
+
await page
|
|
1284
|
+
.evaluate(
|
|
1285
|
+
({ tag, baseStepLabel, i, n }) => {
|
|
1286
|
+
const panel = document.getElementById("wg-panel");
|
|
1287
|
+
if (!panel) return;
|
|
1288
|
+
panel.dataset.wgHlIdx = String(i + 1);
|
|
1289
|
+
panel.dataset.wgHlN = String(n);
|
|
1290
|
+
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1291
|
+
if (titleEl) {
|
|
1292
|
+
const base = panel.dataset.wgStepLabel || baseStepLabel || "waygraph demo";
|
|
1293
|
+
if (panel.classList.contains("wg-collapsed")) {
|
|
1294
|
+
titleEl.textContent = base + " " + tag;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
let span = document.getElementById("wg-hl-idx");
|
|
1298
|
+
const h3 = panel.querySelector("h3");
|
|
1299
|
+
if (h3) {
|
|
1300
|
+
if (!span) {
|
|
1301
|
+
span = document.createElement("span");
|
|
1302
|
+
span.id = "wg-hl-idx";
|
|
1303
|
+
span.style.marginLeft = "8px";
|
|
1304
|
+
span.style.color = "#c9a6ff";
|
|
1305
|
+
span.style.fontWeight = "700";
|
|
1306
|
+
h3.appendChild(span);
|
|
1307
|
+
}
|
|
1308
|
+
span.textContent = tag;
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
{ tag, baseStepLabel, i, n },
|
|
1312
|
+
)
|
|
1313
|
+
.catch(() => {});
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
const stopLiveFollow = async () => {
|
|
1317
|
+
await page
|
|
1318
|
+
.evaluate(() => {
|
|
1319
|
+
if (window.__wgRingRaf) {
|
|
1320
|
+
cancelAnimationFrame(window.__wgRingRaf);
|
|
1321
|
+
window.__wgRingRaf = null;
|
|
1322
|
+
}
|
|
1323
|
+
if (window.__wgRingTrack) {
|
|
1324
|
+
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1325
|
+
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1326
|
+
window.__wgRingTrack = null;
|
|
1327
|
+
}
|
|
1328
|
+
})
|
|
1329
|
+
.catch(() => {});
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1268
1332
|
for (let i = 0; i < list.length; i++) {
|
|
1269
1333
|
const h = list[i];
|
|
1270
1334
|
try {
|
|
1335
|
+
await stopLiveFollow();
|
|
1271
1336
|
await ensureSelectorInView(page, h.selector);
|
|
1272
|
-
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1273
1337
|
const box = await page.locator(h.selector).first().boundingBox();
|
|
1274
|
-
if (box)
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1338
|
+
if (!box) continue;
|
|
1339
|
+
await updateHlProgress(i);
|
|
1340
|
+
await applyCameraZoom(page, box, h.zoom);
|
|
1341
|
+
const useCursor = h.cursor !== false;
|
|
1342
|
+
if (useCursor) {
|
|
1343
|
+
const cx = box.x + box.width / 2;
|
|
1344
|
+
const cy = box.y + box.height / 2;
|
|
1345
|
+
await page
|
|
1346
|
+
.evaluate(({ cx, cy }) => {
|
|
1347
|
+
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(cx, cy, 500, false);
|
|
1348
|
+
}, { cx, cy })
|
|
1349
|
+
.catch(() => {});
|
|
1350
|
+
}
|
|
1351
|
+
await showRing(page, box, h.label, h.tone || "planned", {
|
|
1352
|
+
size: h.size,
|
|
1353
|
+
weight: h.weight,
|
|
1354
|
+
focus: !!h.focus,
|
|
1355
|
+
color: h.color || h.labelColor || h.textColor,
|
|
1356
|
+
});
|
|
1357
|
+
// Live follow while this ring is up (seamless, not taped).
|
|
1358
|
+
await page
|
|
1359
|
+
.evaluate(({ sel, label, tone, size, weight, focus, color, zoom }) => {
|
|
1360
|
+
const tick = () => {
|
|
1361
|
+
const el = document.querySelector(sel);
|
|
1362
|
+
if (!el) {
|
|
1363
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const r = el.getBoundingClientRect();
|
|
1367
|
+
const box = { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
1368
|
+
if (window.__wgPositionRing) {
|
|
1369
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight, focus, color });
|
|
1370
|
+
}
|
|
1371
|
+
if (zoom && zoom > 1 && window.__wgSetCamera) {
|
|
1372
|
+
// Prefer cursor tip when visible (Screen Studio follow-mouse);
|
|
1373
|
+
// else keep the ring target centered.
|
|
1374
|
+
let cx = box.x + box.width / 2;
|
|
1375
|
+
let cy = box.y + box.height / 2;
|
|
1376
|
+
const cur = document.getElementById("wg-cursor");
|
|
1377
|
+
if (cur && cur.style.opacity !== "0") {
|
|
1378
|
+
const m = /translate\(([-\d.]+)px,\s*([-\d.]+)px\)/.exec(cur.style.transform || "");
|
|
1379
|
+
if (m) {
|
|
1380
|
+
cx = Number(m[1]);
|
|
1381
|
+
cy = Number(m[2]);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
window.__wgSetCamera(cx, cy, zoom, 0);
|
|
1385
|
+
}
|
|
1386
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1387
|
+
};
|
|
1388
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1389
|
+
const onScroll = () => {};
|
|
1390
|
+
window.__wgRingTrack = onScroll;
|
|
1391
|
+
}, {
|
|
1392
|
+
sel: h.selector,
|
|
1393
|
+
label: h.label,
|
|
1394
|
+
tone: h.tone || "planned",
|
|
1395
|
+
size: h.size || "md",
|
|
1396
|
+
weight: h.weight || "normal",
|
|
1397
|
+
focus: !!h.focus,
|
|
1398
|
+
color: h.color || h.labelColor || h.textColor || "",
|
|
1399
|
+
zoom: h.zoom != null && h.zoom > 1 ? h.zoom : 0,
|
|
1400
|
+
})
|
|
1401
|
+
.catch(() => {});
|
|
1402
|
+
|
|
1403
|
+
if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1404
|
+
const rows =
|
|
1405
|
+
opts && opts.todos && opts.todos.length
|
|
1406
|
+
? opts.todos
|
|
1407
|
+
: normalizeTodos(h.todos, h.todoIndex);
|
|
1408
|
+
await page
|
|
1409
|
+
.evaluate((todos) => {
|
|
1410
|
+
const ul = document.getElementById("wg-todos");
|
|
1411
|
+
if (!ul) return;
|
|
1412
|
+
ul.innerHTML = todos
|
|
1413
|
+
.map((t) => {
|
|
1414
|
+
const cls = t.current
|
|
1415
|
+
? "wg-todo-current"
|
|
1416
|
+
: t.done
|
|
1417
|
+
? "wg-todo-done"
|
|
1418
|
+
: "wg-todo-pending";
|
|
1419
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1420
|
+
const esc = (s) =>
|
|
1421
|
+
String(s)
|
|
1422
|
+
.replace(/&/g, "&")
|
|
1423
|
+
.replace(/</g, "<")
|
|
1424
|
+
.replace(/>/g, ">");
|
|
1425
|
+
return (
|
|
1426
|
+
"<li class=\\"" +
|
|
1427
|
+
cls +
|
|
1428
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1429
|
+
mark +
|
|
1430
|
+
"</span><span>" +
|
|
1431
|
+
esc(t.text || "") +
|
|
1432
|
+
"</span></li>"
|
|
1433
|
+
);
|
|
1434
|
+
})
|
|
1435
|
+
.join("");
|
|
1436
|
+
}, rows)
|
|
1437
|
+
.catch(() => {});
|
|
1320
1438
|
}
|
|
1439
|
+
|
|
1440
|
+
// Appear settle
|
|
1441
|
+
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1442
|
+
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1443
|
+
const legacyMs = 1100;
|
|
1444
|
+
const holdMs =
|
|
1445
|
+
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1446
|
+
await new Promise((res) => setTimeout(res, holdMs));
|
|
1447
|
+
// Disappear before next (respect fade)
|
|
1448
|
+
await stopLiveFollow();
|
|
1449
|
+
await hideRing(page);
|
|
1450
|
+
await resetCameraZoom(page);
|
|
1451
|
+
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1321
1452
|
} catch {
|
|
1322
|
-
|
|
1453
|
+
/* best-effort */
|
|
1323
1454
|
}
|
|
1324
1455
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
.evaluate((h) => {
|
|
1329
|
-
if (!window.__wgPositionRing) return;
|
|
1330
|
-
const reposition = () => {
|
|
1331
|
-
const el = document.querySelector(h.selector);
|
|
1332
|
-
if (!el) {
|
|
1333
|
-
if (window.__wgHideRing) window.__wgHideRing();
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned", {
|
|
1337
|
-
size: h.size || "md",
|
|
1338
|
-
weight: h.weight || "normal",
|
|
1339
|
-
});
|
|
1340
|
-
};
|
|
1341
|
-
reposition();
|
|
1342
|
-
window.__wgRingTrack = reposition;
|
|
1343
|
-
window.addEventListener("resize", reposition);
|
|
1344
|
-
window.addEventListener("scroll", reposition, true);
|
|
1345
|
-
}, last)
|
|
1346
|
-
.catch(() => {});
|
|
1347
|
-
} else {
|
|
1456
|
+
await stopLiveFollow();
|
|
1457
|
+
await resetCameraZoom(page);
|
|
1458
|
+
if (list.length === 0) {
|
|
1348
1459
|
await hideRing(page);
|
|
1349
1460
|
}
|
|
1350
1461
|
}
|
|
1351
1462
|
|
|
1463
|
+
|
|
1352
1464
|
async function renderAfterStep(page, info) {
|
|
1353
1465
|
await installOverlay(page, info.title);
|
|
1354
1466
|
// Clear any tracker from a previous highlight before cycling through this
|
|
@@ -1698,14 +1810,16 @@ async function markStepRunning(page, _opts) {
|
|
|
1698
1810
|
async function showRing(page, box, label, tone, style) {
|
|
1699
1811
|
const size = normalizeHighlightSize(style && style.size);
|
|
1700
1812
|
const weight = normalizeHighlightWeight(style && style.weight);
|
|
1813
|
+
const focus = !!(style && style.focus);
|
|
1814
|
+
const color = (style && (style.color || style.labelColor || style.textColor)) || "";
|
|
1701
1815
|
await page
|
|
1702
1816
|
.evaluate(
|
|
1703
|
-
({ box, label, tone, size, weight }) => {
|
|
1817
|
+
({ box, label, tone, size, weight, focus, color }) => {
|
|
1704
1818
|
if (window.__wgPositionRing) {
|
|
1705
|
-
window.__wgPositionRing(box, label, tone || "planned", { size, weight });
|
|
1819
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight, focus, color });
|
|
1706
1820
|
}
|
|
1707
1821
|
},
|
|
1708
|
-
{ box, label, tone: tone || "planned", size, weight },
|
|
1822
|
+
{ box, label, tone: tone || "planned", size, weight, focus, color },
|
|
1709
1823
|
)
|
|
1710
1824
|
.catch(() => {});
|
|
1711
1825
|
}
|
|
@@ -1795,7 +1909,10 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1795
1909
|
if (s.selector) {
|
|
1796
1910
|
try {
|
|
1797
1911
|
await ensureSelectorInView(page, s.selector);
|
|
1798
|
-
|
|
1912
|
+
{
|
|
1913
|
+
const zb = await page.locator(s.selector).first().boundingBox().catch(() => null);
|
|
1914
|
+
await applyCameraZoom(page, zb, s.zoom);
|
|
1915
|
+
}
|
|
1799
1916
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
1800
1917
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
1801
1918
|
} catch {
|
|
@@ -2565,9 +2682,10 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2565
2682
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
2566
2683
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
2567
2684
|
const autoNow = await currentAutoplay();
|
|
2568
|
-
//
|
|
2569
|
-
//
|
|
2570
|
-
|
|
2685
|
+
// Video recording: keep chrome compact. Live auto-next used to force-collapse
|
|
2686
|
+
// every NavBlock and hid the stepper (todos / modules) - only collapse while
|
|
2687
|
+
// act() runs (markStepRunning) so Playwright clicks hit the page, not the panel.
|
|
2688
|
+
const forceCollapsed = !!process.env.WAYGRAPH_VIDEO;
|
|
2571
2689
|
const requires = r.block.requires ?? [];
|
|
2572
2690
|
const keys = requires.map((k) => {
|
|
2573
2691
|
let value = "<not yet set>";
|
|
@@ -2622,8 +2740,40 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2622
2740
|
}
|
|
2623
2741
|
}
|
|
2624
2742
|
const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
|
|
2625
|
-
|
|
2626
|
-
|
|
2743
|
+
// Sequential stubBefore queue (appear -> dwell -> fade) before act.
|
|
2744
|
+
if (stubBeforeRef.current && stubBeforeRef.current.length) {
|
|
2745
|
+
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
2746
|
+
const beforeRings = stubBeforeRef.current.map((h) => {
|
|
2747
|
+
const styled = applyHighlightStyleDefaults(h, flowStyle);
|
|
2748
|
+
return {
|
|
2749
|
+
selector: styled.selector,
|
|
2750
|
+
label: formatHighlightCaption(styled),
|
|
2751
|
+
duration: styled.duration,
|
|
2752
|
+
fastMode: styled.fastMode,
|
|
2753
|
+
tone: styled.tone,
|
|
2754
|
+
size: styled.size,
|
|
2755
|
+
weight: styled.weight,
|
|
2756
|
+
zoom: styled.zoom,
|
|
2757
|
+
focus: styled.focus,
|
|
2758
|
+
color: styled.color,
|
|
2759
|
+
cursor: styled.cursor,
|
|
2760
|
+
};
|
|
2761
|
+
});
|
|
2762
|
+
await cycleHighlightRings(page, beforeRings, !!pacing.gatesFast, {
|
|
2763
|
+
pace: pacing.demoPace,
|
|
2764
|
+
todos: stubBeforeTodos,
|
|
2765
|
+
stepLabel: miniStepLabel({
|
|
2766
|
+
index: i,
|
|
2767
|
+
total: resolved.length,
|
|
2768
|
+
blockName: r.block.name,
|
|
2769
|
+
episodeNumber: r.episodeNumber,
|
|
2770
|
+
paceBadge: paceSpeak.paceBadge,
|
|
2771
|
+
}),
|
|
2772
|
+
});
|
|
2773
|
+
} else if (stubBeforePhase && stubBeforePhase.title) {
|
|
2774
|
+
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
2775
|
+
}
|
|
2776
|
+
await markStepRunning(page, {});
|
|
2627
2777
|
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
2628
2778
|
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
2629
2779
|
// (not only on regular Block clicks).
|
|
@@ -2672,6 +2822,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2672
2822
|
fixtures: fixturesOnError,
|
|
2673
2823
|
error: err,
|
|
2674
2824
|
});
|
|
2825
|
+
await setDemoBannerTitle(page, errPhase.title);
|
|
2675
2826
|
const errHighlights = errPhase.highlights.map((h) => {
|
|
2676
2827
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2677
2828
|
return {
|
|
@@ -2683,6 +2834,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2683
2834
|
size: styled.size,
|
|
2684
2835
|
weight: styled.weight,
|
|
2685
2836
|
zoom: styled.zoom,
|
|
2837
|
+
focus: styled.focus,
|
|
2838
|
+
color: styled.color,
|
|
2839
|
+
cursor: styled.cursor,
|
|
2686
2840
|
};
|
|
2687
2841
|
});
|
|
2688
2842
|
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
@@ -2761,6 +2915,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2761
2915
|
fixtures: fixturesAfter,
|
|
2762
2916
|
});
|
|
2763
2917
|
stubAfterTodos = afterPhase.todos || [];
|
|
2918
|
+
await setDemoBannerTitle(page, afterPhase.title);
|
|
2764
2919
|
highlights = afterPhase.highlights.map((h) => {
|
|
2765
2920
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2766
2921
|
return {
|
|
@@ -2772,6 +2927,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2772
2927
|
size: styled.size,
|
|
2773
2928
|
weight: styled.weight,
|
|
2774
2929
|
zoom: styled.zoom,
|
|
2930
|
+
focus: styled.focus,
|
|
2931
|
+
color: styled.color,
|
|
2932
|
+
cursor: styled.cursor,
|
|
2775
2933
|
};
|
|
2776
2934
|
});
|
|
2777
2935
|
} else {
|
|
@@ -2799,8 +2957,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2799
2957
|
episodeTitle: r.episodeTitle,
|
|
2800
2958
|
stepperMode,
|
|
2801
2959
|
todos: stubAfterTodos,
|
|
2802
|
-
|
|
2803
|
-
forceCollapsed: !!process.env.WAYGRAPH_VIDEO || ((await currentAutoplay()) && isNavBlock),
|
|
2960
|
+
forceCollapsed: !!process.env.WAYGRAPH_VIDEO,
|
|
2804
2961
|
});
|
|
2805
2962
|
await gate();
|
|
2806
2963
|
}
|
|
@@ -4197,7 +4354,7 @@ Aliases (compat): \`chain <spec>\` -> run --blocks; \`chain auto A B\` -> auto -
|
|
|
4197
4354
|
|
|
4198
4355
|
Project path optional (defaults to cwd). Flags beat WAYGRAPH_* env.
|
|
4199
4356
|
\`run\`/\`demo\`/\`auto\`: Flow export, .flow.ts path, or "a then b" chain (auto also explores).
|
|
4200
|
-
Hide stepper = compact "N / M · block" pill.
|
|
4357
|
+
Hide stepper = compact "N / M · block" pill. Collapses while a step runs (clicks hit the page). Video mode stays compact.
|
|
4201
4358
|
`);
|
|
4202
4359
|
process.exit(0);
|
|
4203
4360
|
}
|
package/dist/highlights.d.ts
CHANGED
|
@@ -45,8 +45,13 @@ export type StubPhaseFixtures = {
|
|
|
45
45
|
todos?: readonly WaygraphTodoInput[];
|
|
46
46
|
/** Which todo is current (0-based). Rows before = done, after = pending. */
|
|
47
47
|
todoIndex?: number;
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Camera zoom (Screen Studio style) - scales the page toward the target /
|
|
50
|
+
* cursor. Not element CSS zoom. Typical `1.25` .. `2`.
|
|
51
|
+
*/
|
|
49
52
|
zoom?: number;
|
|
53
|
+
/** Top demo banner title (the purple "waygraph demo" card). */
|
|
54
|
+
title?: string;
|
|
50
55
|
};
|
|
51
56
|
/** One named highlight slot on a block (selector + caption). */
|
|
52
57
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
@@ -75,14 +80,28 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
75
80
|
*/
|
|
76
81
|
weight?: HighlightWeight;
|
|
77
82
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
83
|
+
* Camera zoom while this ring is up (Screen Studio style - page scales toward
|
|
84
|
+
* the target / cursor). Typical `1.25` .. `2`. Cleared when the ring hides.
|
|
80
85
|
* Episode default: {@link StubCtx.zoom}.
|
|
81
86
|
*/
|
|
82
87
|
zoom?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Spotlight: dim the rest of the page, keep this target as the focal point.
|
|
90
|
+
*/
|
|
91
|
+
focus?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Caption text color (CSS color). Overrides tone label color when set.
|
|
94
|
+
* Aliases accepted at runtime: `color`, `labelColor`, `textColor`.
|
|
95
|
+
*/
|
|
96
|
+
color?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Move the demo cursor to the target while this ring is up (default true
|
|
99
|
+
* for authored stub queues; set false to keep cursor hidden).
|
|
100
|
+
*/
|
|
101
|
+
cursor?: boolean;
|
|
83
102
|
}
|
|
84
103
|
/** Flow fixture patch: label required; selector optional (inherits from stub). */
|
|
85
|
-
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom"> & FixtureDurationFields;
|
|
104
|
+
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom" | "focus" | "color" | "cursor"> & FixtureDurationFields;
|
|
86
105
|
export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
|
|
87
106
|
/**
|
|
88
107
|
* Open stub lifecycle context. Authors set rings + episode fixtures freely —
|
|
@@ -118,8 +137,12 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
118
137
|
todos(items: readonly WaygraphTodoInput[]): void;
|
|
119
138
|
/** Current checklist index (bump for sequential plans). */
|
|
120
139
|
todoIndex(n: number): void;
|
|
121
|
-
/** Default zoom for rings without their own zoom. */
|
|
140
|
+
/** Default camera zoom for rings without their own zoom. */
|
|
122
141
|
zoom(n: number): void;
|
|
142
|
+
/** Top banner title (waygraph demo card). */
|
|
143
|
+
title(text: string): void;
|
|
144
|
+
/** Alias for {@link StubCtx.title}. */
|
|
145
|
+
banner(text: string): void;
|
|
123
146
|
/** Batch-set rings + episode fixtures. */
|
|
124
147
|
set(partial: {
|
|
125
148
|
highlights?: HighlightStubPhase;
|
|
@@ -139,6 +162,7 @@ export type StubPhaseResult = {
|
|
|
139
162
|
todos: WaygraphTodoItem[];
|
|
140
163
|
todoIndex?: number;
|
|
141
164
|
zoom?: number;
|
|
165
|
+
title?: string;
|
|
142
166
|
};
|
|
143
167
|
/**
|
|
144
168
|
* One yap/slide in a multi-step demo caption sequence.
|
|
@@ -154,7 +178,7 @@ export interface WaygraphSlide extends FixtureDurationFields {
|
|
|
154
178
|
tone?: HighlightTone;
|
|
155
179
|
size?: HighlightSize;
|
|
156
180
|
weight?: HighlightWeight;
|
|
157
|
-
/** Magnify
|
|
181
|
+
/** Magnify via camera (Screen Studio) while this slide is showing. */
|
|
158
182
|
zoom?: number;
|
|
159
183
|
}
|
|
160
184
|
/**
|
package/dist/highlights.js
CHANGED
|
@@ -324,12 +324,26 @@ function pickZoom(base, patch) {
|
|
|
324
324
|
return { zoom };
|
|
325
325
|
return {};
|
|
326
326
|
}
|
|
327
|
+
function pickFxFields(base, patch) {
|
|
328
|
+
const out = {};
|
|
329
|
+
const focus = patch?.focus !== undefined ? patch.focus : base?.focus;
|
|
330
|
+
const color = patch?.color !== undefined ? patch.color : base?.color;
|
|
331
|
+
const cursor = patch?.cursor !== undefined ? patch.cursor : base?.cursor;
|
|
332
|
+
if (focus !== undefined)
|
|
333
|
+
out.focus = !!focus;
|
|
334
|
+
if (color !== undefined && String(color).trim())
|
|
335
|
+
out.color = String(color).trim();
|
|
336
|
+
if (cursor !== undefined)
|
|
337
|
+
out.cursor = !!cursor;
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
327
340
|
function mergeSlot(base, patch) {
|
|
328
341
|
if (!base && !patch)
|
|
329
342
|
return null;
|
|
330
343
|
const dwell = pickDurationFields(base, patch);
|
|
331
344
|
const style = pickStyleFields(base, patch);
|
|
332
345
|
const zoom = pickZoom(base, patch);
|
|
346
|
+
const fx = pickFxFields(base, patch);
|
|
333
347
|
if (!base && patch) {
|
|
334
348
|
if (!patch.selector)
|
|
335
349
|
return null;
|
|
@@ -341,6 +355,7 @@ function mergeSlot(base, patch) {
|
|
|
341
355
|
...style,
|
|
342
356
|
...dwell,
|
|
343
357
|
...zoom,
|
|
358
|
+
...fx,
|
|
344
359
|
};
|
|
345
360
|
}
|
|
346
361
|
if (base && !patch) {
|
|
@@ -350,6 +365,7 @@ function mergeSlot(base, patch) {
|
|
|
350
365
|
...(base.size ? { size: normalizeHighlightSize(base.size) } : {}),
|
|
351
366
|
...(base.weight ? { weight: normalizeHighlightWeight(base.weight) } : {}),
|
|
352
367
|
...pickZoom(base, undefined),
|
|
368
|
+
...pickFxFields(base, undefined),
|
|
353
369
|
};
|
|
354
370
|
}
|
|
355
371
|
return {
|
|
@@ -364,6 +380,7 @@ function mergeSlot(base, patch) {
|
|
|
364
380
|
...style,
|
|
365
381
|
...dwell,
|
|
366
382
|
...zoom,
|
|
383
|
+
...fx,
|
|
367
384
|
};
|
|
368
385
|
}
|
|
369
386
|
function createStubCtx(bag, opts) {
|
|
@@ -386,6 +403,12 @@ function createStubCtx(bag, opts) {
|
|
|
386
403
|
if (Number.isFinite(n) && n > 0)
|
|
387
404
|
bag.zoom = n;
|
|
388
405
|
},
|
|
406
|
+
title(text) {
|
|
407
|
+
bag.title = String(text ?? "");
|
|
408
|
+
},
|
|
409
|
+
banner(text) {
|
|
410
|
+
bag.title = String(text ?? "");
|
|
411
|
+
},
|
|
389
412
|
set(partial) {
|
|
390
413
|
if (partial.highlights)
|
|
391
414
|
bag.highlights = { ...partial.highlights };
|
|
@@ -396,6 +419,8 @@ function createStubCtx(bag, opts) {
|
|
|
396
419
|
if (partial.zoom !== undefined && Number.isFinite(partial.zoom) && partial.zoom > 0) {
|
|
397
420
|
bag.zoom = partial.zoom;
|
|
398
421
|
}
|
|
422
|
+
if (partial.title !== undefined)
|
|
423
|
+
bag.title = String(partial.title ?? "");
|
|
399
424
|
},
|
|
400
425
|
};
|
|
401
426
|
}
|
|
@@ -527,6 +552,7 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
527
552
|
todos,
|
|
528
553
|
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
529
554
|
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
555
|
+
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
|
530
556
|
};
|
|
531
557
|
}
|
|
532
558
|
/**
|
|
@@ -45,34 +45,61 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
45
45
|
out.__state === "LoggedIn"
|
|
46
46
|
? [Trait.url({ pathname: "/inventory.html" })]
|
|
47
47
|
: [Trait.visible('[data-test="error"]')],
|
|
48
|
-
// Open lifecycle:
|
|
49
|
-
//
|
|
48
|
+
// Open lifecycle: banner title, todos, Screen Studio camera zoom,
|
|
49
|
+
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
50
50
|
stubBefore: (ctx) => {
|
|
51
|
+
ctx.title("Signing in");
|
|
51
52
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
52
53
|
ctx.todoIndex(0);
|
|
53
|
-
ctx.zoom(1.
|
|
54
|
+
ctx.zoom(1.35);
|
|
54
55
|
ctx.highlights({
|
|
55
|
-
username: {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
username: {
|
|
57
|
+
selector: "#user-name",
|
|
58
|
+
label: "Username",
|
|
59
|
+
focus: true,
|
|
60
|
+
color: "#c9a6ff",
|
|
61
|
+
zoom: 1.4,
|
|
62
|
+
},
|
|
63
|
+
password: {
|
|
64
|
+
selector: "#password",
|
|
65
|
+
label: "Password",
|
|
66
|
+
focus: true,
|
|
67
|
+
color: "#c9a6ff",
|
|
68
|
+
zoom: 1.4,
|
|
69
|
+
},
|
|
70
|
+
submit: {
|
|
71
|
+
selector: "#login-button",
|
|
72
|
+
label: "Login",
|
|
73
|
+
focus: true,
|
|
74
|
+
color: "#86efac",
|
|
75
|
+
zoom: 1.55,
|
|
76
|
+
weight: "bold",
|
|
77
|
+
},
|
|
58
78
|
});
|
|
59
79
|
},
|
|
60
80
|
stubAfter: (ctx) => {
|
|
81
|
+
ctx.banner("Inventory");
|
|
61
82
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
62
83
|
ctx.todoIndex(2);
|
|
63
|
-
//
|
|
84
|
+
// Fleece sits lower - demo smooth-scrolls + Screen Studio zooms to it.
|
|
64
85
|
ctx.ring("fleece", {
|
|
65
86
|
selector: '[data-test="add-to-cart-sauce-labs-fleece-jacket"]',
|
|
66
87
|
label: "Fleece jacket",
|
|
67
|
-
detail: "Smooth scroll + zoom",
|
|
68
|
-
|
|
88
|
+
detail: "Smooth scroll + camera zoom",
|
|
89
|
+
focus: true,
|
|
90
|
+
color: "#86efac",
|
|
91
|
+
zoom: 1.45,
|
|
69
92
|
duration: true,
|
|
70
93
|
});
|
|
71
94
|
},
|
|
72
95
|
stubOnError: (ctx) => {
|
|
96
|
+
ctx.title("Login failed");
|
|
73
97
|
ctx.ring("error", {
|
|
74
98
|
selector: '[data-test="error"]',
|
|
75
99
|
label: "Login error banner",
|
|
100
|
+
tone: "danger",
|
|
101
|
+
focus: true,
|
|
102
|
+
color: "#f87171",
|
|
76
103
|
duration: true,
|
|
77
104
|
});
|
|
78
105
|
},
|
package/package.json
CHANGED
|
@@ -45,31 +45,61 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
45
45
|
out.__state === "LoggedIn"
|
|
46
46
|
? [Trait.url({ pathname: "/inventory.html" })]
|
|
47
47
|
: [Trait.visible('[data-test="error"]')],
|
|
48
|
+
// Open lifecycle: banner title, todos, Screen Studio camera zoom,
|
|
49
|
+
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
48
50
|
stubBefore: (ctx) => {
|
|
51
|
+
ctx.title("Signing in");
|
|
49
52
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
50
53
|
ctx.todoIndex(0);
|
|
51
|
-
ctx.zoom(1.
|
|
54
|
+
ctx.zoom(1.35);
|
|
52
55
|
ctx.highlights({
|
|
53
|
-
username: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
username: {
|
|
57
|
+
selector: "#user-name",
|
|
58
|
+
label: "Username",
|
|
59
|
+
focus: true,
|
|
60
|
+
color: "#c9a6ff",
|
|
61
|
+
zoom: 1.4,
|
|
62
|
+
},
|
|
63
|
+
password: {
|
|
64
|
+
selector: "#password",
|
|
65
|
+
label: "Password",
|
|
66
|
+
focus: true,
|
|
67
|
+
color: "#c9a6ff",
|
|
68
|
+
zoom: 1.4,
|
|
69
|
+
},
|
|
70
|
+
submit: {
|
|
71
|
+
selector: "#login-button",
|
|
72
|
+
label: "Login",
|
|
73
|
+
focus: true,
|
|
74
|
+
color: "#86efac",
|
|
75
|
+
zoom: 1.55,
|
|
76
|
+
weight: "bold",
|
|
77
|
+
},
|
|
56
78
|
});
|
|
57
79
|
},
|
|
58
80
|
stubAfter: (ctx) => {
|
|
81
|
+
ctx.banner("Inventory");
|
|
59
82
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
60
83
|
ctx.todoIndex(2);
|
|
84
|
+
// Fleece sits lower - demo smooth-scrolls + Screen Studio zooms to it.
|
|
61
85
|
ctx.ring("fleece", {
|
|
62
86
|
selector: '[data-test="add-to-cart-sauce-labs-fleece-jacket"]',
|
|
63
87
|
label: "Fleece jacket",
|
|
64
|
-
detail: "Smooth scroll + zoom",
|
|
65
|
-
|
|
88
|
+
detail: "Smooth scroll + camera zoom",
|
|
89
|
+
focus: true,
|
|
90
|
+
color: "#86efac",
|
|
91
|
+
zoom: 1.45,
|
|
66
92
|
duration: true,
|
|
67
93
|
});
|
|
68
94
|
},
|
|
69
95
|
stubOnError: (ctx) => {
|
|
96
|
+
ctx.title("Login failed");
|
|
70
97
|
ctx.ring("error", {
|
|
71
98
|
selector: '[data-test="error"]',
|
|
72
99
|
label: "Login error banner",
|
|
100
|
+
tone: "danger",
|
|
101
|
+
focus: true,
|
|
102
|
+
color: "#f87171",
|
|
73
103
|
duration: true,
|
|
74
104
|
});
|
|
75
105
|
},
|