waygraph 0.12.27 → 0.12.29
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 +307 -146
- 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,17 @@ 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
|
+
// Unquoted [data-focus=1]: this lives inside CHAIN_RUNNER_SCRIPT's template
|
|
418
|
+
// literal - a \" here would be eaten by the outer template and break the
|
|
419
|
+
// generated .mjs. CSS allows unquoted attr values for identifiers.
|
|
420
|
+
"#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);}" +
|
|
421
|
+
"#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);}" +
|
|
422
|
+
"#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);}" +
|
|
423
|
+
"#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);}" +
|
|
424
|
+
"#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);}" +
|
|
425
|
+
"#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
426
|
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
417
427
|
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
418
428
|
// can ignore engine checks and watch purple + semantic tones.
|
|
@@ -431,7 +441,7 @@ const RING_CSS =
|
|
|
431
441
|
// window.__wgPositionRing in installOverlay.
|
|
432
442
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
433
443
|
"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 .
|
|
444
|
+
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .32s ease,left .28s ease,top .28s ease,background .15s,color .15s;}" +
|
|
435
445
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
436
446
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
437
447
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -685,7 +695,8 @@ async function installOverlay(page, title) {
|
|
|
685
695
|
window.__wgMoveCursorTo = (x, y, ms, instant) => {
|
|
686
696
|
const cursor = document.getElementById("wg-cursor");
|
|
687
697
|
if (!cursor) return;
|
|
688
|
-
|
|
698
|
+
const travel = ms || 600;
|
|
699
|
+
cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
|
|
689
700
|
if (instant) {
|
|
690
701
|
const prev = cursor.style.transition;
|
|
691
702
|
cursor.style.transition = "none";
|
|
@@ -696,6 +707,11 @@ async function installOverlay(page, title) {
|
|
|
696
707
|
cursor.style.transform = "translate(" + x + "px," + y + "px)";
|
|
697
708
|
}
|
|
698
709
|
cursor.style.opacity = "1";
|
|
710
|
+
// Screen Studio: pan the zoomed camera with the cursor.
|
|
711
|
+
const cam = window.__wgCamera;
|
|
712
|
+
if (cam && cam.scale > 1.001 && window.__wgSetCamera) {
|
|
713
|
+
window.__wgSetCamera(x, y, cam.scale, instant ? 0 : travel);
|
|
714
|
+
}
|
|
699
715
|
};
|
|
700
716
|
window.__wgHideCursor = () => {
|
|
701
717
|
const cursor = document.getElementById("wg-cursor");
|
|
@@ -721,6 +737,7 @@ async function installOverlay(page, title) {
|
|
|
721
737
|
ring.dataset.size = size;
|
|
722
738
|
ringLabel.dataset.size = size;
|
|
723
739
|
ringLabel.dataset.weight = weight;
|
|
740
|
+
ring.dataset.focus = st.focus ? "1" : "0";
|
|
724
741
|
const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
|
|
725
742
|
const margin = 6;
|
|
726
743
|
const vw = window.innerWidth;
|
|
@@ -740,6 +757,9 @@ async function installOverlay(page, title) {
|
|
|
740
757
|
ring.style.opacity = "1";
|
|
741
758
|
ringLabel.textContent = label;
|
|
742
759
|
ringLabel.style.opacity = "1";
|
|
760
|
+
const capColor = st.color || st.labelColor || st.textColor;
|
|
761
|
+
if (capColor) ringLabel.style.color = String(capColor);
|
|
762
|
+
else ringLabel.style.removeProperty("color");
|
|
743
763
|
// Measure the label's own natural width/height (its real
|
|
744
764
|
// rendered size, unchanged) before deciding where it fits.
|
|
745
765
|
const lw = ringLabel.offsetWidth;
|
|
@@ -779,29 +799,44 @@ async function installOverlay(page, title) {
|
|
|
779
799
|
if (ringLabel) ringLabel.style.opacity = "0";
|
|
780
800
|
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
781
801
|
};
|
|
802
|
+
// Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
|
|
803
|
+
window.__wgCamera = { scale: 1, cx: 0, cy: 0 };
|
|
804
|
+
window.__wgSetCamera = (cx, cy, scale, ms) => {
|
|
805
|
+
const body = document.body;
|
|
806
|
+
if (!body) return;
|
|
807
|
+
const s = Math.max(1, Math.min(Number(scale) || 1, 2.75));
|
|
808
|
+
window.__wgCamera = { scale: s, cx, cy };
|
|
809
|
+
const dur = ms != null && Number(ms) >= 0 ? Number(ms) : 500;
|
|
810
|
+
body.style.transition =
|
|
811
|
+
dur <= 0
|
|
812
|
+
? "none"
|
|
813
|
+
: "transform " + dur / 1000 + "s cubic-bezier(.22,1,.36,1)";
|
|
814
|
+
body.style.transformOrigin = "0 0";
|
|
815
|
+
if (s <= 1.001) {
|
|
816
|
+
body.style.transform = "";
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
body.style.transform =
|
|
820
|
+
"translate(" + cx + "px," + cy + "px) scale(" + s + ") translate(" + -cx + "px," + -cy + "px)";
|
|
821
|
+
};
|
|
822
|
+
window.__wgResetCamera = () => {
|
|
823
|
+
window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
|
|
824
|
+
};
|
|
782
825
|
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
|
-
});
|
|
826
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
791
827
|
};
|
|
792
|
-
window.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
el.dataset.wgZoomed = "1";
|
|
828
|
+
window.__wgSetBannerTitle = (title) => {
|
|
829
|
+
const banner = document.getElementById("wg-banner");
|
|
830
|
+
if (!banner) return;
|
|
831
|
+
let textEl = null;
|
|
832
|
+
for (const sp of banner.querySelectorAll("span")) {
|
|
833
|
+
if (!sp.classList.contains("wg-banner-tag")) { textEl = sp; break; }
|
|
834
|
+
}
|
|
835
|
+
if (!textEl) {
|
|
836
|
+
textEl = document.createElement("span");
|
|
837
|
+
banner.appendChild(textEl);
|
|
838
|
+
}
|
|
839
|
+
textEl.textContent = String(title || "");
|
|
805
840
|
};
|
|
806
841
|
const POSITIONS = ["left", "center", "right"];
|
|
807
842
|
const applyPos = (el, pos) => {
|
|
@@ -1202,153 +1237,233 @@ async function ensureSelectorInView(page, selector) {
|
|
|
1202
1237
|
}
|
|
1203
1238
|
}
|
|
1204
1239
|
|
|
1205
|
-
async function
|
|
1206
|
-
const z = Number(zoom);
|
|
1207
|
-
if (!
|
|
1240
|
+
async function applyCameraZoom(page, box, zoom) {
|
|
1241
|
+
const z = zoom != null && Number(zoom) > 1 ? Number(zoom) : 1;
|
|
1242
|
+
if (!box) {
|
|
1208
1243
|
await page.evaluate(() => {
|
|
1209
|
-
if (window.
|
|
1244
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
1210
1245
|
}).catch(() => {});
|
|
1211
1246
|
return;
|
|
1212
1247
|
}
|
|
1248
|
+
const cx = box.x + box.width / 2;
|
|
1249
|
+
const cy = box.y + box.height / 2;
|
|
1213
1250
|
await page
|
|
1214
|
-
.evaluate(
|
|
1215
|
-
(
|
|
1216
|
-
|
|
1217
|
-
},
|
|
1218
|
-
{ sel: selector, scale: z },
|
|
1219
|
-
)
|
|
1251
|
+
.evaluate(({ cx, cy, scale }) => {
|
|
1252
|
+
if (window.__wgSetCamera) window.__wgSetCamera(cx, cy, scale);
|
|
1253
|
+
}, { cx, cy, scale: z })
|
|
1220
1254
|
.catch(() => {});
|
|
1221
1255
|
}
|
|
1222
1256
|
|
|
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
|
-
);
|
|
1257
|
+
async function resetCameraZoom(page) {
|
|
1258
|
+
await page
|
|
1259
|
+
.evaluate(() => {
|
|
1260
|
+
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
1261
|
+
})
|
|
1262
|
+
.catch(() => {});
|
|
1245
1263
|
}
|
|
1246
1264
|
|
|
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
|
-
);
|
|
1265
|
+
async function setDemoBannerTitle(page, title) {
|
|
1266
|
+
if (title == null || title === "") return;
|
|
1267
|
+
await page
|
|
1268
|
+
.evaluate((t) => {
|
|
1269
|
+
if (window.__wgSetBannerTitle) window.__wgSetBannerTitle(t);
|
|
1270
|
+
}, String(title))
|
|
1271
|
+
.catch(() => {});
|
|
1260
1272
|
}
|
|
1261
1273
|
|
|
1274
|
+
|
|
1262
1275
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1263
1276
|
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
1277
|
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1267
1278
|
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1279
|
+
const fadeMs = 320;
|
|
1280
|
+
const n = list.length;
|
|
1281
|
+
const baseStepLabel = (opts && opts.stepLabel) || "";
|
|
1282
|
+
|
|
1283
|
+
const updateHlProgress = async (i) => {
|
|
1284
|
+
if (n <= 0) return;
|
|
1285
|
+
const tag = "[" + (i + 1) + "/" + n + "]";
|
|
1286
|
+
await page
|
|
1287
|
+
.evaluate(
|
|
1288
|
+
({ tag, baseStepLabel, i, n }) => {
|
|
1289
|
+
const panel = document.getElementById("wg-panel");
|
|
1290
|
+
if (!panel) return;
|
|
1291
|
+
panel.dataset.wgHlIdx = String(i + 1);
|
|
1292
|
+
panel.dataset.wgHlN = String(n);
|
|
1293
|
+
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1294
|
+
if (titleEl) {
|
|
1295
|
+
const base = panel.dataset.wgStepLabel || baseStepLabel || "waygraph demo";
|
|
1296
|
+
if (panel.classList.contains("wg-collapsed")) {
|
|
1297
|
+
titleEl.textContent = base + " " + tag;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
let span = document.getElementById("wg-hl-idx");
|
|
1301
|
+
const h3 = panel.querySelector("h3");
|
|
1302
|
+
if (h3) {
|
|
1303
|
+
if (!span) {
|
|
1304
|
+
span = document.createElement("span");
|
|
1305
|
+
span.id = "wg-hl-idx";
|
|
1306
|
+
span.style.marginLeft = "8px";
|
|
1307
|
+
span.style.color = "#c9a6ff";
|
|
1308
|
+
span.style.fontWeight = "700";
|
|
1309
|
+
h3.appendChild(span);
|
|
1310
|
+
}
|
|
1311
|
+
span.textContent = tag;
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
{ tag, baseStepLabel, i, n },
|
|
1315
|
+
)
|
|
1316
|
+
.catch(() => {});
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
const stopLiveFollow = async () => {
|
|
1320
|
+
await page
|
|
1321
|
+
.evaluate(() => {
|
|
1322
|
+
if (window.__wgRingRaf) {
|
|
1323
|
+
cancelAnimationFrame(window.__wgRingRaf);
|
|
1324
|
+
window.__wgRingRaf = null;
|
|
1325
|
+
}
|
|
1326
|
+
if (window.__wgRingTrack) {
|
|
1327
|
+
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1328
|
+
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1329
|
+
window.__wgRingTrack = null;
|
|
1330
|
+
}
|
|
1331
|
+
})
|
|
1332
|
+
.catch(() => {});
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1268
1335
|
for (let i = 0; i < list.length; i++) {
|
|
1269
1336
|
const h = list[i];
|
|
1270
1337
|
try {
|
|
1338
|
+
await stopLiveFollow();
|
|
1271
1339
|
await ensureSelectorInView(page, h.selector);
|
|
1272
|
-
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1273
1340
|
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
|
-
|
|
1341
|
+
if (!box) continue;
|
|
1342
|
+
await updateHlProgress(i);
|
|
1343
|
+
await applyCameraZoom(page, box, h.zoom);
|
|
1344
|
+
const useCursor = h.cursor !== false;
|
|
1345
|
+
if (useCursor) {
|
|
1346
|
+
const cx = box.x + box.width / 2;
|
|
1347
|
+
const cy = box.y + box.height / 2;
|
|
1348
|
+
await page
|
|
1349
|
+
.evaluate(({ cx, cy }) => {
|
|
1350
|
+
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(cx, cy, 500, false);
|
|
1351
|
+
}, { cx, cy })
|
|
1352
|
+
.catch(() => {});
|
|
1353
|
+
}
|
|
1354
|
+
await showRing(page, box, h.label, h.tone || "planned", {
|
|
1355
|
+
size: h.size,
|
|
1356
|
+
weight: h.weight,
|
|
1357
|
+
focus: !!h.focus,
|
|
1358
|
+
color: h.color || h.labelColor || h.textColor,
|
|
1359
|
+
});
|
|
1360
|
+
// Live follow while this ring is up (seamless, not taped).
|
|
1361
|
+
await page
|
|
1362
|
+
.evaluate(({ sel, label, tone, size, weight, focus, color, zoom }) => {
|
|
1363
|
+
const tick = () => {
|
|
1364
|
+
const el = document.querySelector(sel);
|
|
1365
|
+
if (!el) {
|
|
1366
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const r = el.getBoundingClientRect();
|
|
1370
|
+
const box = { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
1371
|
+
if (window.__wgPositionRing) {
|
|
1372
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight, focus, color });
|
|
1373
|
+
}
|
|
1374
|
+
if (zoom && zoom > 1 && window.__wgSetCamera) {
|
|
1375
|
+
// Prefer cursor tip when visible (Screen Studio follow-mouse);
|
|
1376
|
+
// else keep the ring target centered.
|
|
1377
|
+
let cx = box.x + box.width / 2;
|
|
1378
|
+
let cy = box.y + box.height / 2;
|
|
1379
|
+
const cur = document.getElementById("wg-cursor");
|
|
1380
|
+
if (cur && cur.style.opacity !== "0") {
|
|
1381
|
+
const m = /translate\(([-\d.]+)px,\s*([-\d.]+)px\)/.exec(cur.style.transform || "");
|
|
1382
|
+
if (m) {
|
|
1383
|
+
cx = Number(m[1]);
|
|
1384
|
+
cy = Number(m[2]);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
window.__wgSetCamera(cx, cy, zoom, 0);
|
|
1388
|
+
}
|
|
1389
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1390
|
+
};
|
|
1391
|
+
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
1392
|
+
const onScroll = () => {};
|
|
1393
|
+
window.__wgRingTrack = onScroll;
|
|
1394
|
+
}, {
|
|
1395
|
+
sel: h.selector,
|
|
1396
|
+
label: h.label,
|
|
1397
|
+
tone: h.tone || "planned",
|
|
1398
|
+
size: h.size || "md",
|
|
1399
|
+
weight: h.weight || "normal",
|
|
1400
|
+
focus: !!h.focus,
|
|
1401
|
+
color: h.color || h.labelColor || h.textColor || "",
|
|
1402
|
+
zoom: h.zoom != null && h.zoom > 1 ? h.zoom : 0,
|
|
1403
|
+
})
|
|
1404
|
+
.catch(() => {});
|
|
1405
|
+
|
|
1406
|
+
if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1407
|
+
const rows =
|
|
1408
|
+
opts && opts.todos && opts.todos.length
|
|
1409
|
+
? opts.todos
|
|
1410
|
+
: normalizeTodos(h.todos, h.todoIndex);
|
|
1411
|
+
await page
|
|
1412
|
+
.evaluate((todos) => {
|
|
1413
|
+
const ul = document.getElementById("wg-todos");
|
|
1414
|
+
if (!ul) return;
|
|
1415
|
+
ul.innerHTML = todos
|
|
1416
|
+
.map((t) => {
|
|
1417
|
+
const cls = t.current
|
|
1418
|
+
? "wg-todo-current"
|
|
1419
|
+
: t.done
|
|
1420
|
+
? "wg-todo-done"
|
|
1421
|
+
: "wg-todo-pending";
|
|
1422
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1423
|
+
const esc = (s) =>
|
|
1424
|
+
String(s)
|
|
1425
|
+
.replace(/&/g, "&")
|
|
1426
|
+
.replace(/</g, "<")
|
|
1427
|
+
.replace(/>/g, ">");
|
|
1428
|
+
return (
|
|
1429
|
+
"<li class=\\"" +
|
|
1430
|
+
cls +
|
|
1431
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1432
|
+
mark +
|
|
1433
|
+
"</span><span>" +
|
|
1434
|
+
esc(t.text || "") +
|
|
1435
|
+
"</span></li>"
|
|
1436
|
+
);
|
|
1437
|
+
})
|
|
1438
|
+
.join("");
|
|
1439
|
+
}, rows)
|
|
1440
|
+
.catch(() => {});
|
|
1320
1441
|
}
|
|
1442
|
+
|
|
1443
|
+
// Appear settle
|
|
1444
|
+
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1445
|
+
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1446
|
+
const legacyMs = 1100;
|
|
1447
|
+
const holdMs =
|
|
1448
|
+
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1449
|
+
await new Promise((res) => setTimeout(res, holdMs));
|
|
1450
|
+
// Disappear before next (respect fade)
|
|
1451
|
+
await stopLiveFollow();
|
|
1452
|
+
await hideRing(page);
|
|
1453
|
+
await resetCameraZoom(page);
|
|
1454
|
+
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1321
1455
|
} catch {
|
|
1322
|
-
|
|
1456
|
+
/* best-effort */
|
|
1323
1457
|
}
|
|
1324
1458
|
}
|
|
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 {
|
|
1459
|
+
await stopLiveFollow();
|
|
1460
|
+
await resetCameraZoom(page);
|
|
1461
|
+
if (list.length === 0) {
|
|
1348
1462
|
await hideRing(page);
|
|
1349
1463
|
}
|
|
1350
1464
|
}
|
|
1351
1465
|
|
|
1466
|
+
|
|
1352
1467
|
async function renderAfterStep(page, info) {
|
|
1353
1468
|
await installOverlay(page, info.title);
|
|
1354
1469
|
// Clear any tracker from a previous highlight before cycling through this
|
|
@@ -1698,14 +1813,16 @@ async function markStepRunning(page, _opts) {
|
|
|
1698
1813
|
async function showRing(page, box, label, tone, style) {
|
|
1699
1814
|
const size = normalizeHighlightSize(style && style.size);
|
|
1700
1815
|
const weight = normalizeHighlightWeight(style && style.weight);
|
|
1816
|
+
const focus = !!(style && style.focus);
|
|
1817
|
+
const color = (style && (style.color || style.labelColor || style.textColor)) || "";
|
|
1701
1818
|
await page
|
|
1702
1819
|
.evaluate(
|
|
1703
|
-
({ box, label, tone, size, weight }) => {
|
|
1820
|
+
({ box, label, tone, size, weight, focus, color }) => {
|
|
1704
1821
|
if (window.__wgPositionRing) {
|
|
1705
|
-
window.__wgPositionRing(box, label, tone || "planned", { size, weight });
|
|
1822
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight, focus, color });
|
|
1706
1823
|
}
|
|
1707
1824
|
},
|
|
1708
|
-
{ box, label, tone: tone || "planned", size, weight },
|
|
1825
|
+
{ box, label, tone: tone || "planned", size, weight, focus, color },
|
|
1709
1826
|
)
|
|
1710
1827
|
.catch(() => {});
|
|
1711
1828
|
}
|
|
@@ -1795,7 +1912,10 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1795
1912
|
if (s.selector) {
|
|
1796
1913
|
try {
|
|
1797
1914
|
await ensureSelectorInView(page, s.selector);
|
|
1798
|
-
|
|
1915
|
+
{
|
|
1916
|
+
const zb = await page.locator(s.selector).first().boundingBox().catch(() => null);
|
|
1917
|
+
await applyCameraZoom(page, zb, s.zoom);
|
|
1918
|
+
}
|
|
1799
1919
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
1800
1920
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
1801
1921
|
} catch {
|
|
@@ -2623,6 +2743,39 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2623
2743
|
}
|
|
2624
2744
|
}
|
|
2625
2745
|
const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
|
|
2746
|
+
// Sequential stubBefore queue (appear -> dwell -> fade) before act.
|
|
2747
|
+
if (stubBeforeRef.current && stubBeforeRef.current.length) {
|
|
2748
|
+
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
2749
|
+
const beforeRings = stubBeforeRef.current.map((h) => {
|
|
2750
|
+
const styled = applyHighlightStyleDefaults(h, flowStyle);
|
|
2751
|
+
return {
|
|
2752
|
+
selector: styled.selector,
|
|
2753
|
+
label: formatHighlightCaption(styled),
|
|
2754
|
+
duration: styled.duration,
|
|
2755
|
+
fastMode: styled.fastMode,
|
|
2756
|
+
tone: styled.tone,
|
|
2757
|
+
size: styled.size,
|
|
2758
|
+
weight: styled.weight,
|
|
2759
|
+
zoom: styled.zoom,
|
|
2760
|
+
focus: styled.focus,
|
|
2761
|
+
color: styled.color,
|
|
2762
|
+
cursor: styled.cursor,
|
|
2763
|
+
};
|
|
2764
|
+
});
|
|
2765
|
+
await cycleHighlightRings(page, beforeRings, !!pacing.gatesFast, {
|
|
2766
|
+
pace: pacing.demoPace,
|
|
2767
|
+
todos: stubBeforeTodos,
|
|
2768
|
+
stepLabel: miniStepLabel({
|
|
2769
|
+
index: i,
|
|
2770
|
+
total: resolved.length,
|
|
2771
|
+
blockName: r.block.name,
|
|
2772
|
+
episodeNumber: r.episodeNumber,
|
|
2773
|
+
paceBadge: paceSpeak.paceBadge,
|
|
2774
|
+
}),
|
|
2775
|
+
});
|
|
2776
|
+
} else if (stubBeforePhase && stubBeforePhase.title) {
|
|
2777
|
+
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
2778
|
+
}
|
|
2626
2779
|
await markStepRunning(page, {});
|
|
2627
2780
|
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
2628
2781
|
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
@@ -2672,6 +2825,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2672
2825
|
fixtures: fixturesOnError,
|
|
2673
2826
|
error: err,
|
|
2674
2827
|
});
|
|
2828
|
+
await setDemoBannerTitle(page, errPhase.title);
|
|
2675
2829
|
const errHighlights = errPhase.highlights.map((h) => {
|
|
2676
2830
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2677
2831
|
return {
|
|
@@ -2683,6 +2837,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2683
2837
|
size: styled.size,
|
|
2684
2838
|
weight: styled.weight,
|
|
2685
2839
|
zoom: styled.zoom,
|
|
2840
|
+
focus: styled.focus,
|
|
2841
|
+
color: styled.color,
|
|
2842
|
+
cursor: styled.cursor,
|
|
2686
2843
|
};
|
|
2687
2844
|
});
|
|
2688
2845
|
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
@@ -2761,6 +2918,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2761
2918
|
fixtures: fixturesAfter,
|
|
2762
2919
|
});
|
|
2763
2920
|
stubAfterTodos = afterPhase.todos || [];
|
|
2921
|
+
await setDemoBannerTitle(page, afterPhase.title);
|
|
2764
2922
|
highlights = afterPhase.highlights.map((h) => {
|
|
2765
2923
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2766
2924
|
return {
|
|
@@ -2772,6 +2930,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2772
2930
|
size: styled.size,
|
|
2773
2931
|
weight: styled.weight,
|
|
2774
2932
|
zoom: styled.zoom,
|
|
2933
|
+
focus: styled.focus,
|
|
2934
|
+
color: styled.color,
|
|
2935
|
+
cursor: styled.cursor,
|
|
2775
2936
|
};
|
|
2776
2937
|
});
|
|
2777
2938
|
} else {
|
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
|
},
|