waygraph 0.12.42 → 0.12.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/auto-explore-run.js +49 -0
- package/dist/cli.js +464 -91
- package/dist/engine.d.ts +8 -8
- package/dist/engine.js +8 -8
- package/dist/highlights.d.ts +13 -0
- package/dist/highlights.js +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/overlay-beacon.d.ts +44 -0
- package/dist/overlay-beacon.js +85 -0
- package/dist/step-overlay.d.ts +15 -0
- package/dist/step-overlay.js +16 -2
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -0
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +11 -7
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -0
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +11 -7
package/dist/engine.d.ts
CHANGED
|
@@ -292,15 +292,15 @@ export declare function withBlockPace<In extends Checkpoint<string>, Out extends
|
|
|
292
292
|
* {@link withTitle}.
|
|
293
293
|
*
|
|
294
294
|
* By itself this changes nothing about how the Flow runs - it still throws
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
295
|
+
* Marks a Flow whose last step is an intentional product failure (demo overlay).
|
|
296
|
+
* Step mode shows stubOnError rings + an amber "expected outcome" panel when:
|
|
297
|
+
* - the last block **throws**, or
|
|
298
|
+
* - the last block **succeeds** on the fail branch (e.g. LoginPage + error banner
|
|
299
|
+
* after submit-login branched instead of throwing).
|
|
300
|
+
* Headless `run()` is unchanged - branching success returns the checkpoint.
|
|
301
301
|
* @example const viewerBlockedFlow = withExpectedFailure(
|
|
302
|
-
* engine.defineFlow([start, NavLoginBlock,
|
|
303
|
-
* "locked_out_user
|
|
302
|
+
* engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end]),
|
|
303
|
+
* "locked_out_user stays on LoginPage with the error banner - working as intended.",
|
|
304
304
|
* );
|
|
305
305
|
*/
|
|
306
306
|
export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow: Flow<Out>, reason: string): Flow<Out>;
|
package/dist/engine.js
CHANGED
|
@@ -405,15 +405,15 @@ export function withBlockPace(block, pace) {
|
|
|
405
405
|
* {@link withTitle}.
|
|
406
406
|
*
|
|
407
407
|
* By itself this changes nothing about how the Flow runs - it still throws
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
408
|
+
* Marks a Flow whose last step is an intentional product failure (demo overlay).
|
|
409
|
+
* Step mode shows stubOnError rings + an amber "expected outcome" panel when:
|
|
410
|
+
* - the last block **throws**, or
|
|
411
|
+
* - the last block **succeeds** on the fail branch (e.g. LoginPage + error banner
|
|
412
|
+
* after submit-login branched instead of throwing).
|
|
413
|
+
* Headless `run()` is unchanged - branching success returns the checkpoint.
|
|
414
414
|
* @example const viewerBlockedFlow = withExpectedFailure(
|
|
415
|
-
* engine.defineFlow([start, NavLoginBlock,
|
|
416
|
-
* "locked_out_user
|
|
415
|
+
* engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end]),
|
|
416
|
+
* "locked_out_user stays on LoginPage with the error banner - working as intended.",
|
|
417
417
|
* );
|
|
418
418
|
*/
|
|
419
419
|
export function withExpectedFailure(flow, reason) {
|
package/dist/highlights.d.ts
CHANGED
|
@@ -54,6 +54,11 @@ export type StubPhaseFixtures = {
|
|
|
54
54
|
zoomOut?: boolean;
|
|
55
55
|
/** Top demo banner title (the purple "waygraph demo" card). */
|
|
56
56
|
title?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Floating checklist dock side. `left` | `right`.
|
|
59
|
+
* Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
|
|
60
|
+
*/
|
|
61
|
+
todoPos?: "left" | "right";
|
|
57
62
|
};
|
|
58
63
|
/** One named highlight slot on a block (selector + caption). */
|
|
59
64
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
@@ -161,6 +166,11 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
161
166
|
title(text: string): void;
|
|
162
167
|
/** Alias for {@link StubCtx.title}. */
|
|
163
168
|
banner(text: string): void;
|
|
169
|
+
/**
|
|
170
|
+
* Floating checklist dock side (`left` | `right`). Authors control this in
|
|
171
|
+
* code; click / env / --todo-left|right are fallbacks.
|
|
172
|
+
*/
|
|
173
|
+
todoPos(side: "left" | "right"): void;
|
|
164
174
|
/** Batch-set rings + episode fixtures. */
|
|
165
175
|
set(partial: {
|
|
166
176
|
highlights?: HighlightStubPhase;
|
|
@@ -182,6 +192,7 @@ export type StubPhaseResult = {
|
|
|
182
192
|
zoom?: number;
|
|
183
193
|
zoomOut?: boolean;
|
|
184
194
|
title?: string;
|
|
195
|
+
todoPos?: "left" | "right";
|
|
185
196
|
};
|
|
186
197
|
/**
|
|
187
198
|
* One yap/slide in a multi-step demo caption sequence.
|
|
@@ -322,6 +333,8 @@ export type ResolvedHighlight = WaygraphHighlightStub;
|
|
|
322
333
|
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
323
334
|
*/
|
|
324
335
|
export declare function normalizeTodos(todos: readonly WaygraphTodoInput[] | undefined, todoIndex?: number): WaygraphTodoItem[];
|
|
336
|
+
/** Normalize checklist dock side. */
|
|
337
|
+
export declare function normalizeTodoPos(raw: unknown): "left" | "right" | undefined;
|
|
325
338
|
/**
|
|
326
339
|
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
327
340
|
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
package/dist/highlights.js
CHANGED
|
@@ -383,6 +383,17 @@ function mergeSlot(base, patch) {
|
|
|
383
383
|
...fx,
|
|
384
384
|
};
|
|
385
385
|
}
|
|
386
|
+
/** Normalize checklist dock side. */
|
|
387
|
+
export function normalizeTodoPos(raw) {
|
|
388
|
+
const s = String(raw ?? "")
|
|
389
|
+
.trim()
|
|
390
|
+
.toLowerCase();
|
|
391
|
+
if (s === "left" || s === "l")
|
|
392
|
+
return "left";
|
|
393
|
+
if (s === "right" || s === "r")
|
|
394
|
+
return "right";
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
386
397
|
function createStubCtx(bag, opts) {
|
|
387
398
|
return {
|
|
388
399
|
out: opts.out,
|
|
@@ -412,6 +423,11 @@ function createStubCtx(bag, opts) {
|
|
|
412
423
|
banner(text) {
|
|
413
424
|
bag.title = String(text ?? "");
|
|
414
425
|
},
|
|
426
|
+
todoPos(side) {
|
|
427
|
+
const p = normalizeTodoPos(side);
|
|
428
|
+
if (p)
|
|
429
|
+
bag.todoPos = p;
|
|
430
|
+
},
|
|
415
431
|
set(partial) {
|
|
416
432
|
if (partial.highlights)
|
|
417
433
|
bag.highlights = { ...partial.highlights };
|
|
@@ -426,6 +442,9 @@ function createStubCtx(bag, opts) {
|
|
|
426
442
|
bag.zoomOut = !!partial.zoomOut;
|
|
427
443
|
if (partial.title !== undefined)
|
|
428
444
|
bag.title = String(partial.title ?? "");
|
|
445
|
+
const tp = normalizeTodoPos(partial.todoPos);
|
|
446
|
+
if (tp)
|
|
447
|
+
bag.todoPos = tp;
|
|
429
448
|
},
|
|
430
449
|
};
|
|
431
450
|
}
|
|
@@ -565,6 +584,7 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
565
584
|
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
566
585
|
...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
|
|
567
586
|
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
|
587
|
+
...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
|
|
568
588
|
};
|
|
569
589
|
}
|
|
570
590
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
7
|
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, } from "./highlights.js";
|
|
8
8
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
@@ -16,3 +16,5 @@ export { runTraverse } from "./traverse-run.js";
|
|
|
16
16
|
export type { TraverseOptions, TraverseSessionMode, TraverseWorkerResult, } from "./traverse-run.js";
|
|
17
17
|
export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse-coverage.js";
|
|
18
18
|
export type { TraverseCoverageReport } from "./traverse-coverage.js";
|
|
19
|
+
export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady, } from "./overlay-beacon.js";
|
|
20
|
+
export type { WgModalKind, WgBeaconRow } from "./overlay-beacon.js";
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@ export { connect, checkpoint } from "./types.js";
|
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
7
7
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
8
8
|
export { EdgeLeaseCoordinator } from "./traverse-lease.js";
|
|
9
9
|
export { runTraverse } from "./traverse-run.js";
|
|
10
10
|
export { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, normalizeHitKey, parseMinEdgeCoverage, writeCoverageReport, } from "./traverse-coverage.js";
|
|
11
|
+
export { WG_UI_SEL, WG_READY_PANEL_SEL, WG_READY_AUTO_SEL, WG_READY_BANNER_SEL, readWgOverlayBeacon, readWgOverlayBeaconInPage, assertWgOverlayReady, } from "./overlay-beacon.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blind-agent / Playwright checks for waygraph overlay modals.
|
|
3
|
+
*
|
|
4
|
+
* Every live modal root must carry:
|
|
5
|
+
* data-wg-ui="1"
|
|
6
|
+
* data-wg-modal="panel|banner|auto-panel"
|
|
7
|
+
* data-wg-ready="1" when painted and readable
|
|
8
|
+
*
|
|
9
|
+
* Agents: do not claim the stepper works unless
|
|
10
|
+
* `assertWgOverlayReady(page)` (or WAYGRAPH_PROVE_EXIT=1) passes.
|
|
11
|
+
*/
|
|
12
|
+
import type { Page } from "@playwright/test";
|
|
13
|
+
export declare const WG_UI_SEL = "[data-wg-ui=\"1\"]";
|
|
14
|
+
export declare const WG_READY_PANEL_SEL = "[data-wg-modal=\"panel\"][data-wg-ready=\"1\"]";
|
|
15
|
+
export declare const WG_READY_AUTO_SEL = "[data-wg-modal=\"auto-panel\"][data-wg-ready=\"1\"]";
|
|
16
|
+
export declare const WG_READY_BANNER_SEL = "[data-wg-modal=\"banner\"][data-wg-ready=\"1\"]";
|
|
17
|
+
export type WgModalKind = "panel" | "banner" | "auto-panel";
|
|
18
|
+
export type WgBeaconRow = {
|
|
19
|
+
id: string | null;
|
|
20
|
+
modal: string | null;
|
|
21
|
+
ready: boolean;
|
|
22
|
+
phase: string | null;
|
|
23
|
+
step: string | null;
|
|
24
|
+
block: string | null;
|
|
25
|
+
collapsed: boolean;
|
|
26
|
+
opacity: string;
|
|
27
|
+
textLen: number;
|
|
28
|
+
w: number;
|
|
29
|
+
h: number;
|
|
30
|
+
visible: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** Browser-side beacon reader (also installed as window.__wgOverlayBeacon). */
|
|
33
|
+
export declare function readWgOverlayBeaconInPage(): WgBeaconRow[];
|
|
34
|
+
export declare function readWgOverlayBeacon(page: Page): Promise<WgBeaconRow[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Hard gate: a blank / missing / opacity-0 modal fails.
|
|
37
|
+
* Use after demo/auto paints its first overlay.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertWgOverlayReady(page: Page, opts?: {
|
|
40
|
+
modal?: WgModalKind;
|
|
41
|
+
minTextLen?: number;
|
|
42
|
+
/** When true, collapsed mini chrome is allowed (still needs visible box + text). */
|
|
43
|
+
allowCollapsed?: boolean;
|
|
44
|
+
}): Promise<WgBeaconRow>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blind-agent / Playwright checks for waygraph overlay modals.
|
|
3
|
+
*
|
|
4
|
+
* Every live modal root must carry:
|
|
5
|
+
* data-wg-ui="1"
|
|
6
|
+
* data-wg-modal="panel|banner|auto-panel"
|
|
7
|
+
* data-wg-ready="1" when painted and readable
|
|
8
|
+
*
|
|
9
|
+
* Agents: do not claim the stepper works unless
|
|
10
|
+
* `assertWgOverlayReady(page)` (or WAYGRAPH_PROVE_EXIT=1) passes.
|
|
11
|
+
*/
|
|
12
|
+
export const WG_UI_SEL = '[data-wg-ui="1"]';
|
|
13
|
+
export const WG_READY_PANEL_SEL = '[data-wg-modal="panel"][data-wg-ready="1"]';
|
|
14
|
+
export const WG_READY_AUTO_SEL = '[data-wg-modal="auto-panel"][data-wg-ready="1"]';
|
|
15
|
+
export const WG_READY_BANNER_SEL = '[data-wg-modal="banner"][data-wg-ready="1"]';
|
|
16
|
+
/** Browser-side beacon reader (also installed as window.__wgOverlayBeacon). */
|
|
17
|
+
export function readWgOverlayBeaconInPage() {
|
|
18
|
+
const w = globalThis;
|
|
19
|
+
if (typeof w.__wgOverlayBeacon === "function")
|
|
20
|
+
return w.__wgOverlayBeacon();
|
|
21
|
+
return Array.from(document.querySelectorAll(WG_UI_SEL)).map((el) => {
|
|
22
|
+
const r = el.getBoundingClientRect();
|
|
23
|
+
const st = getComputedStyle(el);
|
|
24
|
+
return {
|
|
25
|
+
id: el.id || null,
|
|
26
|
+
modal: el.getAttribute("data-wg-modal"),
|
|
27
|
+
ready: el.getAttribute("data-wg-ready") === "1",
|
|
28
|
+
phase: el.getAttribute("data-wg-phase"),
|
|
29
|
+
step: el.getAttribute("data-wg-step"),
|
|
30
|
+
block: el.getAttribute("data-wg-block"),
|
|
31
|
+
collapsed: el.getAttribute("data-wg-collapsed") === "1" || el.classList.contains("wg-collapsed"),
|
|
32
|
+
opacity: st.opacity,
|
|
33
|
+
textLen: (el.textContent || "").trim().length,
|
|
34
|
+
w: Math.round(r.width),
|
|
35
|
+
h: Math.round(r.height),
|
|
36
|
+
visible: r.width > 0 && r.height > 0 && Number(st.opacity) > 0.05,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function readWgOverlayBeacon(page) {
|
|
41
|
+
return page.evaluate(() => {
|
|
42
|
+
const w = window;
|
|
43
|
+
if (typeof w.__wgOverlayBeacon === "function") {
|
|
44
|
+
return w.__wgOverlayBeacon();
|
|
45
|
+
}
|
|
46
|
+
return Array.from(document.querySelectorAll('[data-wg-ui="1"]')).map((el) => {
|
|
47
|
+
const r = el.getBoundingClientRect();
|
|
48
|
+
const st = getComputedStyle(el);
|
|
49
|
+
return {
|
|
50
|
+
id: el.id || null,
|
|
51
|
+
modal: el.getAttribute("data-wg-modal"),
|
|
52
|
+
ready: el.getAttribute("data-wg-ready") === "1",
|
|
53
|
+
phase: el.getAttribute("data-wg-phase"),
|
|
54
|
+
step: el.getAttribute("data-wg-step"),
|
|
55
|
+
block: el.getAttribute("data-wg-block"),
|
|
56
|
+
collapsed: el.getAttribute("data-wg-collapsed") === "1" || el.classList.contains("wg-collapsed"),
|
|
57
|
+
opacity: st.opacity,
|
|
58
|
+
textLen: (el.textContent || "").trim().length,
|
|
59
|
+
w: Math.round(r.width),
|
|
60
|
+
h: Math.round(r.height),
|
|
61
|
+
visible: r.width > 0 && r.height > 0 && Number(st.opacity) > 0.05,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Hard gate: a blank / missing / opacity-0 modal fails.
|
|
68
|
+
* Use after demo/auto paints its first overlay.
|
|
69
|
+
*/
|
|
70
|
+
export async function assertWgOverlayReady(page, opts) {
|
|
71
|
+
const modal = opts?.modal ?? "panel";
|
|
72
|
+
const minText = opts?.minTextLen ?? 12;
|
|
73
|
+
const rows = await readWgOverlayBeacon(page);
|
|
74
|
+
const hit = rows.find((r) => r.modal === modal &&
|
|
75
|
+
r.ready &&
|
|
76
|
+
r.visible &&
|
|
77
|
+
r.textLen >= minText &&
|
|
78
|
+
(opts?.allowCollapsed || !r.collapsed || r.textLen >= minText));
|
|
79
|
+
if (!hit) {
|
|
80
|
+
throw new Error(`waygraph overlay NOT ready (modal=${modal}, minTextLen=${minText}). ` +
|
|
81
|
+
`beacons=${JSON.stringify(rows)}. ` +
|
|
82
|
+
`Do not claim stepper/modal works.`);
|
|
83
|
+
}
|
|
84
|
+
return hit;
|
|
85
|
+
}
|
package/dist/step-overlay.d.ts
CHANGED
|
@@ -18,5 +18,20 @@ declare global {
|
|
|
18
18
|
__wgHideRing?: () => void;
|
|
19
19
|
__wgPendingNavClickLabel?: string;
|
|
20
20
|
__wgWirePanelChrome?: (panel: HTMLElement, storageKey: string, chromeTitle?: string) => void;
|
|
21
|
+
__wgStampModal?: (el: Element, kind: string, meta?: Record<string, unknown>) => void;
|
|
22
|
+
__wgOverlayBeacon?: () => Array<{
|
|
23
|
+
id: string | null;
|
|
24
|
+
modal: string | null;
|
|
25
|
+
ready: boolean;
|
|
26
|
+
phase: string | null;
|
|
27
|
+
step: string | null;
|
|
28
|
+
block: string | null;
|
|
29
|
+
collapsed: boolean;
|
|
30
|
+
opacity: string;
|
|
31
|
+
textLen: number;
|
|
32
|
+
w: number;
|
|
33
|
+
h: number;
|
|
34
|
+
visible: boolean;
|
|
35
|
+
}>;
|
|
21
36
|
}
|
|
22
37
|
}
|
package/dist/step-overlay.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
2
2
|
"border:2.5px solid #7C3AED;border-radius:10px;box-shadow:0 0 0 4px rgba(124,58,237,.16);" +
|
|
3
|
-
|
|
3
|
+
// Opacity only - tone color must snap (no gray/purple/yellow morph).
|
|
4
|
+
"transition:opacity .3s ease;}" +
|
|
4
5
|
// Automation = gray; semantic tones for authored highlights.
|
|
6
|
+
"#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
|
|
5
7
|
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
6
8
|
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
7
9
|
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
@@ -9,7 +11,8 @@ const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none
|
|
|
9
11
|
"#wg-ring[data-tone=success]{border-color:#22C55E;box-shadow:0 0 0 4px rgba(34,197,94,.22);}" +
|
|
10
12
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
11
13
|
"white-space:nowrap;padding:4px 9px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
12
|
-
"font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease
|
|
14
|
+
"font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
15
|
+
"#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
|
|
13
16
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
14
17
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
15
18
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -146,6 +149,17 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
|
|
|
146
149
|
banner.innerHTML =
|
|
147
150
|
"<span class='wg-banner-tag'>waygraph auto</span><span>" + title + "</span>";
|
|
148
151
|
document.documentElement.appendChild(banner);
|
|
152
|
+
banner.setAttribute("data-wg-ui", "1");
|
|
153
|
+
banner.setAttribute("data-wg-modal", "banner");
|
|
154
|
+
banner.setAttribute("data-wg-ready", "1");
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const existing = document.getElementById("wg-banner");
|
|
158
|
+
if (existing) {
|
|
159
|
+
existing.setAttribute("data-wg-ui", "1");
|
|
160
|
+
existing.setAttribute("data-wg-modal", "banner");
|
|
161
|
+
existing.setAttribute("data-wg-ready", "1");
|
|
162
|
+
}
|
|
149
163
|
}
|
|
150
164
|
if (showBanner && !document.title.startsWith("[waygraph] ")) {
|
|
151
165
|
document.title = "[waygraph] " + document.title;
|
|
@@ -78,6 +78,22 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
78
78
|
});
|
|
79
79
|
},
|
|
80
80
|
stubAfter: (ctx) => {
|
|
81
|
+
// Fail branch lands on LoginPage with Epic sadface - do not ring the
|
|
82
|
+
// inventory shelf. Success (LoggedIn) hands off to shopFlow.
|
|
83
|
+
if (ctx.out && ctx.out.__state === "LoginPage") {
|
|
84
|
+
ctx.title("Login blocked");
|
|
85
|
+
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
86
|
+
ctx.todoIndex(2);
|
|
87
|
+
ctx.ring("error", {
|
|
88
|
+
selector: '[data-test="error"]',
|
|
89
|
+
label: "Login error banner",
|
|
90
|
+
tone: "danger",
|
|
91
|
+
focus: true,
|
|
92
|
+
color: "#f87171",
|
|
93
|
+
duration: true,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
81
97
|
ctx.banner("Inventory");
|
|
82
98
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
83
99
|
ctx.todoIndex(2);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Engine, start, end, withTitle, withSessionReset } from "waygraph";
|
|
1
|
+
import { Engine, start, end, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
|
|
2
2
|
import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
|
|
3
3
|
import { SubmitLoginActionBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
|
|
4
4
|
|
|
@@ -16,10 +16,14 @@ const engine = new Engine();
|
|
|
16
16
|
// genuinely fresh cookies/storage, or the previous user's session would
|
|
17
17
|
// still be live and this login attempt wouldn't mean anything.
|
|
18
18
|
//
|
|
19
|
-
// submit-login
|
|
20
|
-
// resolve -> LoginPage, verify confirms the error banner
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// submit-login branches: observe sees no /inventory.html navigation,
|
|
20
|
+
// resolve -> LoginPage, verify confirms the error banner. Demo still wraps
|
|
21
|
+
// withExpectedFailure so step 9/9 shows stubOnError rings + amber
|
|
22
|
+
// "expected outcome" panel (branching no longer throws).
|
|
23
|
+
export const viewerBlockedFlow = withExpectedFailure(
|
|
24
|
+
withTitle(
|
|
25
|
+
withSessionReset(engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end])),
|
|
26
|
+
"Viewer: Blocked Login Attempt",
|
|
27
|
+
),
|
|
28
|
+
"locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
|
|
25
29
|
);
|
package/package.json
CHANGED
|
@@ -78,6 +78,22 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
78
78
|
});
|
|
79
79
|
},
|
|
80
80
|
stubAfter: (ctx) => {
|
|
81
|
+
// Fail branch lands on LoginPage with Epic sadface - do not ring the
|
|
82
|
+
// inventory shelf. Success (LoggedIn) hands off to shopFlow.
|
|
83
|
+
if (ctx.out && ctx.out.__state === "LoginPage") {
|
|
84
|
+
ctx.title("Login blocked");
|
|
85
|
+
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
86
|
+
ctx.todoIndex(2);
|
|
87
|
+
ctx.ring("error", {
|
|
88
|
+
selector: '[data-test="error"]',
|
|
89
|
+
label: "Login error banner",
|
|
90
|
+
tone: "danger",
|
|
91
|
+
focus: true,
|
|
92
|
+
color: "#f87171",
|
|
93
|
+
duration: true,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
81
97
|
ctx.banner("Inventory");
|
|
82
98
|
ctx.todos(["Enter username", "Enter password", "Click Login"]);
|
|
83
99
|
ctx.todoIndex(2);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Engine, start, end, withTitle, withSessionReset } from "waygraph";
|
|
1
|
+
import { Engine, start, end, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
|
|
2
2
|
import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
|
|
3
3
|
import { SubmitLoginActionBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
|
|
4
4
|
|
|
@@ -16,10 +16,14 @@ const engine = new Engine();
|
|
|
16
16
|
// genuinely fresh cookies/storage, or the previous user's session would
|
|
17
17
|
// still be live and this login attempt wouldn't mean anything.
|
|
18
18
|
//
|
|
19
|
-
// submit-login
|
|
20
|
-
// resolve -> LoginPage, verify confirms the error banner
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// submit-login branches: observe sees no /inventory.html navigation,
|
|
20
|
+
// resolve -> LoginPage, verify confirms the error banner. Demo still wraps
|
|
21
|
+
// withExpectedFailure so step 9/9 shows stubOnError rings + amber
|
|
22
|
+
// "expected outcome" panel (branching no longer throws).
|
|
23
|
+
export const viewerBlockedFlow = withExpectedFailure(
|
|
24
|
+
withTitle(
|
|
25
|
+
withSessionReset(engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end])),
|
|
26
|
+
"Viewer: Blocked Login Attempt",
|
|
27
|
+
),
|
|
28
|
+
"locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
|
|
25
29
|
);
|