waygraph 0.15.21 → 0.15.23
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-explore-run.d.ts +2 -1
- package/dist/auto-explore-run.js +2 -1
- package/dist/auto-session.js +2 -1
- package/dist/cli.js +21 -6067
- package/dist/runner/chain-run.js +102 -0
- package/dist/runner/cursor.js +140 -0
- package/dist/runner/demo-log.js +314 -0
- package/dist/runner/device-stage.js +400 -0
- package/dist/runner/discovery.js +105 -0
- package/dist/runner/index.js +2 -0
- package/dist/runner/interaction-patch.js +353 -0
- package/dist/runner/main.js +397 -0
- package/dist/runner/overlay-css.js +342 -0
- package/dist/runner/overlay-install.js +1301 -0
- package/dist/runner/rings.js +490 -0
- package/dist/runner/seed-mem.js +110 -0
- package/dist/runner/slides.js +272 -0
- package/dist/runner/step-mode.js +776 -0
- package/dist/runner/step-panels.js +723 -0
- package/dist/runner/teardown.js +195 -0
- package/dist/runner/todo-dock.js +142 -0
- package/examples/saucedemo/.waygraph-auto/19b9eee5.log +0 -0
- package/examples/saucedemo/.waygraph-auto/422d4390.log +0 -0
- package/examples/saucedemo/.waygraph-auto/6c9f8e10.log +0 -0
- package/examples/saucedemo/.waygraph-auto/74ce745a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/89477e92.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b7e545f0.log +0 -0
- package/examples/saucedemo/.waygraph-auto/cb2726d7.log +0 -0
- package/package.json +8 -4
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
// Moved verbatim from the former CHAIN_RUNNER_SCRIPT template string in cli.ts (see src/ARCHITECTURE.md).
|
|
2
|
+
// Runs inside the target project's own waygraph copy - keep it dependency-light and self-contained.
|
|
3
|
+
import { instrumentInteractionHighlighting } from "./interaction-patch.js";
|
|
4
|
+
import { applyDevicePhase, applyHighlightStyleDefaults, applyTodoPhase, demoPaceGateMs, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, formatHighlightCaption, hasAuthoredStubAfter, resolveSlides, resolveStepDemoPace, resolveTodoDockUi, runStubPhase } from "../highlights.js";
|
|
5
|
+
import { markStepRunning, resetPageState, teardownOverlay } from "./teardown.js";
|
|
6
|
+
import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
|
|
7
|
+
import { applyDeviceToPage } from "./device-stage.js";
|
|
8
|
+
import { extractVerifyHighlights, presentFailPanel, renderAfterStep, renderBeforeStep } from "./step-panels.js";
|
|
9
|
+
import { probeTodoDocksOnPage } from "./todo-dock.js";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { presentSlides } from "./slides.js";
|
|
12
|
+
|
|
13
|
+
export async function runStepMode(engine, start, end, context, page, mem, resolved, slowMo, title, fastBlockNames, clearSession, baseURL) {
|
|
14
|
+
// A shared, mutable pacing knob the interaction patches read live (per
|
|
15
|
+
// call, not once at setup) - flipped per-block below so one block (e.g.
|
|
16
|
+
// WAYGRAPH_FAST_BLOCKS=login or an FFCompose unit) can run through with
|
|
17
|
+
// none of the overlay's own added dwell while the rest of the chain keeps
|
|
18
|
+
// the full theatrical pace.
|
|
19
|
+
// --fast / WAYGRAPH_DEMO_FAST: shorter auto-next / Next gates ONLY -
|
|
20
|
+
// keeps smooth cursor travel (not akin to waygraph run).
|
|
21
|
+
// FFCompose + WAYGRAPH_FAST_BLOCKS: skipTheater (blitz overlay delays).
|
|
22
|
+
// When the flow has any opaque FF unit and WAYGRAPH_SLOWMO is unset,
|
|
23
|
+
// launch slowMo is 0 so Playwright itself does not re-tax every click
|
|
24
|
+
// inside the FF (that was the "30s login / cursor gone but still slow" bug).
|
|
25
|
+
const demoFast = process.env.WAYGRAPH_DEMO_FAST === "1";
|
|
26
|
+
const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
|
|
27
|
+
const pacing = { gatesFast: demoFast, gatesSlow: false, skipTheater: false, demoPace: "normal" };
|
|
28
|
+
const stubBeforeRef = { current: [] };
|
|
29
|
+
/** Live todo dock for Method fill/click advance (same carry as lastTodoDock). */
|
|
30
|
+
const todoDockRef = { current: undefined };
|
|
31
|
+
/** Live device fixture for touch theater (same carry as lastDevice). */
|
|
32
|
+
const deviceRef = { current: undefined };
|
|
33
|
+
instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef, deviceRef);
|
|
34
|
+
// Force the panel checkbox from this process's flags/env at run start.
|
|
35
|
+
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
36
|
+
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
37
|
+
// session sticks forever and agents cannot flip back with --no-autoplay.
|
|
38
|
+
// Must run on the *demo origin* (after goto), not about:blank - localStorage
|
|
39
|
+
// is origin-scoped.
|
|
40
|
+
const syncAutoplayFromEnv = async () => {
|
|
41
|
+
if (process.env.WAYGRAPH_AUTOPLAY === undefined) return;
|
|
42
|
+
const on = process.env.WAYGRAPH_AUTOPLAY === "1";
|
|
43
|
+
await page
|
|
44
|
+
.evaluate((want) => {
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem("wg-autoplay", want ? "1" : "0");
|
|
47
|
+
} catch {
|
|
48
|
+
/* private mode / blocked storage */
|
|
49
|
+
}
|
|
50
|
+
}, on)
|
|
51
|
+
.catch(() => {});
|
|
52
|
+
};
|
|
53
|
+
await syncAutoplayFromEnv();
|
|
54
|
+
// Some real Blocks (e.g. zsign-all's login.block.ts) call
|
|
55
|
+
// page.setViewportSize({ width: 1280, height: 720 }) inside their own
|
|
56
|
+
// act() - a hardcoded override for THEIR OWN testing consistency, with no
|
|
57
|
+
// idea an interactive human session is watching. Restoring the size
|
|
58
|
+
// AFTER each step (the first attempt at this) was still just repairing
|
|
59
|
+
// damage after the fact, on a delay, per-step - not the real fix. The
|
|
60
|
+
// real fix is to stop the override from ever landing at all: no-op
|
|
61
|
+
// setViewportSize entirely for the duration of step mode. Blocks that
|
|
62
|
+
// call it don't need it to actually do anything here - their own
|
|
63
|
+
// selectors/layouts still work fine at whatever size the real window
|
|
64
|
+
// already is. Authored ctx.device() still uses __wgRealSetViewportSize.
|
|
65
|
+
page.__wgRealSetViewportSize = page.setViewportSize.bind(page);
|
|
66
|
+
page.setViewportSize = async () => {};
|
|
67
|
+
let resolveNext = null;
|
|
68
|
+
// Render-to-gate race: a slide/step panel's Next button is wired to
|
|
69
|
+
// window.__wgNext BEFORE gate() calls waitForNext() (there is at least
|
|
70
|
+
// one more await - the autoplay-sync evaluate - in between on the
|
|
71
|
+
// presentSlides path). A click landing in that small window used to be
|
|
72
|
+
// dropped silently (resolveNext was still null) - the panel then looked
|
|
73
|
+
// "stuck" forever since gate() started listening AFTER the click already
|
|
74
|
+
// fired and no second click ever came. Queue it instead: the very next
|
|
75
|
+
// waitForNext() call picks up a pending click immediately.
|
|
76
|
+
let pendingNext = null;
|
|
77
|
+
await page.exposeFunction("__wgNext", (edits) => {
|
|
78
|
+
if (resolveNext) {
|
|
79
|
+
const r = resolveNext;
|
|
80
|
+
resolveNext = null;
|
|
81
|
+
r(edits);
|
|
82
|
+
} else {
|
|
83
|
+
pendingNext = edits;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const waitForNext = () =>
|
|
87
|
+
new Promise((res) => {
|
|
88
|
+
if (pendingNext !== null) {
|
|
89
|
+
const edits = pendingNext;
|
|
90
|
+
pendingNext = null;
|
|
91
|
+
res(edits);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
resolveNext = res;
|
|
95
|
+
});
|
|
96
|
+
// WAYGRAPH_AUTOPLAY=1 only sets the STARTING checkbox state now - the
|
|
97
|
+
// panel's own "Auto-advance" checkbox can flip it live, mid-run, and a
|
|
98
|
+
// manual click always wins over an in-flight autoplay wait regardless of
|
|
99
|
+
// which way the checkbox is set. That's the hybrid Dan asked for: some
|
|
100
|
+
// steps auto-advance, some get a manual click, toggled as the demo goes,
|
|
101
|
+
// not fixed for the whole run from a single env var.
|
|
102
|
+
const autoplayMs = process.env.WAYGRAPH_AUTOPLAY_MS
|
|
103
|
+
? Number(process.env.WAYGRAPH_AUTOPLAY_MS)
|
|
104
|
+
: demoFast
|
|
105
|
+
? 450
|
|
106
|
+
: 1800;
|
|
107
|
+
const currentAutoplay = () =>
|
|
108
|
+
page
|
|
109
|
+
.evaluate(() => {
|
|
110
|
+
try {
|
|
111
|
+
return localStorage.getItem("wg-autoplay") === "1";
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.catch(() => false);
|
|
117
|
+
const gate = async () => {
|
|
118
|
+
const next = waitForNext();
|
|
119
|
+
// WAYGRAPH_FAST_BLOCKS names a block that should blow past its own
|
|
120
|
+
// gates too, not just skip its interaction dwell - otherwise autoplay
|
|
121
|
+
// still stalls the full autoplayMs admiring a step that intentionally
|
|
122
|
+
// ran too fast to watch.
|
|
123
|
+
const ms = demoPaceGateMs(
|
|
124
|
+
pacing.skipTheater ? "blitz" : pacing.demoPace,
|
|
125
|
+
autoplayMs,
|
|
126
|
+
);
|
|
127
|
+
let elapsed = 0;
|
|
128
|
+
for (;;) {
|
|
129
|
+
// Re-read the checkbox EVERY loop tick, not once up front - a human
|
|
130
|
+
// starting a step in manual mode and then checking "Auto-advance"
|
|
131
|
+
// mid-wait must actually start counting down from that moment, not
|
|
132
|
+
// get stuck on whatever mode was live when gate() was first called.
|
|
133
|
+
const auto = await currentAutoplay();
|
|
134
|
+
const slice = auto
|
|
135
|
+
? Math.min(250, Math.max(50, ms - elapsed))
|
|
136
|
+
: 250; // manual: just a cheap poll tick, waiting on either a click or the box getting checked
|
|
137
|
+
const winner = await Promise.race([
|
|
138
|
+
next.then((edits) => ({ clicked: true, edits })),
|
|
139
|
+
new Promise((res) => setTimeout(() => res({ clicked: false }), slice)),
|
|
140
|
+
]);
|
|
141
|
+
if (winner.clicked) return winner.edits;
|
|
142
|
+
if (!auto) continue;
|
|
143
|
+
elapsed += slice;
|
|
144
|
+
if (elapsed >= ms) return {};
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const allNames = resolved.map((r) => r.block.name);
|
|
148
|
+
const allDescriptions = resolved.map((r) => r.block.description || "");
|
|
149
|
+
// Deduped, in-order list of every real episode this chain touches - for
|
|
150
|
+
// the episode tab bar. Ad hoc block segments (no named Flow) carry no
|
|
151
|
+
// episodeNumber and never appear here, same gate the "Episode N:"
|
|
152
|
+
// heading already used.
|
|
153
|
+
const allEpisodes = [];
|
|
154
|
+
for (const r of resolved) {
|
|
155
|
+
if (r.episodeNumber && !allEpisodes.some((e) => e.episodeNumber === r.episodeNumber)) {
|
|
156
|
+
allEpisodes.push({ episodeNumber: r.episodeNumber, episodeTitle: r.episodeTitle });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let result;
|
|
161
|
+
/** Carry floating todo dock across steps (empty stubBefore must not wipe). */
|
|
162
|
+
let lastTodoDock = undefined;
|
|
163
|
+
/** Author todoDockUi patches (merged); resolved against env each step. */
|
|
164
|
+
let lastTodoDockUiPatch = undefined;
|
|
165
|
+
/** Multi-todo: every dock keyed by todoId (or "_default"), survives navigation. */
|
|
166
|
+
const todoDockById = new Map();
|
|
167
|
+
const pushTodoDockUi = async (patch) => {
|
|
168
|
+
if (patch && typeof patch === "object") {
|
|
169
|
+
lastTodoDockUiPatch = { ...(lastTodoDockUiPatch || {}), ...patch };
|
|
170
|
+
}
|
|
171
|
+
const ui = resolveTodoDockUi(lastTodoDockUiPatch);
|
|
172
|
+
await page
|
|
173
|
+
.evaluate((u) => {
|
|
174
|
+
window.__wgTodoDockUi = u;
|
|
175
|
+
}, ui)
|
|
176
|
+
.catch(() => {});
|
|
177
|
+
};
|
|
178
|
+
const dockRegistryKey = (dock) =>
|
|
179
|
+
dock && dock.id && String(dock.id).trim() ? String(dock.id).trim() : "_default";
|
|
180
|
+
const rememberTodoDock = (dock, sync, parallel) => {
|
|
181
|
+
if (sync === "clear") {
|
|
182
|
+
todoDockById.clear();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (sync === "set" && dock) {
|
|
186
|
+
if (!parallel) todoDockById.clear();
|
|
187
|
+
todoDockById.set(dockRegistryKey(dock), dock);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// keep: refresh / re-seed so syncAllTodoDocks never sees an empty map
|
|
191
|
+
// while lastTodoDock still carries (nav wipe + mid-act advance).
|
|
192
|
+
if (sync === "keep" && dock) {
|
|
193
|
+
todoDockById.set(dockRegistryKey(dock), dock);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const syncAllTodoDocks = async (opts) => {
|
|
197
|
+
const docks = [...todoDockById.values()];
|
|
198
|
+
const parallel = !!(opts && opts.parallel);
|
|
199
|
+
if (!docks.length) {
|
|
200
|
+
// Empty registry: do NOT clear the DOM. renderBefore/After may have
|
|
201
|
+
// just painted from lastTodoDock (keep). Clearing here made todos
|
|
202
|
+
// vanish until the next ctx.todos() set (PIA #15).
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
await page
|
|
206
|
+
.evaluate(
|
|
207
|
+
({ list, parallel }) => {
|
|
208
|
+
if (!window.__wgSyncTodos) return;
|
|
209
|
+
for (let i = 0; i < list.length; i++) {
|
|
210
|
+
const d = list[i];
|
|
211
|
+
window.__wgSyncTodos({
|
|
212
|
+
sync: "set",
|
|
213
|
+
dock: d,
|
|
214
|
+
todoId: (d && d.id) || null,
|
|
215
|
+
pos: (d && d.pos) || null,
|
|
216
|
+
// First dock replaces; later ones keep siblings when parallel.
|
|
217
|
+
replace: !(parallel && i > 0),
|
|
218
|
+
parallel: parallel && i > 0,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{ list: docks, parallel },
|
|
223
|
+
)
|
|
224
|
+
.catch(() => {});
|
|
225
|
+
};
|
|
226
|
+
/** Carry device / touch fixture across steps (omit = keep, like todos). */
|
|
227
|
+
let lastDevice = undefined;
|
|
228
|
+
for (let i = 0; i < resolved.length; i++) {
|
|
229
|
+
const r = resolved[i];
|
|
230
|
+
// The block breadcrumb is scoped to THIS step's own episode, not the
|
|
231
|
+
// whole chain - within episode 2, step 1 should read as "1st of 2,"
|
|
232
|
+
// not "6th of 7." Falls back to the whole chain when this block has no
|
|
233
|
+
// episode (an ad hoc chain with no named Flows), unchanged from before.
|
|
234
|
+
const episodeBlockIndices = r.episodeNumber
|
|
235
|
+
? resolved.reduce((acc, x, idx) => {
|
|
236
|
+
if (x.episodeNumber === r.episodeNumber) acc.push(idx);
|
|
237
|
+
return acc;
|
|
238
|
+
}, [])
|
|
239
|
+
: resolved.map((_x, idx) => idx);
|
|
240
|
+
const moduleNames = episodeBlockIndices.map((idx) => allNames[idx]);
|
|
241
|
+
const moduleDescriptions = episodeBlockIndices.map((idx) => allDescriptions[idx]);
|
|
242
|
+
const moduleIndex = episodeBlockIndices.indexOf(i);
|
|
243
|
+
// Where a "Retry Episode" click (see the catch block below) rewinds to -
|
|
244
|
+
// the current episode's own first block, or the whole chain's first
|
|
245
|
+
// block when there's no real episode (episodeBlockIndices degrades to
|
|
246
|
+
// the full resolved array in that case, so this is just 0).
|
|
247
|
+
const episodeStartIndex = episodeBlockIndices[0];
|
|
248
|
+
// True only on an episode's own first block - the one moment worth a
|
|
249
|
+
// gentle "you're here now" signal, not every step inside it.
|
|
250
|
+
const justEnteredEpisode = moduleIndex === 0 && r.episodeNumber !== undefined;
|
|
251
|
+
// Seed THIS segment's own json payload now, not earlier - see the long
|
|
252
|
+
// comment where seedMem closures are built, in main()'s resolution
|
|
253
|
+
// loop. Must run before "keys" below reads mem.get() for the panel
|
|
254
|
+
// display, and before this segment's own first block executes.
|
|
255
|
+
if (r.seedMem) r.seedMem();
|
|
256
|
+
// Reset session state before this block if EITHER: the blanket
|
|
257
|
+
// WAYGRAPH_CLEAR_SESSION=1 override is set (a manual, always-on-after-
|
|
258
|
+
// the-first-block stopgap for ad hoc block chains with no real Flow
|
|
259
|
+
// object), OR this specific block is a Flow's own declared entry point
|
|
260
|
+
// (defineFlow(...) wrapped in withSessionReset) reached via a
|
|
261
|
+
// multi-flow chain spec - the proper, opt-in-per-Flow mechanism. Never
|
|
262
|
+
// resets before the very first block - there's no prior state yet.
|
|
263
|
+
//
|
|
264
|
+
// Done HERE, before this step's own "before" panel is even shown - not
|
|
265
|
+
// after the human clicks "Run this step" - so the panel for the new
|
|
266
|
+
// episode appears over an already-fresh page, not the PREVIOUS
|
|
267
|
+
// episode's stale final page (e.g. still showing /dashboard) with the
|
|
268
|
+
// new episode's panel merely floating on top of it ("overlapping",
|
|
269
|
+
// Dan's own word for this). The explicit re-navigate (not just
|
|
270
|
+
// clearing cookies/storage) is what actually makes the page itself
|
|
271
|
+
// look fresh again - clearing storage alone doesn't change what's
|
|
272
|
+
// still rendered on screen.
|
|
273
|
+
if ((clearSession || r.resetSession) && i > 0) {
|
|
274
|
+
await resetPageState(context, page, baseURL);
|
|
275
|
+
// clear() wiped origin storage - re-apply CLI autoplay default.
|
|
276
|
+
await syncAutoplayFromEnv();
|
|
277
|
+
}
|
|
278
|
+
// Episode pace: block > flow > CLI --fast (only when no authored pace).
|
|
279
|
+
// Authoring wins: withDemoPace("slow"|2|4500) keeps slow dwell even under --fast.
|
|
280
|
+
const stepPace = resolveStepDemoPace({
|
|
281
|
+
blockPace: r.block.demoPace,
|
|
282
|
+
flowPace: r.demoPace,
|
|
283
|
+
fastForward: !!r.block.fastForward,
|
|
284
|
+
wasFastForward: !!r.wasFastForward,
|
|
285
|
+
});
|
|
286
|
+
const authoredPace =
|
|
287
|
+
(r.block.demoPace != null && r.block.demoPace !== "") ||
|
|
288
|
+
(r.demoPace != null && r.demoPace !== "");
|
|
289
|
+
const cliFast = demoFast || fastBlockNames.has(r.block.name);
|
|
290
|
+
pacing.demoPace =
|
|
291
|
+
stepPace === "normal" && cliFast && !authoredPace
|
|
292
|
+
? fastBlockNames.has(r.block.name)
|
|
293
|
+
? "blitz"
|
|
294
|
+
: "fast"
|
|
295
|
+
: stepPace;
|
|
296
|
+
pacing.gatesFast =
|
|
297
|
+
demoPaceIsFast(pacing.demoPace) ||
|
|
298
|
+
!!r.block.fastForward ||
|
|
299
|
+
!!r.wasFastForward ||
|
|
300
|
+
(!authoredPace && demoFast);
|
|
301
|
+
pacing.gatesSlow = demoPaceIsSlow(pacing.demoPace);
|
|
302
|
+
pacing.skipTheater =
|
|
303
|
+
demoPaceIsBlitz(pacing.demoPace) ||
|
|
304
|
+
fastBlockNames.has(r.block.name) ||
|
|
305
|
+
!!r.block.fastForward ||
|
|
306
|
+
!!r.wasFastForward;
|
|
307
|
+
const fixtures = r.highlightFixtures;
|
|
308
|
+
const flowStyle = r.highlightStyle;
|
|
309
|
+
const stubBeforePhase = await runStubPhase(r.block, "stubBefore", { fixtures, mem });
|
|
310
|
+
if (stubBeforePhase.todoDockUi) {
|
|
311
|
+
await pushTodoDockUi(stubBeforePhase.todoDockUi);
|
|
312
|
+
} else {
|
|
313
|
+
await pushTodoDockUi(undefined);
|
|
314
|
+
}
|
|
315
|
+
stubBeforeRef.current = stubBeforePhase.highlights.map((h) =>
|
|
316
|
+
applyHighlightStyleDefaults(h, flowStyle),
|
|
317
|
+
);
|
|
318
|
+
logStubPhaseFixtures("before", {
|
|
319
|
+
...stubBeforePhase,
|
|
320
|
+
highlights: stubBeforeRef.current.map((h) => ({
|
|
321
|
+
...h,
|
|
322
|
+
label: formatHighlightCaption(h),
|
|
323
|
+
})),
|
|
324
|
+
});
|
|
325
|
+
const appliedBefore = applyTodoPhase(lastTodoDock, {
|
|
326
|
+
todoSync: stubBeforePhase.todoSync,
|
|
327
|
+
todoDock: stubBeforePhase.todoDock,
|
|
328
|
+
todos: stubBeforePhase.todos,
|
|
329
|
+
todoPos: stubBeforePhase.todoPos,
|
|
330
|
+
});
|
|
331
|
+
lastTodoDock = appliedBefore.dock;
|
|
332
|
+
todoDockRef.current = lastTodoDock;
|
|
333
|
+
rememberTodoDock(appliedBefore.dock, appliedBefore.sync, !!stubBeforePhase.todoParallel);
|
|
334
|
+
const appliedDeviceBefore = applyDevicePhase(lastDevice, {
|
|
335
|
+
deviceSync: stubBeforePhase.deviceSync,
|
|
336
|
+
device: stubBeforePhase.device,
|
|
337
|
+
});
|
|
338
|
+
lastDevice = appliedDeviceBefore.device;
|
|
339
|
+
deviceRef.current = lastDevice;
|
|
340
|
+
if (appliedDeviceBefore.sync !== "keep" || lastDevice) {
|
|
341
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceBefore.sync);
|
|
342
|
+
}
|
|
343
|
+
const stubBeforeTodos =
|
|
344
|
+
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
345
|
+
stubBeforePhase.todos ||
|
|
346
|
+
[];
|
|
347
|
+
const overlayTitle =
|
|
348
|
+
(stubBeforePhase.title && String(stubBeforePhase.title).trim()) ||
|
|
349
|
+
(r.episodeTitle && String(r.episodeTitle).trim()) ||
|
|
350
|
+
title ||
|
|
351
|
+
"waygraph demo";
|
|
352
|
+
const overlayTodoPos =
|
|
353
|
+
(lastTodoDock && lastTodoDock.pos) || stubBeforePhase.todoPos || undefined;
|
|
354
|
+
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
355
|
+
const autoNow = await currentAutoplay();
|
|
356
|
+
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
357
|
+
const forceCollapsed =
|
|
358
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
359
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
360
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
361
|
+
? true
|
|
362
|
+
: undefined;
|
|
363
|
+
const requires = r.block.requires ?? [];
|
|
364
|
+
const keys = requires.map((k) => {
|
|
365
|
+
let value = "<not yet set>";
|
|
366
|
+
try {
|
|
367
|
+
value = JSON.stringify(mem.get(k));
|
|
368
|
+
} catch {
|
|
369
|
+
// not written yet - shown as a placeholder, not a crash
|
|
370
|
+
}
|
|
371
|
+
return { name: k.name, value };
|
|
372
|
+
});
|
|
373
|
+
const paceSpeak = paceSpeakFields(pacing.demoPace, autoplayMs);
|
|
374
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
375
|
+
demoLog(
|
|
376
|
+
"step " +
|
|
377
|
+
(i + 1) +
|
|
378
|
+
"/" +
|
|
379
|
+
resolved.length +
|
|
380
|
+
" " +
|
|
381
|
+
r.block.name +
|
|
382
|
+
" · " +
|
|
383
|
+
paceSpeak.paceLabel +
|
|
384
|
+
(r.episodeNumber ? " · episode " + r.episodeNumber : ""),
|
|
385
|
+
);
|
|
386
|
+
demoLog(
|
|
387
|
+
" gate~" +
|
|
388
|
+
autoplayMs +
|
|
389
|
+
"ms skipTheater=" +
|
|
390
|
+
!!pacing.skipTheater +
|
|
391
|
+
" gatesFast=" +
|
|
392
|
+
!!pacing.gatesFast,
|
|
393
|
+
);
|
|
394
|
+
demoLog(" " + summarizeDevice(lastDevice));
|
|
395
|
+
const phaseZoom =
|
|
396
|
+
stubBeforePhase.zoom != null && Number(stubBeforePhase.zoom) > 0
|
|
397
|
+
? Number(stubBeforePhase.zoom)
|
|
398
|
+
: null;
|
|
399
|
+
const phaseZoomOut =
|
|
400
|
+
stubBeforePhase.zoomOut !== undefined ? !!stubBeforePhase.zoomOut : null;
|
|
401
|
+
demoLog(
|
|
402
|
+
" zoom=" +
|
|
403
|
+
(phaseZoom != null ? phaseZoom + "x" : "default") +
|
|
404
|
+
(phaseZoomOut != null ? " zoomOut=" + phaseZoomOut : "") +
|
|
405
|
+
" (StubCtx / API; live chip top-left when ring zoom >1)",
|
|
406
|
+
);
|
|
407
|
+
logTodoDockFull(lastTodoDock, " before");
|
|
408
|
+
demoLog(" todoSync=" + appliedBefore.sync);
|
|
409
|
+
}
|
|
410
|
+
await renderBeforeStep(page, {
|
|
411
|
+
index: i,
|
|
412
|
+
total: resolved.length,
|
|
413
|
+
blockName: r.block.name,
|
|
414
|
+
description: r.block.description || "",
|
|
415
|
+
keys,
|
|
416
|
+
allNames: moduleNames,
|
|
417
|
+
allDescriptions: moduleDescriptions,
|
|
418
|
+
moduleIndex,
|
|
419
|
+
allEpisodes,
|
|
420
|
+
justEnteredEpisode,
|
|
421
|
+
title: overlayTitle,
|
|
422
|
+
todoPos: overlayTodoPos,
|
|
423
|
+
episodeNumber: r.episodeNumber,
|
|
424
|
+
episodeTitle: r.episodeTitle,
|
|
425
|
+
stepperMode,
|
|
426
|
+
forceCollapsed,
|
|
427
|
+
todos: stubBeforeTodos,
|
|
428
|
+
todoDock: lastTodoDock,
|
|
429
|
+
todoSync: appliedBefore.sync,
|
|
430
|
+
todoId: lastTodoDock && lastTodoDock.id,
|
|
431
|
+
...paceSpeak,
|
|
432
|
+
});
|
|
433
|
+
// Re-paint dock(s) - renderBeforeStep syncs the active one; this enforces replace.
|
|
434
|
+
await syncAllTodoDocks({ parallel: !!stubBeforePhase.todoParallel });
|
|
435
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
436
|
+
const docks = await probeTodoDocksOnPage(page);
|
|
437
|
+
if (!docks.length && lastTodoDock) {
|
|
438
|
+
demoLog(" WARN todos authored but dock-dom empty (not painted?)");
|
|
439
|
+
} else if (docks.length) {
|
|
440
|
+
demoLog(
|
|
441
|
+
" dock-dom " +
|
|
442
|
+
docks
|
|
443
|
+
.map(
|
|
444
|
+
(d) =>
|
|
445
|
+
d.key +
|
|
446
|
+
"@" +
|
|
447
|
+
d.pos +
|
|
448
|
+
" " +
|
|
449
|
+
d.left +
|
|
450
|
+
"," +
|
|
451
|
+
d.top +
|
|
452
|
+
" " +
|
|
453
|
+
d.w +
|
|
454
|
+
"x" +
|
|
455
|
+
d.h +
|
|
456
|
+
(d.onScreen ? "" : " OFFSCREEN") +
|
|
457
|
+
" n=" +
|
|
458
|
+
d.n +
|
|
459
|
+
(d.current ? ' cur="' + d.current + '"' : ""),
|
|
460
|
+
)
|
|
461
|
+
.join(" | "),
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const edits = await gate();
|
|
466
|
+
for (const k of requires) {
|
|
467
|
+
if (edits[k.name] !== undefined) {
|
|
468
|
+
try {
|
|
469
|
+
mem.set(k, JSON.parse(edits[k.name]));
|
|
470
|
+
} catch {
|
|
471
|
+
// left as-authored if the human's edit isn't valid JSON
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
|
|
476
|
+
await markStepRunning(page, {});
|
|
477
|
+
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
478
|
+
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
479
|
+
// (not only on regular Block clicks).
|
|
480
|
+
const navClick = r.block.__waygraphNavClick;
|
|
481
|
+
if (r.block.__waygraphKind === "nav" && navClick !== undefined) {
|
|
482
|
+
await page
|
|
483
|
+
.evaluate((label) => {
|
|
484
|
+
window.__wgPendingNavClickLabel = label;
|
|
485
|
+
}, "nav: " + (r.block.name || "click"))
|
|
486
|
+
.catch(() => {});
|
|
487
|
+
} else {
|
|
488
|
+
await page
|
|
489
|
+
.evaluate(() => {
|
|
490
|
+
delete window.__wgPendingNavClickLabel;
|
|
491
|
+
})
|
|
492
|
+
.catch(() => {});
|
|
493
|
+
}
|
|
494
|
+
const stepFlow = engine.defineFlow([start, r.block, end]);
|
|
495
|
+
// { closeOnFinish: false } makes Flow.run return { result, page }, not
|
|
496
|
+
// the plain checkpoint - destructure it, don't treat the wrapper as the
|
|
497
|
+
// checkpoint itself (caught via the standalone verify script: this used
|
|
498
|
+
// to serialize the whole { result, page } object into the panel/log).
|
|
499
|
+
let stepOutcome;
|
|
500
|
+
try {
|
|
501
|
+
stepOutcome = await stepFlow.run(context, mem, { page, closeOnFinish: false });
|
|
502
|
+
} catch (err) {
|
|
503
|
+
await page
|
|
504
|
+
.evaluate(() => {
|
|
505
|
+
delete window.__wgPendingNavClickLabel;
|
|
506
|
+
})
|
|
507
|
+
.catch(() => {});
|
|
508
|
+
// stubOnError + error/expected panel BEFORE acknowledging (PIA RFC).
|
|
509
|
+
const errEdits = await presentFailPanel(page, {
|
|
510
|
+
block: r.block,
|
|
511
|
+
fixtures: r.highlightFixtures,
|
|
512
|
+
highlightStyle: r.highlightStyle,
|
|
513
|
+
error: err,
|
|
514
|
+
index: i,
|
|
515
|
+
total: resolved.length,
|
|
516
|
+
blockName: r.block.name,
|
|
517
|
+
message: err && err.message ? err.message : String(err),
|
|
518
|
+
allNames: moduleNames,
|
|
519
|
+
allDescriptions: moduleDescriptions,
|
|
520
|
+
moduleIndex,
|
|
521
|
+
allEpisodes,
|
|
522
|
+
title,
|
|
523
|
+
episodeNumber: r.episodeNumber,
|
|
524
|
+
episodeTitle: r.episodeTitle,
|
|
525
|
+
expectedFailureReason: r.expectedFailureReason,
|
|
526
|
+
stepperMode,
|
|
527
|
+
gatesFast: pacing.gatesFast,
|
|
528
|
+
demoPace: pacing.demoPace,
|
|
529
|
+
gate,
|
|
530
|
+
mem,
|
|
531
|
+
});
|
|
532
|
+
if (errEdits && errEdits.__wgRetry) {
|
|
533
|
+
await resetPageState(context, page, baseURL);
|
|
534
|
+
await syncAutoplayFromEnv();
|
|
535
|
+
i = episodeStartIndex - 1;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
// withExpectedFailure episode (e.g. locked_out_user) - throwing here is
|
|
539
|
+
// the demo working, not a broken run.
|
|
540
|
+
if (r.expectedFailureReason) {
|
|
541
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
542
|
+
console.log(
|
|
543
|
+
"waygraph: expected failure on " + r.block.name + " - " + r.expectedFailureReason,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
throw err;
|
|
549
|
+
}
|
|
550
|
+
await page
|
|
551
|
+
.evaluate(() => {
|
|
552
|
+
delete window.__wgPendingNavClickLabel;
|
|
553
|
+
})
|
|
554
|
+
.catch(() => {});
|
|
555
|
+
result = stepOutcome.result;
|
|
556
|
+
// Mid-act Method fill/click advances live on todoDockRef - fold that
|
|
557
|
+
// back into lastTodoDock before stubAfter / next-block keep, or the
|
|
558
|
+
// next step reverts to the pre-act (blank / index-0) checklist.
|
|
559
|
+
if (todoDockRef.current) lastTodoDock = todoDockRef.current;
|
|
560
|
+
// withExpectedFailure last block that SUCCEEDS on the intentional fail
|
|
561
|
+
// branch (e.g. submit-login -> LoginPage + error banner). Still show
|
|
562
|
+
// stubOnError rings + amber "expected outcome" panel - branching no
|
|
563
|
+
// longer throws, but the demo must not skip the educational overlay.
|
|
564
|
+
if (r.expectedFailureReason) {
|
|
565
|
+
const errEdits = await presentFailPanel(page, {
|
|
566
|
+
block: r.block,
|
|
567
|
+
fixtures: r.highlightFixtures,
|
|
568
|
+
highlightStyle: r.highlightStyle,
|
|
569
|
+
error: undefined,
|
|
570
|
+
out: result,
|
|
571
|
+
index: i,
|
|
572
|
+
total: resolved.length,
|
|
573
|
+
blockName: r.block.name,
|
|
574
|
+
message:
|
|
575
|
+
"Resolved " +
|
|
576
|
+
(result && result.__state ? result.__state : JSON.stringify(result)) +
|
|
577
|
+
" (expected failure path)",
|
|
578
|
+
allNames: moduleNames,
|
|
579
|
+
allDescriptions: moduleDescriptions,
|
|
580
|
+
moduleIndex,
|
|
581
|
+
allEpisodes,
|
|
582
|
+
title,
|
|
583
|
+
episodeNumber: r.episodeNumber,
|
|
584
|
+
episodeTitle: r.episodeTitle,
|
|
585
|
+
expectedFailureReason: r.expectedFailureReason,
|
|
586
|
+
stepperMode,
|
|
587
|
+
gatesFast: pacing.gatesFast,
|
|
588
|
+
demoPace: pacing.demoPace,
|
|
589
|
+
gate,
|
|
590
|
+
mem,
|
|
591
|
+
});
|
|
592
|
+
if (errEdits && errEdits.__wgRetry) {
|
|
593
|
+
await resetPageState(context, page, baseURL);
|
|
594
|
+
await syncAutoplayFromEnv();
|
|
595
|
+
i = episodeStartIndex - 1;
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
599
|
+
console.log(
|
|
600
|
+
"waygraph: expected failure on " + r.block.name + " - " + r.expectedFailureReason,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
const fixturesAfter = r.highlightFixtures;
|
|
606
|
+
const slides = resolveSlides(r.block, { out: result, fixtures: fixturesAfter }).map((s) =>
|
|
607
|
+
applyHighlightStyleDefaults(s, r.highlightStyle),
|
|
608
|
+
);
|
|
609
|
+
if (slides.length > 0) {
|
|
610
|
+
await presentSlides(page, slides, gate, {
|
|
611
|
+
title,
|
|
612
|
+
blockName: r.block.name,
|
|
613
|
+
fast: pacing.gatesFast,
|
|
614
|
+
pace: pacing.demoPace,
|
|
615
|
+
episodeNumber: r.episodeNumber,
|
|
616
|
+
episodeTitle: r.episodeTitle,
|
|
617
|
+
autoplayMs,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
let highlights;
|
|
621
|
+
let stubAfterTodos = [];
|
|
622
|
+
let afterOverlayTitle = overlayTitle;
|
|
623
|
+
let afterTodoPos = overlayTodoPos;
|
|
624
|
+
let appliedAfter = { dock: lastTodoDock, sync: "keep" };
|
|
625
|
+
let appliedDeviceAfter = { device: lastDevice, sync: "keep" };
|
|
626
|
+
let afterTodoParallel = false;
|
|
627
|
+
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
628
|
+
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
629
|
+
out: result,
|
|
630
|
+
fixtures: fixturesAfter,
|
|
631
|
+
mem,
|
|
632
|
+
});
|
|
633
|
+
if (afterPhase.todoDockUi) {
|
|
634
|
+
await pushTodoDockUi(afterPhase.todoDockUi);
|
|
635
|
+
}
|
|
636
|
+
logStubPhaseFixtures("after", {
|
|
637
|
+
...afterPhase,
|
|
638
|
+
highlights: (afterPhase.highlights || []).map((h) => {
|
|
639
|
+
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
640
|
+
return { ...styled, label: formatHighlightCaption(styled) };
|
|
641
|
+
}),
|
|
642
|
+
});
|
|
643
|
+
afterTodoParallel = !!afterPhase.todoParallel;
|
|
644
|
+
appliedAfter = applyTodoPhase(lastTodoDock, {
|
|
645
|
+
todoSync: afterPhase.todoSync,
|
|
646
|
+
todoDock: afterPhase.todoDock,
|
|
647
|
+
todos: afterPhase.todos,
|
|
648
|
+
todoPos: afterPhase.todoPos,
|
|
649
|
+
});
|
|
650
|
+
lastTodoDock = appliedAfter.dock;
|
|
651
|
+
todoDockRef.current = lastTodoDock;
|
|
652
|
+
rememberTodoDock(appliedAfter.dock, appliedAfter.sync, afterTodoParallel);
|
|
653
|
+
appliedDeviceAfter = applyDevicePhase(lastDevice, {
|
|
654
|
+
deviceSync: afterPhase.deviceSync,
|
|
655
|
+
device: afterPhase.device,
|
|
656
|
+
});
|
|
657
|
+
lastDevice = appliedDeviceAfter.device;
|
|
658
|
+
deviceRef.current = lastDevice;
|
|
659
|
+
if (appliedDeviceAfter.sync !== "keep" || (appliedDeviceAfter.sync === "set" && lastDevice)) {
|
|
660
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceAfter.sync);
|
|
661
|
+
}
|
|
662
|
+
stubAfterTodos =
|
|
663
|
+
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
664
|
+
afterPhase.todos ||
|
|
665
|
+
[];
|
|
666
|
+
if (afterPhase.title && String(afterPhase.title).trim()) {
|
|
667
|
+
afterOverlayTitle = String(afterPhase.title).trim();
|
|
668
|
+
}
|
|
669
|
+
if (afterPhase.todoPos) afterTodoPos = afterPhase.todoPos;
|
|
670
|
+
if (lastTodoDock && lastTodoDock.pos) afterTodoPos = lastTodoDock.pos;
|
|
671
|
+
highlights = afterPhase.highlights.map((h) => {
|
|
672
|
+
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
673
|
+
return {
|
|
674
|
+
selector: styled.selector,
|
|
675
|
+
label: formatHighlightCaption(styled),
|
|
676
|
+
duration: styled.duration,
|
|
677
|
+
fastMode: styled.fastMode,
|
|
678
|
+
tone: styled.tone,
|
|
679
|
+
size: styled.size,
|
|
680
|
+
weight: styled.weight,
|
|
681
|
+
zoom: styled.zoom,
|
|
682
|
+
zoomOut: styled.zoomOut,
|
|
683
|
+
focus: !!styled.focus,
|
|
684
|
+
tone: styled.tone,
|
|
685
|
+
color: styled.color,
|
|
686
|
+
detail: styled.detail,
|
|
687
|
+
};
|
|
688
|
+
});
|
|
689
|
+
} else {
|
|
690
|
+
highlights = extractVerifyHighlights(r.block, result.__state).map((h) =>
|
|
691
|
+
applyHighlightStyleDefaults(h, r.highlightStyle),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
await renderAfterStep(page, {
|
|
695
|
+
index: i,
|
|
696
|
+
total: resolved.length,
|
|
697
|
+
blockName: r.block.name,
|
|
698
|
+
result,
|
|
699
|
+
resultTag: JSON.stringify(result),
|
|
700
|
+
highlights,
|
|
701
|
+
gatesFast: pacing.gatesFast,
|
|
702
|
+
pace: pacing.demoPace,
|
|
703
|
+
...paceSpeakFields(pacing.demoPace, autoplayMs),
|
|
704
|
+
isLast: i === resolved.length - 1,
|
|
705
|
+
allNames: moduleNames,
|
|
706
|
+
allDescriptions: moduleDescriptions,
|
|
707
|
+
moduleIndex,
|
|
708
|
+
allEpisodes,
|
|
709
|
+
title: afterOverlayTitle,
|
|
710
|
+
todoPos: afterTodoPos,
|
|
711
|
+
episodeNumber: r.episodeNumber,
|
|
712
|
+
episodeTitle: r.episodeTitle,
|
|
713
|
+
stepperMode,
|
|
714
|
+
todos: stubAfterTodos,
|
|
715
|
+
todoDock: lastTodoDock,
|
|
716
|
+
todoSync: appliedAfter.sync,
|
|
717
|
+
todoId: lastTodoDock && lastTodoDock.id,
|
|
718
|
+
todoDockRef,
|
|
719
|
+
forceCollapsed:
|
|
720
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
721
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
722
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
723
|
+
? true
|
|
724
|
+
: undefined,
|
|
725
|
+
});
|
|
726
|
+
await syncAllTodoDocks({ parallel: afterTodoParallel });
|
|
727
|
+
// Ring cycle / stubAfter may have advanced the dock - persist for next block.
|
|
728
|
+
if (todoDockRef.current) {
|
|
729
|
+
lastTodoDock = todoDockRef.current;
|
|
730
|
+
rememberTodoDock(todoDockRef.current, "set", afterTodoParallel);
|
|
731
|
+
}
|
|
732
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
733
|
+
demoLog(
|
|
734
|
+
" after " +
|
|
735
|
+
summarizeDevice(lastDevice) +
|
|
736
|
+
" deviceSync=" +
|
|
737
|
+
appliedDeviceAfter.sync,
|
|
738
|
+
);
|
|
739
|
+
logTodoDockFull(lastTodoDock, " after");
|
|
740
|
+
demoLog(" after todoSync=" + appliedAfter.sync);
|
|
741
|
+
const docksAfter = await probeTodoDocksOnPage(page);
|
|
742
|
+
if (!docksAfter.length && lastTodoDock && appliedAfter.sync !== "clear") {
|
|
743
|
+
demoLog(" WARN after: todos authored but dock-dom empty");
|
|
744
|
+
} else if (docksAfter.length) {
|
|
745
|
+
demoLog(
|
|
746
|
+
" after dock-dom " +
|
|
747
|
+
docksAfter
|
|
748
|
+
.map(
|
|
749
|
+
(d) =>
|
|
750
|
+
d.key +
|
|
751
|
+
"@" +
|
|
752
|
+
d.pos +
|
|
753
|
+
" " +
|
|
754
|
+
d.left +
|
|
755
|
+
"," +
|
|
756
|
+
d.top +
|
|
757
|
+
" " +
|
|
758
|
+
d.w +
|
|
759
|
+
"x" +
|
|
760
|
+
d.h +
|
|
761
|
+
(d.onScreen ? "" : " OFFSCREEN") +
|
|
762
|
+
" n=" +
|
|
763
|
+
d.n +
|
|
764
|
+
(d.current ? ' cur="' + d.current + '"' : ""),
|
|
765
|
+
)
|
|
766
|
+
.join(" | "),
|
|
767
|
+
);
|
|
768
|
+
} else if (appliedAfter.sync === "clear") {
|
|
769
|
+
demoLog(" after dock-dom cleared (expected)");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
await gate();
|
|
773
|
+
}
|
|
774
|
+
await teardownOverlay(page);
|
|
775
|
+
return result;
|
|
776
|
+
}
|