waygraph 0.15.36 → 0.15.38
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/auto-session/session.d.ts +2 -0
- package/dist/auto-session/session.js +16 -1
- package/dist/engine/branch-regression.d.ts +19 -12
- package/dist/engine/branch-regression.js +75 -30
- package/dist/engine/engine-class.js +6 -5
- package/dist/index.d.ts +1 -1
- package/dist/mem-page.d.ts +8 -0
- package/dist/mem-page.js +14 -0
- package/dist/pilot-overlay/overlay-fixtures.d.ts +9 -0
- package/dist/pilot-overlay/overlay-fixtures.js +60 -0
- package/examples/saucedemo/.waygraph-auto/2b831907.log +6 -0
- package/examples/saucedemo/.waygraph-auto/3c1e5fe0.log +22 -0
- package/examples/saucedemo/.waygraph-auto/7b226350.log +5 -0
- package/examples/saucedemo/.waygraph-auto/7effd5b2.log +20 -0
- package/examples/saucedemo/.waygraph-auto/9761c4c4.log +6 -0
- package/examples/saucedemo/.waygraph-auto/9dd78c5e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/aa1beafb.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ddf975e6.log +0 -0
- package/package.json +1 -1
|
@@ -72,6 +72,8 @@ export declare class AutoSession {
|
|
|
72
72
|
* fallback when a Block's own `resolve()` doesn't set `__state` - same
|
|
73
73
|
* behavior `applyPick` always had, just factored out, not changed.
|
|
74
74
|
*/
|
|
75
|
+
/** Paint + log a Block's authored waygraph overlay fixtures (same as demo does), best-effort. */
|
|
76
|
+
private paintStubPhase;
|
|
75
77
|
private runNamedBlock;
|
|
76
78
|
/** Applies exactly one pick: runs at most one Block, returns the resulting state. */
|
|
77
79
|
applyPick(raw: string): Promise<ApplyPickResult>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Split out of the former 1,078-line auto-session.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
2
|
import { CONSOLE_LOG_MAX_ENTRIES, FULL_MODE_DEFAULT_DEPTH, FULL_MODE_MAX_NODES, FULL_MODE_MAX_TEXT_LENGTH, TRACE_MAX_STEPS, buildSessionSnapshot, parsePick, stubFilePath, stubPhaseHasContent, walkFullDom } from "./helpers.js";
|
|
3
|
-
import { instrumentPilotActions, installPersistentPilotOverlay, showPilotActivity, showPilotFixtures, showPilotVision, updatePilotOverlay } from "../pilot-overlay.js";
|
|
3
|
+
import { instrumentPilotActions, installPersistentPilotOverlay, showPilotActivity, showPilotFixtures, showPilotVision, phaseToOverlayFixtures, describeOverlayFixtures, updatePilotOverlay } from "../pilot-overlay.js";
|
|
4
4
|
import { buildExploreContext, buildExploreMenu } from "../auto-explore.js";
|
|
5
5
|
import { MemPage } from "../mem-page.js";
|
|
6
6
|
import { Engine } from "../engine.js";
|
|
7
7
|
import { detectHere, ensureLivePage, ensureMem, resolveBaseUrl, runOneBlock, seedDefaultMem } from "../auto-explore-run.js";
|
|
8
8
|
import { collectKnownInteractionSelectors, unmappedInteractionsPageScript } from "../coverage-gap.js";
|
|
9
9
|
import { runStubPhase } from "../highlights.js";
|
|
10
|
+
import { verboseLog } from "../verbose-log.js";
|
|
10
11
|
import { findBlockPathDetailed } from "../graph.js";
|
|
11
12
|
/**
|
|
12
13
|
* Non-interactive explore session: same graph/library/mem/browser setup
|
|
@@ -328,9 +329,19 @@ export class AutoSession {
|
|
|
328
329
|
* fallback when a Block's own `resolve()` doesn't set `__state` - same
|
|
329
330
|
* behavior `applyPick` always had, just factored out, not changed.
|
|
330
331
|
*/
|
|
332
|
+
/** Paint + log a Block's authored waygraph overlay fixtures (same as demo does), best-effort. */
|
|
333
|
+
async paintStubPhase(tag, phase, holdMs) {
|
|
334
|
+
if (!this.page || !stubPhaseHasContent(phase))
|
|
335
|
+
return;
|
|
336
|
+
const fixtures = phaseToOverlayFixtures(phase, holdMs);
|
|
337
|
+
for (const line of describeOverlayFixtures(tag, fixtures))
|
|
338
|
+
verboseLog("pilot", line);
|
|
339
|
+
await showPilotFixtures(this.page, fixtures).catch(() => { });
|
|
340
|
+
}
|
|
331
341
|
async runNamedBlock(entry, expectedTo, instanceOption) {
|
|
332
342
|
const from = this.here;
|
|
333
343
|
const stubBeforeResult = await runStubPhase(entry.block, "stubBefore");
|
|
344
|
+
await this.paintStubPhase("stub.before", stubBeforeResult, 6000);
|
|
334
345
|
const stepBase = {
|
|
335
346
|
block: entry.block.name,
|
|
336
347
|
from,
|
|
@@ -348,6 +359,7 @@ export class AutoSession {
|
|
|
348
359
|
this.here = result.__state ?? expectedTo;
|
|
349
360
|
this.lastRunNote = `${entry.block.name} -> ${this.here}`;
|
|
350
361
|
const stubAfterResult = await runStubPhase(entry.block, "stubAfter", { out: result });
|
|
362
|
+
await this.paintStubPhase("stub.after", stubAfterResult, 6000);
|
|
351
363
|
this.pushTrace({
|
|
352
364
|
...stepBase,
|
|
353
365
|
to: this.here,
|
|
@@ -358,6 +370,7 @@ export class AutoSession {
|
|
|
358
370
|
this.lastRunNote = `${entry.block.name} failed`;
|
|
359
371
|
const message = err instanceof Error ? err.message : String(err);
|
|
360
372
|
const stubOnErrorResult = await runStubPhase(entry.block, "stubOnError", { error: err });
|
|
373
|
+
await this.paintStubPhase("stub.error", stubOnErrorResult, 8000);
|
|
361
374
|
this.pushTrace({
|
|
362
375
|
...stepBase,
|
|
363
376
|
error: message,
|
|
@@ -588,6 +601,8 @@ export class AutoSession {
|
|
|
588
601
|
// Same bottom-left activity toast as Running (Dom)/(Block) - agent narration.
|
|
589
602
|
await showPilotActivity(this.page, `Highlighting: ${detail}`);
|
|
590
603
|
try {
|
|
604
|
+
for (const line of describeOverlayFixtures("highlight", fixtures))
|
|
605
|
+
verboseLog("pilot", line);
|
|
591
606
|
const { painted, missing } = await showPilotFixtures(this.page, fixtures);
|
|
592
607
|
const menu = await this.currentMenu();
|
|
593
608
|
this.lastRunNote = fixtures.clear
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Checkpoint } from "../types.js";
|
|
2
2
|
import type { Flow } from "./flow.js";
|
|
3
3
|
import type { RunGraphOptions } from "./run-graph.js";
|
|
4
|
+
import { MemPage } from "../mem-page.js";
|
|
4
5
|
import type { BrowserContext } from "@playwright/test";
|
|
5
6
|
/** One branch reachable from `flow`, possibly several `.branch()` levels deep. */
|
|
6
7
|
export interface BranchEntry {
|
|
@@ -13,27 +14,33 @@ export declare function branchRoutes(flow: Flow<any>): ReadonlyMap<string, Flow<
|
|
|
13
14
|
/**
|
|
14
15
|
* Every branch Flow reachable from `flow`, at any depth, deduped by object identity (the same
|
|
15
16
|
* route Flow reused under two tags is only listed once). Doesn't include `flow` itself - only its
|
|
16
|
-
* `.branch()` continuations.
|
|
17
|
+
* `.branch()` continuations. Static - doesn't run anything.
|
|
17
18
|
*/
|
|
18
19
|
export declare function collectBranchFlows(flow: Flow<any>): BranchEntry[];
|
|
19
|
-
/** Why a branch was skipped instead of run. */
|
|
20
|
-
export type BranchSkipReason = "first Block is not nav/page - this branch needs page state a live run would have reached through its prefix, not its own blocks";
|
|
21
20
|
export interface BranchRunResult {
|
|
22
21
|
path: string;
|
|
23
|
-
status: "ok" | "failed"
|
|
22
|
+
status: "ok" | "failed";
|
|
24
23
|
result?: Checkpoint<string>;
|
|
25
24
|
error?: string;
|
|
26
|
-
|
|
25
|
+
}
|
|
26
|
+
export interface RunBranchRegressionOptions extends RunGraphOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Default `true`: clone the real session (storageState + current URL) at every branch point, so
|
|
29
|
+
* every route runs from a genuine copy of the live state, not a fresh page. `false` runs the
|
|
30
|
+
* whole tree exactly once, through whichever single path the live state actually takes (same as
|
|
31
|
+
* calling `flow.run()` directly) - can't explore siblings, since the one real page has moved on
|
|
32
|
+
* once a branch's Blocks run.
|
|
33
|
+
*/
|
|
34
|
+
cloneSession?: boolean;
|
|
27
35
|
}
|
|
28
36
|
/**
|
|
29
|
-
* Runs every
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* gap to code around.
|
|
37
|
+
* Runs every path through `flow`'s `.branch()` tree - by default in a CLONED session per branch
|
|
38
|
+
* point (see this file's own header), so one branch finishing doesn't consume another's chance to
|
|
39
|
+
* run. Each explored branch gets its own `MemPage` (via {@link MemPage.clone}, seeded further with
|
|
40
|
+
* {@link seedMemStub} for anything a route alone requires that the shared prefix didn't already set).
|
|
34
41
|
* @example
|
|
35
42
|
* for (const r of await runBranchRegression(loginFlow, context)) {
|
|
36
|
-
* console.log(r.path, r.status, r.error ??
|
|
43
|
+
* console.log(r.path, r.status, r.error ?? "");
|
|
37
44
|
* }
|
|
38
45
|
*/
|
|
39
|
-
export declare function runBranchRegression(flow: Flow<any>, context: BrowserContext, options?:
|
|
46
|
+
export declare function runBranchRegression(flow: Flow<any>, context: BrowserContext, mem?: MemPage, options?: RunBranchRegressionOptions): Promise<BranchRunResult[]>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { MemPage } from "../mem-page.js";
|
|
2
2
|
import { seedMemStub } from "../mem-stub.js";
|
|
3
|
+
function branchInfo(flow) {
|
|
4
|
+
return flow.__wgBranch;
|
|
5
|
+
}
|
|
3
6
|
/** This Flow's own immediate `.branch()` routes, or `undefined` if it wasn't built by `.branch()`. */
|
|
4
7
|
export function branchRoutes(flow) {
|
|
5
|
-
const
|
|
6
|
-
return
|
|
8
|
+
const info = branchInfo(flow);
|
|
9
|
+
return info ? new Map(Object.entries(info.routes)) : undefined;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Every branch Flow reachable from `flow`, at any depth, deduped by object identity (the same
|
|
10
13
|
* route Flow reused under two tags is only listed once). Doesn't include `flow` itself - only its
|
|
11
|
-
* `.branch()` continuations.
|
|
14
|
+
* `.branch()` continuations. Static - doesn't run anything.
|
|
12
15
|
*/
|
|
13
16
|
export function collectBranchFlows(flow) {
|
|
14
17
|
const out = [];
|
|
@@ -30,43 +33,85 @@ export function collectBranchFlows(flow) {
|
|
|
30
33
|
return out;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
* Runs every
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* gap to code around.
|
|
36
|
+
* Runs every path through `flow`'s `.branch()` tree - by default in a CLONED session per branch
|
|
37
|
+
* point (see this file's own header), so one branch finishing doesn't consume another's chance to
|
|
38
|
+
* run. Each explored branch gets its own `MemPage` (via {@link MemPage.clone}, seeded further with
|
|
39
|
+
* {@link seedMemStub} for anything a route alone requires that the shared prefix didn't already set).
|
|
38
40
|
* @example
|
|
39
41
|
* for (const r of await runBranchRegression(loginFlow, context)) {
|
|
40
|
-
* console.log(r.path, r.status, r.error ??
|
|
42
|
+
* console.log(r.path, r.status, r.error ?? "");
|
|
41
43
|
* }
|
|
42
44
|
*/
|
|
43
|
-
export async function runBranchRegression(flow, context, options) {
|
|
44
|
-
const
|
|
45
|
+
export async function runBranchRegression(flow, context, mem = new MemPage(), options) {
|
|
46
|
+
const cloneSession = options?.cloneSession ?? true;
|
|
45
47
|
const results = [];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
const browser = cloneSession ? context.browser() : null;
|
|
49
|
+
if (cloneSession && !browser) {
|
|
50
|
+
throw new Error("Waygraph runBranchRegression: cloneSession needs context.browser() (a persistent context " +
|
|
51
|
+
"has none) - pass { cloneSession: false } to run the single live path in this context instead.");
|
|
52
|
+
}
|
|
53
|
+
/** `entryPage`, when given, is already positioned (cloned session + navigated) - reused as-is,
|
|
54
|
+
* never replaced by a fresh `ctx.newPage()` (that would throw away the whole point of cloning). */
|
|
55
|
+
async function walk(f, ctx, m, path, entryPage) {
|
|
56
|
+
const info = branchInfo(f);
|
|
57
|
+
if (!info) {
|
|
58
|
+
// Leaf: whatever Blocks this route itself adds - run them to completion from the (possibly
|
|
59
|
+
// cloned, already-positioned) session already in place.
|
|
60
|
+
seedMemStub(m, f);
|
|
61
|
+
const page = entryPage ?? (await ctx.newPage());
|
|
62
|
+
try {
|
|
63
|
+
const result = await f.run(ctx, m, { ...options, page, closeOnFinish: true });
|
|
64
|
+
results.push({ path, status: "ok", result: result });
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
results.push({ path, status: "failed", error: err instanceof Error ? err.message : String(err) });
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
56
70
|
}
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
seedMemStub(m, info.before);
|
|
72
|
+
const page = entryPage ?? (await ctx.newPage());
|
|
73
|
+
let first;
|
|
59
74
|
try {
|
|
60
|
-
|
|
61
|
-
results.push({ path: entry.path, status: "ok", result: result });
|
|
75
|
+
first = (await info.before.run(ctx, m, { ...options, page, closeOnFinish: false }));
|
|
62
76
|
}
|
|
63
77
|
catch (err) {
|
|
64
|
-
results.push({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
results.push({ path, status: "failed", error: err instanceof Error ? err.message : String(err) });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!cloneSession) {
|
|
82
|
+
// Shared-session mode: only the ONE route the live state actually resolved to is reachable -
|
|
83
|
+
// the page has already moved past every other tag.
|
|
84
|
+
const route = info.routes[first.result.__state];
|
|
85
|
+
const tagPath = path ? `${path} > ${first.result.__state}` : first.result.__state;
|
|
86
|
+
if (!route) {
|
|
87
|
+
results.push({ path: tagPath, status: "ok", result: first.result });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await walk(route, ctx, m, tagPath, first.page);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const url = first.page.url();
|
|
94
|
+
const storageState = await ctx.storageState();
|
|
95
|
+
await first.page.close();
|
|
96
|
+
for (const [tag, route] of Object.entries(info.routes)) {
|
|
97
|
+
const branchPath = path ? `${path} > ${tag}` : tag;
|
|
98
|
+
if (!route) {
|
|
99
|
+
results.push({ path: branchPath, status: "ok", result: { __state: tag } });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const branchContext = await browser.newContext({ storageState });
|
|
103
|
+
try {
|
|
104
|
+
const branchPage = await branchContext.newPage();
|
|
105
|
+
// Best-effort: land back on the same page the prefix ended on before this route's own
|
|
106
|
+
// Blocks run - restores cookies/localStorage AND which page was loaded, not just auth state.
|
|
107
|
+
await branchPage.goto(url).catch(() => { });
|
|
108
|
+
await walk(route, branchContext, m.clone(), branchPath, branchPage);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await branchContext.close();
|
|
112
|
+
}
|
|
69
113
|
}
|
|
70
114
|
}
|
|
115
|
+
await walk(flow, context, mem, "");
|
|
71
116
|
return results;
|
|
72
117
|
}
|
|
@@ -134,7 +134,7 @@ function buildBranchedFlow(before, routes) {
|
|
|
134
134
|
}
|
|
135
135
|
return branchFlow.run(context, mem, { ...options, page: first.page, closeOnFinish });
|
|
136
136
|
});
|
|
137
|
-
|
|
137
|
+
return {
|
|
138
138
|
resetSession: before.resetSession,
|
|
139
139
|
...(before.title !== undefined ? { title: before.title } : {}),
|
|
140
140
|
...(before.expectedFailureReason !== undefined ? { expectedFailureReason: before.expectedFailureReason } : {}),
|
|
@@ -142,6 +142,11 @@ function buildBranchedFlow(before, routes) {
|
|
|
142
142
|
...(before.demoPace !== undefined ? { demoPace: before.demoPace } : {}),
|
|
143
143
|
...(before.highlightStyle !== undefined ? { highlightStyle: before.highlightStyle } : {}),
|
|
144
144
|
...(before.memStub !== undefined ? { memStub: before.memStub } : {}),
|
|
145
|
+
// Read by branchRoutes()/branchInfo() (branch-regression.ts) - real, ORDINARY (enumerable)
|
|
146
|
+
// properties, not hidden ones, specifically so a `with*` wrapper's `{ ...flow, ... }` spread
|
|
147
|
+
// (withSessionReset, withTitle, etc.) carries them through instead of silently dropping them -
|
|
148
|
+
// the same class of bug branch()'s missing copyWaygraphRuntime call already was.
|
|
149
|
+
__wgBranch: { before, routes },
|
|
145
150
|
// Static introspection (`waygraph graph`/list): the fixed prefix, plus every branch's own
|
|
146
151
|
// Blocks prefixed by the tag that leads to them - can't know at analysis time which one a real
|
|
147
152
|
// run takes, so this shows all of them rather than none.
|
|
@@ -157,10 +162,6 @@ function buildBranchedFlow(before, routes) {
|
|
|
157
162
|
return buildBranchedFlow(before.modBlockVerify(block, nameOrIndex, newCheck), routes);
|
|
158
163
|
},
|
|
159
164
|
};
|
|
160
|
-
// Non-enumerable so it doesn't show up on a spread/JSON.stringify of the Flow - read by
|
|
161
|
-
// branchRoutes()/collectBranchFlows() (branch-regression.ts), not part of the public Flow shape.
|
|
162
|
-
Object.defineProperty(flow, "__wgBranchRoutes", { value: routes, enumerable: false, configurable: true });
|
|
163
|
-
return flow;
|
|
164
165
|
}
|
|
165
166
|
/**
|
|
166
167
|
* Fluent builder over {@link Engine.defineFlow} - see `map()`'s own doc
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
|
4
4
|
export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
|
|
5
5
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
6
6
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./engine.js";
|
|
7
|
-
export type { BranchEntry, BranchRunResult,
|
|
7
|
+
export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from "./engine/branch-regression.js";
|
|
8
8
|
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, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
|
|
9
9
|
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, BannerPos, BannerUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
|
|
10
10
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./engine.js";
|
package/dist/mem-page.d.ts
CHANGED
|
@@ -67,4 +67,12 @@ export declare class MemPage {
|
|
|
67
67
|
setAll<A, B, C, D>(p1: readonly [MemKey<A>, A], p2: readonly [MemKey<B>, B], p3: readonly [MemKey<C>, C], p4: readonly [MemKey<D>, D]): void;
|
|
68
68
|
setAll<A, B, C, D, E>(p1: readonly [MemKey<A>, A], p2: readonly [MemKey<B>, B], p3: readonly [MemKey<C>, C], p4: readonly [MemKey<D>, D], p5: readonly [MemKey<E>, E]): void;
|
|
69
69
|
setAll<A, B, C, D, E, F>(p1: readonly [MemKey<A>, A], p2: readonly [MemKey<B>, B], p3: readonly [MemKey<C>, C], p4: readonly [MemKey<D>, D], p5: readonly [MemKey<E>, E], p6: readonly [MemKey<F>, F]): void;
|
|
70
|
+
/**
|
|
71
|
+
* An independent copy of every key currently set - a later `.set()` on either the original or the
|
|
72
|
+
* clone never touches the other. For branch-session cloning (`runBranchRegression`): each explored
|
|
73
|
+
* branch gets its own mem, so one branch's writes can't bleed into a sibling that also gets tried
|
|
74
|
+
* from the same snapshot point. Shallow per value (same as `set()` always was - MemPage never
|
|
75
|
+
* owned deep-copying the values themselves, only which keys point to which).
|
|
76
|
+
*/
|
|
77
|
+
clone(): MemPage;
|
|
70
78
|
}
|
package/dist/mem-page.js
CHANGED
|
@@ -68,4 +68,18 @@ export class MemPage {
|
|
|
68
68
|
this.set(k, v);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* An independent copy of every key currently set - a later `.set()` on either the original or the
|
|
73
|
+
* clone never touches the other. For branch-session cloning (`runBranchRegression`): each explored
|
|
74
|
+
* branch gets its own mem, so one branch's writes can't bleed into a sibling that also gets tried
|
|
75
|
+
* from the same snapshot point. Shallow per value (same as `set()` always was - MemPage never
|
|
76
|
+
* owned deep-copying the values themselves, only which keys point to which).
|
|
77
|
+
*/
|
|
78
|
+
clone() {
|
|
79
|
+
const copy = new MemPage();
|
|
80
|
+
for (const [k, v] of this.store) {
|
|
81
|
+
copy.store.set(k, v);
|
|
82
|
+
}
|
|
83
|
+
return copy;
|
|
84
|
+
}
|
|
71
85
|
}
|
|
@@ -7,3 +7,12 @@ import type { PilotHighlightFixtures } from "./fixtures.js";
|
|
|
7
7
|
* Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
|
|
8
8
|
*/
|
|
9
9
|
export declare function overlayFixturesToPhase(f: PilotHighlightFixtures): Promise<StubPhaseResult>;
|
|
10
|
+
/**
|
|
11
|
+
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
12
|
+
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
13
|
+
* fixtures (rings, todos, device, zoom, title/banner) the way `waygraph demo` does, instead of only recording
|
|
14
|
+
* them in the trace.
|
|
15
|
+
*/
|
|
16
|
+
export declare function phaseToOverlayFixtures(phase: StubPhaseResult, holdMs?: number): PilotHighlightFixtures;
|
|
17
|
+
/** One verbose line per authored overlay fixture - same wording on every surface so a log reader (or an AI with no eyes) can tell what was painted. */
|
|
18
|
+
export declare function describeOverlayFixtures(tag: string, f: PilotHighlightFixtures): string[];
|
|
@@ -43,3 +43,63 @@ export async function overlayFixturesToPhase(f) {
|
|
|
43
43
|
};
|
|
44
44
|
return runStubPhase({ name: "overlay-fixtures", stubBefore: lifecycle }, "stubBefore");
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
48
|
+
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
49
|
+
* fixtures (rings, todos, device, zoom, title/banner) the way `waygraph demo` does, instead of only recording
|
|
50
|
+
* them in the trace.
|
|
51
|
+
*/
|
|
52
|
+
export function phaseToOverlayFixtures(phase, holdMs = 6000) {
|
|
53
|
+
const items = phase.todoDock?.groups[0]?.items ?? phase.todos;
|
|
54
|
+
const idx = items.findIndex((t) => t.current) >= 0 ? items.findIndex((t) => t.current) : items.findIndex((t) => !t.done);
|
|
55
|
+
return {
|
|
56
|
+
rings: phase.highlights.map((h) => ({
|
|
57
|
+
selector: h.selector,
|
|
58
|
+
label: h.label ?? "",
|
|
59
|
+
...(h.detail ? { detail: h.detail } : {}),
|
|
60
|
+
...(h.tag ? { tag: h.tag } : {}),
|
|
61
|
+
...(h.tone ? { tone: h.tone } : {}),
|
|
62
|
+
...(h.size ? { size: h.size } : {}),
|
|
63
|
+
...(h.weight ? { weight: h.weight } : {}),
|
|
64
|
+
...(h.color ? { color: h.color } : {}),
|
|
65
|
+
...(h.zoom !== undefined ? { zoom: h.zoom } : {}),
|
|
66
|
+
...(h.zoomOut !== undefined ? { zoomOut: h.zoomOut } : {}),
|
|
67
|
+
...(h.focus ? { focus: true } : {}),
|
|
68
|
+
})),
|
|
69
|
+
...(phase.todoSync === "set" && items.length
|
|
70
|
+
? {
|
|
71
|
+
todos: items.map((t) => t.text),
|
|
72
|
+
todoIndex: idx < 0 ? items.length : idx,
|
|
73
|
+
...(phase.todoDock?.groups[0]?.title ? { todoTitle: phase.todoDock.groups[0].title } : {}),
|
|
74
|
+
...(phase.todoPos ? { todoPos: phase.todoPos } : {}),
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
...(phase.todoDockUi ? { todoDockUi: phase.todoDockUi } : {}),
|
|
78
|
+
...(phase.deviceSync === "set" && phase.device ? { device: phase.device } : {}),
|
|
79
|
+
...(phase.zoom !== undefined ? { zoom: phase.zoom } : {}),
|
|
80
|
+
...(phase.zoomOut !== undefined ? { zoomOut: phase.zoomOut } : {}),
|
|
81
|
+
...(phase.title ? { title: phase.title } : {}),
|
|
82
|
+
...(phase.titlePos ? { titlePos: phase.titlePos } : {}),
|
|
83
|
+
...(phase.bannerUi ? { bannerUi: phase.bannerUi } : {}),
|
|
84
|
+
holdMs,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** One verbose line per authored overlay fixture - same wording on every surface so a log reader (or an AI with no eyes) can tell what was painted. */
|
|
88
|
+
export function describeOverlayFixtures(tag, f) {
|
|
89
|
+
const out = [];
|
|
90
|
+
if (f.title)
|
|
91
|
+
out.push(`${tag} ctx.title(${JSON.stringify(f.title)})${f.titlePos ? ` pos=${f.titlePos}` : ""}`);
|
|
92
|
+
if (f.bannerUi)
|
|
93
|
+
out.push(`${tag} ctx.bannerUi(${JSON.stringify(f.bannerUi)})`);
|
|
94
|
+
if (f.todos?.length)
|
|
95
|
+
out.push(`${tag} ctx.todos n=${f.todos.length} index=${f.todoIndex ?? 0}${f.todoPos ? ` pos=${f.todoPos}` : ""}`);
|
|
96
|
+
if (f.todoDockUi ?? f.todoUi)
|
|
97
|
+
out.push(`${tag} ctx.todoDockUi(${JSON.stringify(f.todoDockUi ?? f.todoUi)})`);
|
|
98
|
+
if (f.device)
|
|
99
|
+
out.push(`${tag} ctx.device(${typeof f.device === "string" ? f.device : f.device.preset ?? "custom"})`);
|
|
100
|
+
if (f.zoom)
|
|
101
|
+
out.push(`${tag} ctx.zoom(${f.zoom})`);
|
|
102
|
+
for (const r of f.rings ?? [])
|
|
103
|
+
out.push(`${tag} ring ${r.selector} tone=${r.tone ?? "planned"} label=${JSON.stringify(r.label)}`);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[21:45:47.529] waygraph pilot: stub.before ctx.title("Signing in · mobile")
|
|
2
|
+
[21:45:47.529] waygraph pilot: stub.before ctx.todos n=6 index=0
|
|
3
|
+
[21:45:47.529] waygraph pilot: stub.before ctx.device(mobile)
|
|
4
|
+
[21:45:47.529] waygraph pilot: stub.before ctx.zoom(1.35)
|
|
5
|
+
[21:45:47.529] waygraph pilot: stub.before ring #user-name tone=info label="Username"
|
|
6
|
+
[21:45:47.633] waygraph pilot: stub.after ctx.todos n=6 index=1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[21:46:29.741] waygraph pilot: stub.before ctx.title("Signing in · mobile")
|
|
2
|
+
[21:46:29.741] waygraph pilot: stub.before ctx.todos n=6 index=0
|
|
3
|
+
[21:46:29.741] waygraph pilot: stub.before ctx.device(mobile)
|
|
4
|
+
[21:46:29.741] waygraph pilot: stub.before ctx.zoom(1.35)
|
|
5
|
+
[21:46:29.741] waygraph pilot: stub.before ring #user-name tone=info label="Username"
|
|
6
|
+
[21:46:29.866] waygraph pilot: stub.after ctx.todos n=6 index=1
|
|
7
|
+
[21:46:31.935] waygraph pilot: stub.before ring #password tone=planned label="Password"
|
|
8
|
+
[21:46:31.998] waygraph pilot: stub.after ctx.todos n=6 index=2
|
|
9
|
+
[21:46:33.773] waygraph pilot: stub.before ring #login-button tone=warning label="Login"
|
|
10
|
+
[21:46:33.961] waygraph pilot: stub.after ctx.title("Inventory · landscape")
|
|
11
|
+
[21:46:33.961] waygraph pilot: stub.after ctx.todos n=6 index=3
|
|
12
|
+
[21:46:33.961] waygraph pilot: stub.after ctx.device(mobile)
|
|
13
|
+
[21:46:33.961] waygraph pilot: stub.after ring .inventory_list tone=success label="Product shelf"
|
|
14
|
+
[21:46:37.277] waygraph pilot: stub.before ctx.title("Pick a product · tablet")
|
|
15
|
+
[21:46:37.277] waygraph pilot: stub.before ctx.todos n=6 index=3
|
|
16
|
+
[21:46:37.277] waygraph pilot: stub.before ctx.device(tablet)
|
|
17
|
+
[21:46:37.277] waygraph pilot: stub.before ctx.zoom(1.35)
|
|
18
|
+
[21:46:37.277] waygraph pilot: stub.before ring div.inventory_item:has(button[data-test="add-to-cart-sauce-labs-backpack"], button[data-test="remove-sauce-labs-backpack"]) .inventory_item_name tone=info label="Sauce Labs Backpack"
|
|
19
|
+
[21:46:37.277] waygraph pilot: stub.before ring [data-test="add-to-cart-sauce-labs-backpack"] tone=success label="Add to cart"
|
|
20
|
+
[21:46:37.375] waygraph pilot: stub.after ctx.title("In the cart")
|
|
21
|
+
[21:46:37.376] waygraph pilot: stub.after ctx.todos n=6 index=4
|
|
22
|
+
[21:46:37.376] waygraph pilot: stub.after ring .shopping_cart_badge tone=success label="Cart badge"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
[21:46:10.717] waygraph pilot: highlight ctx.todos n=3 index=0
|
|
2
|
+
[21:46:10.717] waygraph pilot: highlight ring #user-name tone=planned label="Username"
|
|
3
|
+
[21:46:10.717] waygraph pilot: highlight ring #password tone=info label="Password"
|
|
4
|
+
[21:46:12.774] waygraph pilot: highlight ring #no-such-element-waygraph tone=danger label="Gone"
|
|
5
|
+
[21:46:14.903] waygraph pilot: highlight ring #login-button tone=success label="Login"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[21:46:24.561] waygraph pilot: stub.before ctx.title("Signing in · mobile")
|
|
2
|
+
[21:46:24.562] waygraph pilot: stub.before ctx.todos n=6 index=0
|
|
3
|
+
[21:46:24.562] waygraph pilot: stub.before ctx.device(mobile)
|
|
4
|
+
[21:46:24.562] waygraph pilot: stub.before ctx.zoom(1.35)
|
|
5
|
+
[21:46:24.562] waygraph pilot: stub.before ring #user-name tone=info label="Username"
|
|
6
|
+
[21:46:24.751] waygraph pilot: stub.after ctx.todos n=6 index=1
|
|
7
|
+
[21:46:25.895] waygraph pilot: stub.before ring #password tone=planned label="Password"
|
|
8
|
+
[21:46:25.977] waygraph pilot: stub.after ctx.todos n=6 index=2
|
|
9
|
+
[21:46:27.278] waygraph pilot: stub.before ring #login-button tone=warning label="Login"
|
|
10
|
+
[21:46:27.476] waygraph pilot: stub.after ctx.title("Inventory · landscape")
|
|
11
|
+
[21:46:27.476] waygraph pilot: stub.after ctx.todos n=6 index=3
|
|
12
|
+
[21:46:27.476] waygraph pilot: stub.after ctx.device(mobile)
|
|
13
|
+
[21:46:27.476] waygraph pilot: stub.after ring .inventory_list tone=success label="Product shelf"
|
|
14
|
+
[21:46:28.988] waygraph pilot: stub.before ctx.title("Open cart · landscape")
|
|
15
|
+
[21:46:28.988] waygraph pilot: stub.before ctx.todos n=6 index=5
|
|
16
|
+
[21:46:28.988] waygraph pilot: stub.before ctx.device(mobile)
|
|
17
|
+
[21:46:28.988] waygraph pilot: stub.before ctx.zoom(1.4)
|
|
18
|
+
[21:46:28.988] waygraph pilot: stub.before ring .shopping_cart_link tone=planned label="Cart"
|
|
19
|
+
[21:46:29.115] waygraph pilot: stub.after ctx.title("Your cart · desktop")
|
|
20
|
+
[21:46:29.116] waygraph pilot: stub.after ring .cart_item tone=success label="Cart items"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[21:46:28.846] waygraph pilot: stub.before ctx.title("Signing in · mobile")
|
|
2
|
+
[21:46:28.846] waygraph pilot: stub.before ctx.todos n=6 index=0
|
|
3
|
+
[21:46:28.846] waygraph pilot: stub.before ctx.device(mobile)
|
|
4
|
+
[21:46:28.847] waygraph pilot: stub.before ctx.zoom(1.35)
|
|
5
|
+
[21:46:28.847] waygraph pilot: stub.before ring #user-name tone=info label="Username"
|
|
6
|
+
[21:46:29.003] waygraph pilot: stub.after ctx.todos n=6 index=1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|