waygraph 0.11.0 → 0.11.1
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 +232 -129
- package/dist/trait.d.ts +6 -1
- package/dist/trait.js +7 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -6
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -6
package/dist/cli.js
CHANGED
|
@@ -1343,12 +1343,18 @@ function extractVerifyHighlights(block, resultTag) {
|
|
|
1343
1343
|
* __wgNext no-ops once already consumed - but looked live when it wasn't).
|
|
1344
1344
|
* Disables the button and mem-key textareas, and swaps whichever gate
|
|
1345
1345
|
* text was showing (manual button or "Auto-advancing...") to "Running...".
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1346
|
+
* ALWAYS collapses the panel while act() runs - leaving it expanded was a
|
|
1347
|
+
* real bug: #wg-panel sits at z-index max with pointer-events:auto, so
|
|
1348
|
+
* Playwright's login/add-to-cart clicks hit the stepper instead of the app
|
|
1349
|
+
* ("subtree intercepts pointer events"). Empty/partial fills + a lucky
|
|
1350
|
+
* edge click on Login then produced Sauce Demo's Epic sadface, verify
|
|
1351
|
+
* still passed on the error banner, and the next block blew up. Collapse
|
|
1352
|
+
* also matches Dan's "step is running / can't interrupt" ask. (_opts kept
|
|
1353
|
+
* for call-site compat; autoCollapsePanel is now unconditional.)
|
|
1348
1354
|
*/
|
|
1349
|
-
async function markStepRunning(page,
|
|
1355
|
+
async function markStepRunning(page, _opts) {
|
|
1350
1356
|
await page
|
|
1351
|
-
.evaluate((
|
|
1357
|
+
.evaluate(() => {
|
|
1352
1358
|
const runBtn = document.getElementById("wg-run");
|
|
1353
1359
|
if (runBtn) {
|
|
1354
1360
|
runBtn.disabled = true;
|
|
@@ -1359,18 +1365,16 @@ async function markStepRunning(page, opts) {
|
|
|
1359
1365
|
document.querySelectorAll("#wg-panel textarea[data-key]").forEach((ta) => {
|
|
1360
1366
|
ta.disabled = true;
|
|
1361
1367
|
});
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
if (titleEl && label) titleEl.textContent = label;
|
|
1371
|
-
}
|
|
1368
|
+
const panel = document.getElementById("wg-panel");
|
|
1369
|
+
if (panel) {
|
|
1370
|
+
panel.classList.add("wg-collapsed");
|
|
1371
|
+
const toggle = panel.querySelector("[data-wg-toggle]");
|
|
1372
|
+
if (toggle) toggle.textContent = "Show";
|
|
1373
|
+
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1374
|
+
const label = panel.dataset.wgStepLabel;
|
|
1375
|
+
if (titleEl && label) titleEl.textContent = label;
|
|
1372
1376
|
}
|
|
1373
|
-
}
|
|
1377
|
+
})
|
|
1374
1378
|
.catch(() => {});
|
|
1375
1379
|
}
|
|
1376
1380
|
|
|
@@ -1411,10 +1415,23 @@ async function captionForLocator(page, locator, stubs, fallback) {
|
|
|
1411
1415
|
/**
|
|
1412
1416
|
* Multi-step yap captions - not block lifecycle. Each slide waits for Next
|
|
1413
1417
|
* (or auto-next), optional ring via slide.selector.
|
|
1418
|
+
*
|
|
1419
|
+
* Advance wait does NOT use exposeFunction/gate(): after a real navigation
|
|
1420
|
+
* (e.g. finish-order -> checkout-complete) the panel can render while
|
|
1421
|
+
* __wgNext is momentarily wedged, which left "Next slide" looking live but
|
|
1422
|
+
* doing nothing. In-page data-wg-acked + autoplay poll is the source of truth.
|
|
1414
1423
|
*/
|
|
1415
|
-
async function presentSlides(page, slides,
|
|
1424
|
+
async function presentSlides(page, slides, _gate, opts) {
|
|
1416
1425
|
const title = (opts && opts.title) || "waygraph demo";
|
|
1417
1426
|
const blockName = (opts && opts.blockName) || "";
|
|
1427
|
+
const episodeNumber = opts && opts.episodeNumber;
|
|
1428
|
+
const episodeTitle = (opts && opts.episodeTitle) || "";
|
|
1429
|
+
const autoplayMs =
|
|
1430
|
+
opts && opts.autoplayMs
|
|
1431
|
+
? Number(opts.autoplayMs)
|
|
1432
|
+
: process.env.WAYGRAPH_AUTOPLAY_MS
|
|
1433
|
+
? Number(process.env.WAYGRAPH_AUTOPLAY_MS)
|
|
1434
|
+
: 1800;
|
|
1418
1435
|
for (let i = 0; i < slides.length; i++) {
|
|
1419
1436
|
const s = slides[i];
|
|
1420
1437
|
const caption = formatHighlightCaption(s);
|
|
@@ -1430,113 +1447,148 @@ async function presentSlides(page, slides, gate, opts) {
|
|
|
1430
1447
|
} else {
|
|
1431
1448
|
await hideRing(page);
|
|
1432
1449
|
}
|
|
1433
|
-
await page
|
|
1434
|
-
|
|
1435
|
-
(
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
"<div class=\\"wg-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
"<div
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const manual = document.getElementById("wg-gate-manual");
|
|
1504
|
-
const auto = document.getElementById("wg-gate-auto");
|
|
1505
|
-
if (manual) manual.style.display = cb.checked ? "none" : "";
|
|
1506
|
-
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
const runBtn = document.getElementById("wg-run");
|
|
1510
|
-
if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
|
|
1511
|
-
},
|
|
1512
|
-
{
|
|
1513
|
-
index: i,
|
|
1514
|
-
total: slides.length,
|
|
1515
|
-
caption: s.caption,
|
|
1516
|
-
detail: s.detail || "",
|
|
1517
|
-
tag: s.tag || "",
|
|
1518
|
-
blockName,
|
|
1519
|
-
isLast,
|
|
1520
|
-
autoNow: false,
|
|
1521
|
-
},
|
|
1522
|
-
)
|
|
1523
|
-
.catch(() => {});
|
|
1524
|
-
await page
|
|
1525
|
-
.evaluate(() => {
|
|
1450
|
+
await page.evaluate(
|
|
1451
|
+
(info) => {
|
|
1452
|
+
let panel = document.getElementById("wg-panel");
|
|
1453
|
+
const isNew = !panel;
|
|
1454
|
+
if (!panel) {
|
|
1455
|
+
panel = document.createElement("div");
|
|
1456
|
+
panel.id = "wg-panel";
|
|
1457
|
+
}
|
|
1458
|
+
panel.classList.remove("wg-collapsed", "wg-error", "wg-expected");
|
|
1459
|
+
const esc = (t) =>
|
|
1460
|
+
String(t).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
1461
|
+
const episodeLine =
|
|
1462
|
+
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
1463
|
+
? "<div class=\\"wg-episode\\">Episode " +
|
|
1464
|
+
info.episodeNumber +
|
|
1465
|
+
(info.episodeTitle ? ": " + esc(info.episodeTitle) : "") +
|
|
1466
|
+
" · yap slides</div>"
|
|
1467
|
+
: "";
|
|
1468
|
+
panel.innerHTML =
|
|
1469
|
+
"<div class=\\"wg-chrome\\"><span class=\\"wg-chrome-title\\">waygraph demo</span>" +
|
|
1470
|
+
"<button type=\\"button\\" class=\\"wg-hide-btn\\" data-wg-toggle=\\"1\\">Hide</button></div>" +
|
|
1471
|
+
"<div class=\\"wg-body\\">" +
|
|
1472
|
+
episodeLine +
|
|
1473
|
+
"<h3>Slide " +
|
|
1474
|
+
(info.index + 1) +
|
|
1475
|
+
" / " +
|
|
1476
|
+
info.total +
|
|
1477
|
+
(info.blockName ? " · " + esc(info.blockName) : "") +
|
|
1478
|
+
"</h3>" +
|
|
1479
|
+
(info.tag
|
|
1480
|
+
? "<div style=\\"border:none;padding:0;margin:0 0 6px;font-size:11px;color:#c9a6ff\\">" +
|
|
1481
|
+
esc(info.tag) +
|
|
1482
|
+
"</div>"
|
|
1483
|
+
: "") +
|
|
1484
|
+
"<div class=\\"wg-narration\\">" +
|
|
1485
|
+
esc(info.caption) +
|
|
1486
|
+
"</div>" +
|
|
1487
|
+
(info.detail
|
|
1488
|
+
? "<div class=\\"wg-result-pretty\\" style=\\"margin:0 0 12px;color:#f0e8ff\\">" +
|
|
1489
|
+
esc(info.detail) +
|
|
1490
|
+
"</div>"
|
|
1491
|
+
: "") +
|
|
1492
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1493
|
+
(info.autoNow ? " checked" : "") +
|
|
1494
|
+
"> Auto-advance</label></div>" +
|
|
1495
|
+
"<div id=\\"wg-gate-manual\\"" +
|
|
1496
|
+
(info.autoNow ? " style=\\"display:none\\"" : "") +
|
|
1497
|
+
"><button type=\\"button\\" id=\\"wg-run\\">" +
|
|
1498
|
+
(info.isLast ? "Continue \\u25B6" : "Next slide \\u25B6") +
|
|
1499
|
+
"</button></div>" +
|
|
1500
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" +
|
|
1501
|
+
(info.autoNow ? "" : " style=\\"display:none\\"") +
|
|
1502
|
+
">Auto-advancing...</div>" +
|
|
1503
|
+
"</div>";
|
|
1504
|
+
if (isNew) {
|
|
1505
|
+
document.documentElement.appendChild(panel);
|
|
1506
|
+
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1507
|
+
}
|
|
1508
|
+
if (window.__wgWirePanelChrome) {
|
|
1509
|
+
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1510
|
+
stepLabel:
|
|
1511
|
+
"Slide " +
|
|
1512
|
+
(info.index + 1) +
|
|
1513
|
+
" / " +
|
|
1514
|
+
info.total +
|
|
1515
|
+
(info.blockName ? " · " + info.blockName : ""),
|
|
1516
|
+
forceCollapsed: false,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
let autoNow = false;
|
|
1526
1520
|
try {
|
|
1527
|
-
|
|
1528
|
-
const cb = document.getElementById("wg-autoplay-cb");
|
|
1529
|
-
if (cb) cb.checked = on;
|
|
1530
|
-
const manual = document.getElementById("wg-gate-manual");
|
|
1531
|
-
const auto = document.getElementById("wg-gate-auto");
|
|
1532
|
-
if (manual) manual.style.display = on ? "none" : "";
|
|
1533
|
-
if (auto) auto.style.display = on ? "" : "none";
|
|
1521
|
+
autoNow = localStorage.getItem("wg-autoplay") === "1";
|
|
1534
1522
|
} catch {
|
|
1535
1523
|
/* ignore */
|
|
1536
1524
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1525
|
+
const cb = document.getElementById("wg-autoplay-cb");
|
|
1526
|
+
if (cb) {
|
|
1527
|
+
cb.checked = autoNow;
|
|
1528
|
+
cb.addEventListener("change", () => {
|
|
1529
|
+
try {
|
|
1530
|
+
localStorage.setItem("wg-autoplay", cb.checked ? "1" : "0");
|
|
1531
|
+
} catch {
|
|
1532
|
+
/* ignore */
|
|
1533
|
+
}
|
|
1534
|
+
const manual = document.getElementById("wg-gate-manual");
|
|
1535
|
+
const auto = document.getElementById("wg-gate-auto");
|
|
1536
|
+
if (manual) manual.style.display = cb.checked ? "none" : "";
|
|
1537
|
+
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
const manual = document.getElementById("wg-gate-manual");
|
|
1541
|
+
const auto = document.getElementById("wg-gate-auto");
|
|
1542
|
+
if (manual) manual.style.display = autoNow ? "none" : "";
|
|
1543
|
+
if (auto) auto.style.display = autoNow ? "" : "none";
|
|
1544
|
+
const runBtn = document.getElementById("wg-run");
|
|
1545
|
+
if (runBtn) {
|
|
1546
|
+
runBtn.removeAttribute("data-wg-acked");
|
|
1547
|
+
runBtn.addEventListener(
|
|
1548
|
+
"click",
|
|
1549
|
+
(e) => {
|
|
1550
|
+
e.preventDefault();
|
|
1551
|
+
e.stopPropagation();
|
|
1552
|
+
runBtn.setAttribute("data-wg-acked", "1");
|
|
1553
|
+
},
|
|
1554
|
+
{ once: true },
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
index: i,
|
|
1560
|
+
total: slides.length,
|
|
1561
|
+
caption: s.caption,
|
|
1562
|
+
detail: s.detail || "",
|
|
1563
|
+
tag: s.tag || "",
|
|
1564
|
+
blockName,
|
|
1565
|
+
isLast,
|
|
1566
|
+
autoNow: false,
|
|
1567
|
+
episodeNumber: episodeNumber !== undefined ? episodeNumber : null,
|
|
1568
|
+
episodeTitle,
|
|
1569
|
+
},
|
|
1570
|
+
);
|
|
1571
|
+
const started = Date.now();
|
|
1572
|
+
for (;;) {
|
|
1573
|
+
const acked = await page
|
|
1574
|
+
.evaluate(() => {
|
|
1575
|
+
const btn = document.getElementById("wg-run");
|
|
1576
|
+
return !!(btn && btn.getAttribute("data-wg-acked") === "1");
|
|
1577
|
+
})
|
|
1578
|
+
.catch(() => false);
|
|
1579
|
+
if (acked) break;
|
|
1580
|
+
const auto = await page
|
|
1581
|
+
.evaluate(() => {
|
|
1582
|
+
try {
|
|
1583
|
+
return localStorage.getItem("wg-autoplay") === "1";
|
|
1584
|
+
} catch {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
})
|
|
1588
|
+
.catch(() => false);
|
|
1589
|
+
if (auto && Date.now() - started >= autoplayMs) break;
|
|
1590
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
1591
|
+
}
|
|
1540
1592
|
}
|
|
1541
1593
|
await hideRing(page);
|
|
1542
1594
|
}
|
|
@@ -1901,14 +1953,34 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
1901
1953
|
// already is.
|
|
1902
1954
|
page.setViewportSize = async () => {};
|
|
1903
1955
|
let resolveNext = null;
|
|
1956
|
+
// Render-to-gate race: a slide/step panel's Next button is wired to
|
|
1957
|
+
// window.__wgNext BEFORE gate() calls waitForNext() (there is at least
|
|
1958
|
+
// one more await - the autoplay-sync evaluate - in between on the
|
|
1959
|
+
// presentSlides path). A click landing in that small window used to be
|
|
1960
|
+
// dropped silently (resolveNext was still null) - the panel then looked
|
|
1961
|
+
// "stuck" forever since gate() started listening AFTER the click already
|
|
1962
|
+
// fired and no second click ever came. Queue it instead: the very next
|
|
1963
|
+
// waitForNext() call picks up a pending click immediately.
|
|
1964
|
+
let pendingNext = null;
|
|
1904
1965
|
await page.exposeFunction("__wgNext", (edits) => {
|
|
1905
1966
|
if (resolveNext) {
|
|
1906
1967
|
const r = resolveNext;
|
|
1907
1968
|
resolveNext = null;
|
|
1908
1969
|
r(edits);
|
|
1970
|
+
} else {
|
|
1971
|
+
pendingNext = edits;
|
|
1909
1972
|
}
|
|
1910
1973
|
});
|
|
1911
|
-
const waitForNext = () =>
|
|
1974
|
+
const waitForNext = () =>
|
|
1975
|
+
new Promise((res) => {
|
|
1976
|
+
if (pendingNext !== null) {
|
|
1977
|
+
const edits = pendingNext;
|
|
1978
|
+
pendingNext = null;
|
|
1979
|
+
res(edits);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
resolveNext = res;
|
|
1983
|
+
});
|
|
1912
1984
|
// WAYGRAPH_AUTOPLAY=1 only sets the STARTING checkbox state now - the
|
|
1913
1985
|
// panel's own "Auto-advance" checkbox can flip it live, mid-run, and a
|
|
1914
1986
|
// manual click always wins over an in-flight autoplay wait regardless of
|
|
@@ -2152,6 +2224,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2152
2224
|
title,
|
|
2153
2225
|
blockName: r.block.name,
|
|
2154
2226
|
fast: pacing.fast,
|
|
2227
|
+
episodeNumber: r.episodeNumber,
|
|
2228
|
+
episodeTitle: r.episodeTitle,
|
|
2229
|
+
autoplayMs,
|
|
2155
2230
|
});
|
|
2156
2231
|
}
|
|
2157
2232
|
let highlights;
|
|
@@ -2426,8 +2501,13 @@ async function main() {
|
|
|
2426
2501
|
}
|
|
2427
2502
|
}
|
|
2428
2503
|
const step = process.env.WAYGRAPH_STEP === "1";
|
|
2429
|
-
// Stepping through headless defeats the point
|
|
2430
|
-
|
|
2504
|
+
// Stepping through headless defeats the point for a MANUAL demo - a
|
|
2505
|
+
// human can't watch it. But an explicit WAYGRAPH_HEADED=0 always wins
|
|
2506
|
+
// over that default: --auto-play-video sets STEP=1 (it still needs the
|
|
2507
|
+
// overlay/narration machinery) AND HEADED=0 (unattended + recorded,
|
|
2508
|
+
// nobody has to watch a live window for it to be right).
|
|
2509
|
+
const headed =
|
|
2510
|
+
process.env.WAYGRAPH_HEADED === "0" ? false : process.env.WAYGRAPH_HEADED === "1" || step;
|
|
2431
2511
|
// Step mode defaults to a visible pace (each fill/click actually shows on
|
|
2432
2512
|
// screen instead of snapping in) unless the human overrides it - an
|
|
2433
2513
|
// explicit WAYGRAPH_SLOWMO=0 still means "off".
|
|
@@ -2815,13 +2895,23 @@ async function runTryDemo() {
|
|
|
2815
2895
|
if (!destDir)
|
|
2816
2896
|
return;
|
|
2817
2897
|
process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
|
|
2818
|
-
// Default: headed stepper, manual Next. --video records that session (not headless rush).
|
|
2819
|
-
process.env.WAYGRAPH_HEADED ??= "1";
|
|
2820
2898
|
process.env.WAYGRAPH_STEP ??= "1";
|
|
2821
2899
|
process.env.WAYGRAPH_AUTOPLAY ??= "0";
|
|
2822
|
-
// Explicit --no-step --video = unattended headless .webm only
|
|
2823
|
-
|
|
2900
|
+
// Explicit --no-step --video = unattended headless .webm only (the
|
|
2901
|
+
// documented cloud/CI recipe above) - nobody needs to watch a live
|
|
2902
|
+
// window for Playwright's own recordVideo to capture the right thing.
|
|
2903
|
+
// This must be decided BEFORE the headed default below, or the
|
|
2904
|
+
// unconditional "??=" there always wins and the documented recipe never
|
|
2905
|
+
// actually runs headless.
|
|
2906
|
+
const unattendedVideo = Boolean(process.env.WAYGRAPH_VIDEO) && process.env.WAYGRAPH_STEP === "0";
|
|
2907
|
+
if (unattendedVideo) {
|
|
2824
2908
|
process.env.WAYGRAPH_AUTOPLAY ??= "1";
|
|
2909
|
+
process.env.WAYGRAPH_HEADED ??= "0";
|
|
2910
|
+
}
|
|
2911
|
+
else {
|
|
2912
|
+
// Default: headed stepper, manual Next. --video records that session
|
|
2913
|
+
// too (not a headless rush) as long as --step is still on.
|
|
2914
|
+
process.env.WAYGRAPH_HEADED ??= "1";
|
|
2825
2915
|
}
|
|
2826
2916
|
const videoTo = process.env.WAYGRAPH_VIDEO;
|
|
2827
2917
|
console.log(videoTo
|
|
@@ -3107,6 +3197,10 @@ function parseRunFlags(argv) {
|
|
|
3107
3197
|
else if (a === "--auto-play-video") {
|
|
3108
3198
|
out.autoPlayVideo = true;
|
|
3109
3199
|
}
|
|
3200
|
+
else if (a === "--auto-play-video-head") {
|
|
3201
|
+
out.autoPlayVideo = true;
|
|
3202
|
+
out.nonHeadless = true;
|
|
3203
|
+
}
|
|
3110
3204
|
else if (a === "--fast") {
|
|
3111
3205
|
out.fast = true;
|
|
3112
3206
|
}
|
|
@@ -3215,8 +3309,16 @@ function applyRunFlags(flags, opts) {
|
|
|
3215
3309
|
flags.autoplay = true;
|
|
3216
3310
|
if (flags.video === undefined)
|
|
3217
3311
|
flags.video = "";
|
|
3218
|
-
|
|
3219
|
-
|
|
3312
|
+
// Overlay narration (captions/rings/slides) needs step mode to run, but
|
|
3313
|
+
// --auto-play-video is unattended-and-recorded by design - no human
|
|
3314
|
+
// has to watch a live window for Playwright's own recordVideo to
|
|
3315
|
+
// capture the right thing. Headless by default; --non-headless (or the
|
|
3316
|
+
// --auto-play-video-head alias, parsed above) opts back into a
|
|
3317
|
+
// visible browser for someone who wants to watch it live too.
|
|
3318
|
+
process.env.WAYGRAPH_STEP = "1";
|
|
3319
|
+
if (!flags.nonHeadless && process.env.WAYGRAPH_HEADED === undefined) {
|
|
3320
|
+
process.env.WAYGRAPH_HEADED = "0";
|
|
3321
|
+
}
|
|
3220
3322
|
}
|
|
3221
3323
|
if ((flags.fast || flags.fullStepper) && !opts?.allowDemoUi) {
|
|
3222
3324
|
console.error("waygraph: --fast / --full are demo-only flags");
|
|
@@ -3420,7 +3522,8 @@ Primary (less is more):
|
|
|
3420
3522
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
3421
3523
|
--fast Faster transitions + shorter auto-next gates
|
|
3422
3524
|
--full Classic wrap-all block chips (default: carousel)
|
|
3423
|
-
--auto-play-video
|
|
3525
|
+
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|
|
3526
|
+
--auto-play-video-head Same, but keep the browser visible (--non-headless)
|
|
3424
3527
|
--video-viewport WxH Recording size (default demo: 1920x1080)
|
|
3425
3528
|
--title / --base-url
|
|
3426
3529
|
waygraph run [--blocks <flow|file|spec>] Execute (no overlay unless --step)
|
package/dist/trait.d.ts
CHANGED
|
@@ -33,7 +33,12 @@ export interface Trait {
|
|
|
33
33
|
export declare function urlMatches(pattern: URLPatternInit): Trait;
|
|
34
34
|
/** Passes when `selector`'s text content is exactly `expected`. */
|
|
35
35
|
export declare function textEquals(selector: string, expected: string): Trait;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Passes once at least one element matching `selector` is visible on the page,
|
|
38
|
+
* waiting for it to appear. Uses `.first()` so list selectors (e.g. one row per
|
|
39
|
+
* message/document) don't trip Playwright strict mode - the check means
|
|
40
|
+
* "something matching is visible", not "the selector is unambiguous".
|
|
41
|
+
*/
|
|
37
42
|
export declare function visible(selector: string): Trait;
|
|
38
43
|
/**
|
|
39
44
|
* Discoverable entry point for the built-in Trait factories - type `Trait.` in
|
package/dist/trait.js
CHANGED
|
@@ -36,12 +36,17 @@ export function textEquals(selector, expected) {
|
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Passes once at least one element matching `selector` is visible on the page,
|
|
41
|
+
* waiting for it to appear. Uses `.first()` so list selectors (e.g. one row per
|
|
42
|
+
* message/document) don't trip Playwright strict mode - the check means
|
|
43
|
+
* "something matching is visible", not "the selector is unambiguous".
|
|
44
|
+
*/
|
|
40
45
|
export function visible(selector) {
|
|
41
46
|
return {
|
|
42
47
|
name: `visible(${selector})`,
|
|
43
48
|
async check(page) {
|
|
44
|
-
await page.locator(selector).waitFor({ state: "visible" });
|
|
49
|
+
await page.locator(selector).first().waitFor({ state: "visible" });
|
|
45
50
|
return true;
|
|
46
51
|
},
|
|
47
52
|
};
|
|
@@ -28,21 +28,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
28
28
|
detail: "Thank you for your order!",
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
|
+
// Shown AFTER act (order already placed) - selectors must match the
|
|
32
|
+
// completion page, not #finish on the overview.
|
|
31
33
|
slides: [
|
|
32
34
|
{
|
|
33
35
|
caption: "Checkout is a short pipeline",
|
|
34
|
-
detail: "Cart
|
|
36
|
+
detail: "Cart -> info -> overview -> done. You just placed the order.",
|
|
35
37
|
tag: "YAP",
|
|
36
38
|
},
|
|
37
39
|
{
|
|
38
|
-
caption: "
|
|
39
|
-
detail: "
|
|
40
|
+
caption: "Completion page is the durable proof",
|
|
41
|
+
detail: "The thank-you header is what verify asserts on.",
|
|
40
42
|
tag: "YAP",
|
|
41
|
-
selector: "
|
|
43
|
+
selector: ".complete-header",
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
|
-
caption: "
|
|
45
|
-
detail: "
|
|
46
|
+
caption: "Episode continues after this yap",
|
|
47
|
+
detail: "Next step rings the same banner, then you move on.",
|
|
46
48
|
tag: "YAP",
|
|
47
49
|
selector: ".complete-header",
|
|
48
50
|
},
|
package/package.json
CHANGED
|
@@ -28,21 +28,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
|
|
|
28
28
|
detail: "Thank you for your order!",
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
|
+
// Shown AFTER act (order already placed) - selectors must match the
|
|
32
|
+
// completion page, not #finish on the overview.
|
|
31
33
|
slides: [
|
|
32
34
|
{
|
|
33
35
|
caption: "Checkout is a short pipeline",
|
|
34
|
-
detail: "Cart
|
|
36
|
+
detail: "Cart -> info -> overview -> done. You just placed the order.",
|
|
35
37
|
tag: "YAP",
|
|
36
38
|
},
|
|
37
39
|
{
|
|
38
|
-
caption: "
|
|
39
|
-
detail: "
|
|
40
|
+
caption: "Completion page is the durable proof",
|
|
41
|
+
detail: "The thank-you header is what verify asserts on.",
|
|
40
42
|
tag: "YAP",
|
|
41
|
-
selector: "
|
|
43
|
+
selector: ".complete-header",
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
|
-
caption: "
|
|
45
|
-
detail: "
|
|
46
|
+
caption: "Episode continues after this yap",
|
|
47
|
+
detail: "Next step rings the same banner, then you move on.",
|
|
46
48
|
tag: "YAP",
|
|
47
49
|
selector: ".complete-header",
|
|
48
50
|
},
|