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,397 @@
|
|
|
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 { findBlock, findFlow, parseChainSpec } from "./discovery.js";
|
|
4
|
+
import { seedMemForBlock, seedMemForFlow } from "./seed-mem.js";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { mkdirSync } from "node:fs";
|
|
7
|
+
import { isExpectedChainFailure, parseVideoViewport, runChainedFlows, runJsonReportMode } from "./chain-run.js";
|
|
8
|
+
import { runStepMode } from "./step-mode.js";
|
|
9
|
+
import { connect } from "../types.js";
|
|
10
|
+
import { MemPage } from "../mem-page.js";
|
|
11
|
+
import { Engine, start, end, chainFlow, isFastForwardBlock } from "../engine.js";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const projectDir = process.argv[2];
|
|
15
|
+
const spec = process.argv[3];
|
|
16
|
+
const mem = new MemPage();
|
|
17
|
+
// Computed early - JSON-report mode's stdout is meant to be ONE parseable
|
|
18
|
+
// JSON object for whatever's reading it afterward (a script, an agent),
|
|
19
|
+
// not a human's console. Every informational console.log below is
|
|
20
|
+
// skipped for it; only the final JSON report (or a thrown Error, for a
|
|
21
|
+
// truly unexpected failure outside runJsonReportMode's own try/catch)
|
|
22
|
+
// reaches stdout.
|
|
23
|
+
const jsonReport = process.env.WAYGRAPH_JSON === "1";
|
|
24
|
+
const ffExpand =
|
|
25
|
+
process.env.WAYGRAPH_FF_EXPAND === "1" || process.env.WAYGRAPH_FF_DISABLED === "1";
|
|
26
|
+
const ffDisabled = process.env.WAYGRAPH_FF_DISABLED === "1";
|
|
27
|
+
/** Expand or keep fastForwardComposeBlock units for demo/run step lists. */
|
|
28
|
+
const flattenBlockInfos = (blockInfos) => {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const bi of blockInfos) {
|
|
31
|
+
const b = bi.block;
|
|
32
|
+
if (ffExpand && isFastForwardBlock(b)) {
|
|
33
|
+
for (const step of b.steps()) {
|
|
34
|
+
out.push({
|
|
35
|
+
name: step.name,
|
|
36
|
+
block: step.block,
|
|
37
|
+
...(step.routes ? { routes: step.routes } : {}),
|
|
38
|
+
...(bi.resetSessionBefore ? { resetSessionBefore: true } : {}),
|
|
39
|
+
// Dispute / --ff-disabled: keep blitz pacing on former FF inners so
|
|
40
|
+
// wall-clock stays comparable to opaque FF (same real acts).
|
|
41
|
+
wasFastForward: true,
|
|
42
|
+
ffSource: b.name,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
out.push(bi);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
let resolved = [];
|
|
52
|
+
let chainFlows = null;
|
|
53
|
+
// A bare identifier (no "(", no "then") might name an existing Flow
|
|
54
|
+
// that's already wired up (e.g. loginFlow) - try that FIRST so pointing
|
|
55
|
+
// at real, already-built flows needs no chain-spec typing at all. Falls
|
|
56
|
+
// through to ordinary block-chain parsing if nothing matches.
|
|
57
|
+
const bareRef = /^[A-Za-z_$][\w]*$/.test(spec.trim()) ? spec.trim() : null;
|
|
58
|
+
if (bareRef) {
|
|
59
|
+
const flow = await findFlow(projectDir, bareRef);
|
|
60
|
+
if (flow && typeof flow.blocks === "function") {
|
|
61
|
+
const blockInfos = flattenBlockInfos(flow.blocks());
|
|
62
|
+
// Same --data / WAYGRAPH_DATA seeding as the multi-segment path.
|
|
63
|
+
// Without this, `waygraph run shop.flow.ts --data '{...}'` (and bare
|
|
64
|
+
// export names) hit preflight with an empty MemPage.
|
|
65
|
+
const stubActive = flow.memStub === true || process.env.WAYGRAPH_MEM_STUB === "1";
|
|
66
|
+
const seedMem = () => seedMemForFlow(mem, blockInfos, undefined, bareRef, stubActive);
|
|
67
|
+
resolved = blockInfos.map((bi, idx) => ({
|
|
68
|
+
block: bi.block,
|
|
69
|
+
exportName: bi.name,
|
|
70
|
+
seedMem: idx === 0 ? seedMem : undefined,
|
|
71
|
+
highlightFixtures: flow.highlightFixtures,
|
|
72
|
+
demoPace: flow.demoPace,
|
|
73
|
+
highlightStyle: flow.highlightStyle,
|
|
74
|
+
expectedFailureReason:
|
|
75
|
+
flow.expectedFailureReason && idx === blockInfos.length - 1
|
|
76
|
+
? flow.expectedFailureReason
|
|
77
|
+
: undefined,
|
|
78
|
+
...(bi.wasFastForward ? { wasFastForward: true, ffSource: bi.ffSource } : {}),
|
|
79
|
+
}));
|
|
80
|
+
chainFlows = [flow];
|
|
81
|
+
if (!jsonReport) {
|
|
82
|
+
console.log(
|
|
83
|
+
'waygraph: running existing flow "' + bareRef + '" - ' +
|
|
84
|
+
resolved.map((r) => r.block.name).join(" -> ") + " (" + resolved.length + " block" +
|
|
85
|
+
(resolved.length === 1 ? "" : "s") + ", no chain spec needed)",
|
|
86
|
+
);
|
|
87
|
+
if (ffDisabled) {
|
|
88
|
+
console.error(
|
|
89
|
+
"waygraph demo: --ff-disabled - FFCompose expanded to inners (dispute / step locus); blitz pacing kept on former FF steps",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (resolved.length === 0) {
|
|
96
|
+
const segments = parseChainSpec(spec);
|
|
97
|
+
if (segments.length === 0) {
|
|
98
|
+
throw new Error("waygraph chain: empty spec - give at least one block name");
|
|
99
|
+
}
|
|
100
|
+
// Each "then"-separated segment can ALSO name an existing Flow (a
|
|
101
|
+
// Flow's own requires, unioned across its Blocks, take the same
|
|
102
|
+
// JSON-payload shape a single Block's requires would), not just a
|
|
103
|
+
// Block. "loginFlow then dashboardFlow" chains two whole Flows one
|
|
104
|
+
// after another - a bare Block segment gets wrapped as its own
|
|
105
|
+
// trivial one-Block Flow so the whole spec reduces to one thing:
|
|
106
|
+
// chainFlow(...) does the actual sequencing (session-reset boundaries
|
|
107
|
+
// included) that used to be hand-rolled here - this is a thin wrapper
|
|
108
|
+
// over the real engine primitive, not a second copy of its logic.
|
|
109
|
+
const wrapEngine = new Engine();
|
|
110
|
+
const flows = [];
|
|
111
|
+
// Parallel to flows - only a REAL named Flow (found via findFlow)
|
|
112
|
+
// counts as an "episode" a human would want labeled; a bare Block
|
|
113
|
+
// segment gets wrapped as its own trivial one-Block Flow so it can
|
|
114
|
+
// still flow through chainFlow's sequencing uniformly, but it's not
|
|
115
|
+
// an authored scenario and gets no episode number - keeps the plain
|
|
116
|
+
// "quick ad hoc block chain" case free of meaningless "Episode 1"/
|
|
117
|
+
// "Episode 2" labels on things that were never episodes to begin with.
|
|
118
|
+
const flowMeta = [];
|
|
119
|
+
let episodeCounter = 0;
|
|
120
|
+
// seedMem is a closure, NOT called here - seeding every segment's own
|
|
121
|
+
// json payload eagerly, all upfront during resolution, was a real bug:
|
|
122
|
+
// mem is one shared object, so when two segments require the SAME key
|
|
123
|
+
// (e.g. two segments both requiring LoginCreds, each with a DIFFERENT
|
|
124
|
+
// payload - "login as standard_user then login as locked_out_user"),
|
|
125
|
+
// the LAST segment resolved silently overwrote the first's value
|
|
126
|
+
// before either segment had even started running - so the FIRST
|
|
127
|
+
// segment's own blocks would run against the SECOND segment's
|
|
128
|
+
// credentials. Deferred to execution time instead (runStepMode calls
|
|
129
|
+
// this exactly once, right as each segment's first block is reached -
|
|
130
|
+
// see resolved[i].seedMem below), same moment session-reset already
|
|
131
|
+
// happens for the same reason.
|
|
132
|
+
for (const seg of segments) {
|
|
133
|
+
const flow = await findFlow(projectDir, seg.ref);
|
|
134
|
+
if (flow && typeof flow.blocks === "function") {
|
|
135
|
+
flows.push(flow);
|
|
136
|
+
episodeCounter += 1;
|
|
137
|
+
const stubActive = flow.memStub === true || process.env.WAYGRAPH_MEM_STUB === "1";
|
|
138
|
+
flowMeta.push({
|
|
139
|
+
episodeNumber: episodeCounter,
|
|
140
|
+
episodeTitle: flow.title || seg.ref,
|
|
141
|
+
expectedFailureReason: flow.expectedFailureReason,
|
|
142
|
+
highlightFixtures: flow.highlightFixtures,
|
|
143
|
+
demoPace: flow.demoPace,
|
|
144
|
+
highlightStyle: flow.highlightStyle,
|
|
145
|
+
seedMem: () => seedMemForFlow(mem, flow.blocks(), seg.json, seg.ref, stubActive),
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
const r = await findBlock(projectDir, seg.ref);
|
|
149
|
+
flows.push(wrapEngine.defineFlow([start, r.block, end]));
|
|
150
|
+
// A bare Block segment (no Flow, no withMemStub to opt in) only gets
|
|
151
|
+
// memStub via the global --mem-stub/WAYGRAPH_MEM_STUB override.
|
|
152
|
+
const stubActive = process.env.WAYGRAPH_MEM_STUB === "1";
|
|
153
|
+
flowMeta.push({
|
|
154
|
+
episodeNumber: undefined,
|
|
155
|
+
episodeTitle: undefined,
|
|
156
|
+
highlightFixtures: undefined,
|
|
157
|
+
demoPace: undefined,
|
|
158
|
+
highlightStyle: undefined,
|
|
159
|
+
seedMem: () => seedMemForBlock(mem, r, seg.json, stubActive),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const combinedBlocks = flattenBlockInfos(chainFlow(...flows).blocks());
|
|
164
|
+
let fi = 0;
|
|
165
|
+
// Episode lengths must follow the same flatten so remainingInFlow stays aligned.
|
|
166
|
+
const flowLengths = flows.map((f) => flattenBlockInfos(f.blocks()).length);
|
|
167
|
+
let remainingInFlow = flowLengths[0];
|
|
168
|
+
let isFirstOfSegment = true;
|
|
169
|
+
resolved = combinedBlocks.map((bi) => {
|
|
170
|
+
while (remainingInFlow === 0) {
|
|
171
|
+
fi += 1;
|
|
172
|
+
remainingInFlow = flowLengths[fi];
|
|
173
|
+
isFirstOfSegment = true;
|
|
174
|
+
}
|
|
175
|
+
const meta = flowMeta[fi];
|
|
176
|
+
const entry = {
|
|
177
|
+
block: bi.block,
|
|
178
|
+
exportName: bi.name,
|
|
179
|
+
resetSession: bi.resetSessionBefore === true,
|
|
180
|
+
episodeNumber: meta.episodeNumber,
|
|
181
|
+
episodeTitle: meta.episodeTitle,
|
|
182
|
+
highlightFixtures: meta.highlightFixtures,
|
|
183
|
+
demoPace: meta.demoPace,
|
|
184
|
+
highlightStyle: meta.highlightStyle,
|
|
185
|
+
expectedFailureReason:
|
|
186
|
+
meta.expectedFailureReason && remainingInFlow === 1 ? meta.expectedFailureReason : undefined,
|
|
187
|
+
seedMem: isFirstOfSegment ? meta.seedMem : undefined,
|
|
188
|
+
...(bi.wasFastForward ? { wasFastForward: true, ffSource: bi.ffSource } : {}),
|
|
189
|
+
};
|
|
190
|
+
remainingInFlow -= 1;
|
|
191
|
+
isFirstOfSegment = false;
|
|
192
|
+
return entry;
|
|
193
|
+
});
|
|
194
|
+
chainFlows = flows;
|
|
195
|
+
if (!jsonReport) {
|
|
196
|
+
console.log(
|
|
197
|
+
"waygraph: chaining " + resolved.map((r) => r.block.name).join(" -> ") +
|
|
198
|
+
" (" + resolved.length + " block" + (resolved.length === 1 ? "" : "s") + ")",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const step = process.env.WAYGRAPH_STEP === "1";
|
|
203
|
+
// Stepping through headless defeats the point for a MANUAL demo - a
|
|
204
|
+
// human can't watch it. But an explicit WAYGRAPH_HEADED=0 always wins
|
|
205
|
+
// over that default: --auto-play-video sets STEP=1 (it still needs the
|
|
206
|
+
// overlay/narration machinery) AND HEADED=0 (unattended + recorded,
|
|
207
|
+
// nobody has to watch a live window for it to be right).
|
|
208
|
+
const headed =
|
|
209
|
+
process.env.WAYGRAPH_HEADED === "0" ? false : process.env.WAYGRAPH_HEADED === "1" || step;
|
|
210
|
+
// Step mode: blitz Playwright slowMo when any opaque FF **or** former-FF
|
|
211
|
+
// inners (--ff-disabled) are in the resolved list. Explicit WAYGRAPH_SLOWMO wins.
|
|
212
|
+
const hasFfBlitz = resolved.some(
|
|
213
|
+
(r) =>
|
|
214
|
+
r &&
|
|
215
|
+
((r.block && r.block.fastForward === true) || r.wasFastForward === true),
|
|
216
|
+
);
|
|
217
|
+
const slowMo = process.env.WAYGRAPH_SLOWMO !== undefined
|
|
218
|
+
? Number(process.env.WAYGRAPH_SLOWMO)
|
|
219
|
+
: step
|
|
220
|
+
? hasFfBlitz
|
|
221
|
+
? 0
|
|
222
|
+
: 350
|
|
223
|
+
: undefined;
|
|
224
|
+
if (step && hasFfBlitz && process.env.WAYGRAPH_SLOWMO === undefined && !jsonReport) {
|
|
225
|
+
console.error(
|
|
226
|
+
ffDisabled
|
|
227
|
+
? "waygraph demo: --ff-disabled (expanded) -> Playwright slowMo=0 on former FF steps (blitz; same acts as opaque FF)"
|
|
228
|
+
: "waygraph demo: opaque FFCompose in flow -> Playwright slowMo=0 (blitz); set WAYGRAPH_SLOWMO to override",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
const baseURL = process.env.WAYGRAPH_BASE_URL;
|
|
232
|
+
const title = process.env.WAYGRAPH_TITLE;
|
|
233
|
+
// Comma-separated Block names (their real .name, e.g. "login") that
|
|
234
|
+
// should blow past the overlay's own added dwell (ring pop, cursor
|
|
235
|
+
// travel, typing delay, and that block's own gates) - "I want the login
|
|
236
|
+
// block to be faster than the rest of the demo," Dan's own phrase.
|
|
237
|
+
// FFCompose blocks get the same skipTheater automatically (no need to
|
|
238
|
+
// list them here). Distinct from --fast (gates only).
|
|
239
|
+
const fastBlockNames = new Set(
|
|
240
|
+
(process.env.WAYGRAPH_FAST_BLOCKS || "")
|
|
241
|
+
.split(",")
|
|
242
|
+
.map((s) => s.trim())
|
|
243
|
+
.filter(Boolean),
|
|
244
|
+
);
|
|
245
|
+
// Off by default - a same-user chain (login -> dashboard -> ...) needs
|
|
246
|
+
// to STAY authenticated across its own blocks. Only a chain deliberately
|
|
247
|
+
// re-visiting an auth entry point (e.g. "login" appearing twice, to
|
|
248
|
+
// demo the same flow starting fresh each time) needs this.
|
|
249
|
+
const clearSession = process.env.WAYGRAPH_CLEAR_SESSION === "1";
|
|
250
|
+
const videoEnv = process.env.WAYGRAPH_VIDEO;
|
|
251
|
+
let videoDir = null;
|
|
252
|
+
if (videoEnv) {
|
|
253
|
+
videoDir = videoEnv === "1" ? join(projectDir, ".waygraph-videos") : videoEnv;
|
|
254
|
+
mkdirSync(videoDir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
/** Fixed 16:9 capture for --video (demo QA and headless run). Step+video no longer maximizes. */
|
|
257
|
+
const DEFAULT_VIDEO_VIEWPORT = { width: 1920, height: 1080 };
|
|
258
|
+
const DEFAULT_RUN_VIDEO_VIEWPORT = { width: 1280, height: 720 };
|
|
259
|
+
const videoViewportParsed = parseVideoViewport(process.env.WAYGRAPH_VIDEO_VIEWPORT);
|
|
260
|
+
const fixedVideoViewport = videoDir
|
|
261
|
+
? (videoViewportParsed ?? (step ? DEFAULT_VIDEO_VIEWPORT : DEFAULT_RUN_VIDEO_VIEWPORT))
|
|
262
|
+
: null;
|
|
263
|
+
const engine = new Engine({ headless: !headed, slowMo });
|
|
264
|
+
let result;
|
|
265
|
+
// Non-interactive execution (no --step) runs the whole chain as one
|
|
266
|
+
// composed flow with no per-block loop of its own to defer seeding
|
|
267
|
+
// into - it can't support two segments needing DIFFERENT values for the
|
|
268
|
+
// SAME mem key (e.g. two segments both requiring LoginCreds, each with
|
|
269
|
+
// its own payload). Fall back to seeding every segment eagerly, upfront -
|
|
270
|
+
// the same limitation "chain" always had; only --step's own runStepMode
|
|
271
|
+
// loop actually needs (and correctly supports, via resolved[i].seedMem)
|
|
272
|
+
// per-segment differentiated values.
|
|
273
|
+
if (!step && !jsonReport) {
|
|
274
|
+
for (const r of resolved) {
|
|
275
|
+
if (r.seedMem) r.seedMem();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (step || jsonReport || baseURL || videoDir) {
|
|
279
|
+
const { chromium } = await import("@playwright/test");
|
|
280
|
+
// --start-maximized (step mode only): viewport: null alone only made
|
|
281
|
+
// the PAGE content track the window - the actual browser WINDOW still
|
|
282
|
+
// launched at Chromium's own default size/position, which stayed put
|
|
283
|
+
// where it was for a previous, larger monitor and looked chopped off on
|
|
284
|
+
// a smaller one. Maximizing fills whatever screen it's actually on.
|
|
285
|
+
// Same CHROME_PATH/CHROMIUM_PATH rule as Engine.run(mem): prefer the
|
|
286
|
+
// system/Flatpak Chromium when set, otherwise Playwright's bundled build.
|
|
287
|
+
// Without this, STEP mode always launched a different-looking browser than
|
|
288
|
+
// headed Engine runs / waygraph-demo.mjs (which already resolve Flatpak).
|
|
289
|
+
const executablePath = process.env.CHROME_PATH || process.env.CHROMIUM_PATH || undefined;
|
|
290
|
+
// Maximized for interactive step, and for fixed video viewport so the
|
|
291
|
+
// locked record size can actually fit (otherwise .webm gets grey pad).
|
|
292
|
+
const browser = await chromium.launch({
|
|
293
|
+
headless: !headed,
|
|
294
|
+
slowMo,
|
|
295
|
+
args: step || fixedVideoViewport ? ["--start-maximized"] : [],
|
|
296
|
+
...(executablePath ? { executablePath } : {}),
|
|
297
|
+
});
|
|
298
|
+
const contextOpts = fixedVideoViewport
|
|
299
|
+
? {
|
|
300
|
+
baseURL,
|
|
301
|
+
viewport: fixedVideoViewport,
|
|
302
|
+
// Lock DPR so --video-viewport 800x450 stays 800x450 in the .webm
|
|
303
|
+
// (HiDPI hosts otherwise scale recordVideo to e.g. 1920x1080).
|
|
304
|
+
deviceScaleFactor: 1,
|
|
305
|
+
hasTouch: true,
|
|
306
|
+
}
|
|
307
|
+
: step
|
|
308
|
+
? { baseURL, viewport: null, hasTouch: true }
|
|
309
|
+
: { baseURL, viewport: { width: 1280, height: 720 }, hasTouch: true };
|
|
310
|
+
if (videoDir) {
|
|
311
|
+
contextOpts.recordVideo = fixedVideoViewport
|
|
312
|
+
? { dir: videoDir, size: fixedVideoViewport }
|
|
313
|
+
: { dir: videoDir };
|
|
314
|
+
}
|
|
315
|
+
const context = await browser.newContext(contextOpts);
|
|
316
|
+
let pageForVideo = null;
|
|
317
|
+
try {
|
|
318
|
+
if (step) {
|
|
319
|
+
const page = await context.newPage();
|
|
320
|
+
pageForVideo = page;
|
|
321
|
+
if (fixedVideoViewport) page.__wgVideoViewport = fixedVideoViewport;
|
|
322
|
+
// Step 1's "before" panel used to sit over a blank about:blank page
|
|
323
|
+
// until the human clicked Run - show the real destination first.
|
|
324
|
+
if (baseURL) {
|
|
325
|
+
await page.goto(baseURL).catch(() => {});
|
|
326
|
+
}
|
|
327
|
+
result = await runStepMode(engine, start, end, context, page, mem, resolved, slowMo, title, fastBlockNames, clearSession, baseURL);
|
|
328
|
+
} else if (jsonReport) {
|
|
329
|
+
const page = await context.newPage();
|
|
330
|
+
pageForVideo = page;
|
|
331
|
+
if (fixedVideoViewport) page.__wgVideoViewport = fixedVideoViewport;
|
|
332
|
+
if (baseURL) {
|
|
333
|
+
await page.goto(baseURL).catch(() => {});
|
|
334
|
+
}
|
|
335
|
+
const report = await runJsonReportMode(engine, start, end, context, page, mem, resolved, baseURL);
|
|
336
|
+
console.log(JSON.stringify(report, null, 2));
|
|
337
|
+
if (!report.ok) process.exitCode = 1;
|
|
338
|
+
return;
|
|
339
|
+
} else {
|
|
340
|
+
const page = await context.newPage();
|
|
341
|
+
pageForVideo = page;
|
|
342
|
+
if (fixedVideoViewport) page.__wgVideoViewport = fixedVideoViewport;
|
|
343
|
+
if (baseURL) {
|
|
344
|
+
await page.goto(baseURL).catch(() => {});
|
|
345
|
+
}
|
|
346
|
+
if (chainFlows && chainFlows.length > 0) {
|
|
347
|
+
result = await runChainedFlows(chainFlow, chainFlows, context, mem, page);
|
|
348
|
+
} else {
|
|
349
|
+
const blocks = resolved.map((r) => r.block);
|
|
350
|
+
const chained = blocks.reduce((a, b) => connect(a, b));
|
|
351
|
+
const flow = engine.defineFlow([start, chained, end]);
|
|
352
|
+
result = await flow.run(context, mem, { page, closeOnFinish: false });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} finally {
|
|
356
|
+
await context.close();
|
|
357
|
+
const savedVideo =
|
|
358
|
+
pageForVideo?.video() !== null && pageForVideo?.video() !== undefined
|
|
359
|
+
? await pageForVideo.video()?.path().catch(() => null)
|
|
360
|
+
: null;
|
|
361
|
+
if (savedVideo) {
|
|
362
|
+
console.log("waygraph: video saved to " + savedVideo);
|
|
363
|
+
}
|
|
364
|
+
await browser.close();
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
if (chainFlows && chainFlows.length > 0) {
|
|
368
|
+
try {
|
|
369
|
+
result = await chainFlow(...chainFlows).run(mem);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if (isExpectedChainFailure(chainFlows, err)) {
|
|
372
|
+
result = { __expectedFailure: true, message: err && err.message ? err.message : String(err) };
|
|
373
|
+
} else {
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
const blocks = resolved.map((r) => r.block);
|
|
379
|
+
const chained = blocks.reduce((a, b) => connect(a, b));
|
|
380
|
+
const flow = engine.defineFlow([start, chained, end]);
|
|
381
|
+
const recordVideo = videoDir ? { dir: videoDir } : undefined;
|
|
382
|
+
result = await flow.run(mem, recordVideo ? { recordVideo } : undefined);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (result && result.__expectedFailure) {
|
|
386
|
+
console.log("waygraph: chain finished (expected failure on last episode) -- " + result.message);
|
|
387
|
+
} else {
|
|
388
|
+
console.log("waygraph: chain finished -- " + JSON.stringify(result));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function startRunner() {
|
|
393
|
+
main().catch((err) => {
|
|
394
|
+
console.error(err && err.stack ? err.stack : String(err));
|
|
395
|
+
process.exitCode = 1;
|
|
396
|
+
});
|
|
397
|
+
}
|