waygraph 0.15.51 → 0.15.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/analysis/graph.d.ts +1 -1
- package/dist/analysis/graph.js +10 -15
- package/dist/analysis/map-check.d.ts +3 -2
- package/dist/analysis/practices-check.d.ts +0 -1
- package/dist/analysis/practices-check.js +0 -13
- package/dist/auto-session/session.d.ts +1 -1
- package/dist/auto-session/session.js +13 -8
- package/dist/blocks-select.d.ts +7 -4
- package/dist/blocks-select.js +0 -14
- package/dist/browser.d.ts +27 -0
- package/dist/browser.js +62 -0
- package/dist/cli/flags.js +3 -1
- package/dist/cli/run-chain.js +4 -0
- package/dist/cli/usage.js +17 -1
- package/dist/cli.js +13 -1
- package/dist/commands/agent-dive-scaffold.d.ts +5 -20
- package/dist/commands/agent-dive-scaffold.js +10 -3
- package/dist/commands/extensions.d.ts +1 -0
- package/dist/commands/extensions.js +22 -0
- package/dist/commands/init.js +43 -5
- package/dist/commands/test.js +16 -2
- package/dist/commands/try.js +2 -1
- package/dist/commands/upgrade.d.ts +6 -0
- package/dist/commands/upgrade.js +113 -0
- package/dist/config.d.ts +81 -3
- package/dist/config.js +91 -16
- package/dist/core/block.d.ts +19 -2
- package/dist/core/block.js +43 -3
- package/dist/core/blocks/nav.d.ts +11 -0
- package/dist/core/blocks/nav.js +8 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/engine.d.ts +19 -16
- package/dist/core/engine.js +6 -4
- package/dist/core/flow.d.ts +27 -0
- package/dist/core/flow.js +75 -28
- package/dist/core/map-builder.d.ts +30 -4
- package/dist/core/map-builder.js +45 -7
- package/dist/env-file.d.ts +7 -7
- package/dist/env-file.js +29 -16
- package/dist/extensions.d.ts +21 -0
- package/dist/extensions.js +178 -0
- package/dist/highlights/demo-pace.d.ts +0 -1
- package/dist/highlights/demo-pace.js +24 -3
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -2
- package/dist/pilot-overlay/action-ring.d.ts +0 -9
- package/dist/pilot-overlay/action-ring.js +1 -1
- package/dist/pilot-overlay/overlay-fixtures.d.ts +0 -7
- package/dist/pilot-overlay/overlay-fixtures.js +0 -45
- package/dist/pilot-overlay/panel.d.ts +2 -1
- package/dist/playwright-config.d.ts +4 -0
- package/dist/playwright-config.js +15 -0
- package/dist/playwright.config.d.ts +2 -0
- package/dist/playwright.config.js +6 -0
- package/dist/runner/demo-log.js +1 -7
- package/dist/runner/inpage/core.d.ts +1 -1
- package/dist/runner/inpage/core.js +8 -7
- package/dist/runner/interaction-patch.js +32 -1
- package/dist/runner/main.js +22 -11
- package/dist/runner/rings.js +0 -49
- package/dist/runner/step-mode.js +10 -4
- package/dist/session/auto-explore-run.js +9 -5
- package/dist/session/auto-explore.d.ts +2 -1
- package/dist/session/auto-explore.js +6 -4
- package/dist/session/auto-session-ipc.d.ts +1 -2
- package/dist/session/auto-session-ipc.js +1 -1
- package/dist/session/block-inject.d.ts +0 -13
- package/dist/session/block-inject.js +3 -3
- package/dist/session/pilot.d.ts +0 -2
- package/dist/session/pilot.js +1 -2
- package/dist/session/step-overlay.js +3 -1
- package/dist/traverse/coverage.d.ts +0 -4
- package/dist/traverse/coverage.js +1 -1
- package/dist/traverse/run.js +3 -1
- package/dist/types.d.ts +1 -1
- package/dist/ui/components/ring.js +1 -0
- package/dist/ui/overlay.css +1 -0
- package/dist/ui/shadow.d.ts +1 -3
- package/dist/ui/shadow.js +0 -4
- package/dist/ui/tokens.d.ts +0 -11
- package/dist/ui/tokens.js +0 -5
- package/dist/video-dir.d.ts +8 -0
- package/dist/video-dir.js +60 -0
- package/dist/webserver.js +5 -1
- package/package.json +11 -5
- package/templates/scaffold/README.md +5 -3
- package/templates/scaffold/STRUCTURE.md +10 -4
- package/templates/scaffold/config/waygraph.config.ts +45 -0
- package/templates/scaffold/package.json +7 -7
- package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
- package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
- package/templates/scaffold/src/flows/README.md +35 -14
- package/templates/scaffold/src/flows/home/cart.flow.ts +51 -0
- package/templates/scaffold/src/flows/home/hello.flow.ts +42 -0
- package/templates/scaffold/src/map/(app_base)/docs/_nav.block.ts +0 -9
- package/templates/scaffold/src/map/(app_base)/docs/_page.block.ts +0 -5
- package/templates/scaffold/src/map/(app_base)/docs/_sel.ts +1 -1
- package/templates/scaffold/src/map/(app_base)/home/_methods/add-item.effect.block.ts +3 -19
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +2 -25
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-item-added.method.block.ts +2 -19
- package/templates/scaffold/src/map/(app_base)/home/_methods/clear-cart.method.block.ts +1 -17
- package/templates/scaffold/src/map/(app_base)/home/_methods/remove-item.effect.block.ts +1 -10
- package/templates/scaffold/src/map/(app_base)/home/_nav.block.ts +1 -12
- package/templates/scaffold/src/map/(app_base)/home/_page.block.ts +0 -9
- package/templates/scaffold/src/map/(app_base)/home/_sel.ts +3 -1
- package/templates/scaffold/src/map/(app_base)/home/nav-verification-link.block.ts +1 -23
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-body.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-header.method.block.ts +19 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-status.method.block.ts +20 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.ts +18 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-body.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-headers.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/send-request.method.block.ts +15 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-bearer-token.method.block.ts +20 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-headers.method.block.ts +23 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-json-body.method.block.ts +36 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-method.method.block.ts +17 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-query-params.method.block.ts +22 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-url.method.block.ts +18 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_nav.block.ts +11 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_page.block.ts +36 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_sel.ts +27 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/settings/_nav.block.ts +10 -0
- package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-content.method.block.ts +2 -27
- package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-received.method.block.ts +2 -21
- package/templates/scaffold/src/map/(external)/mailpit/_methods/extract-email-link.method.block.ts +1 -19
- package/templates/scaffold/src/map/(external)/mailpit/_methods/open-message.method.block.ts +1 -11
- package/templates/scaffold/src/map/(external)/mailpit/_nav.block.ts +1 -9
- package/templates/scaffold/src/map/(external)/mailpit/_page.block.ts +0 -11
- package/templates/scaffold/src/states/demo.mem-keys.ts +20 -19
- package/templates/scaffold/src/states/demo.mem-stubs.ts +4 -0
- package/templates/scaffold/src/states/demo.states.ts +5 -1
- package/templates/scaffold/tests/example.spec.ts +6 -7
- package/templates/scaffold/tests/hoppscotch.spec.ts +97 -0
- package/templates/scaffold/tests/mail-verify.spec.ts +8 -17
- package/templates/scaffold/tsconfig.json +1 -1
- package/templates/skills/waygraph-convention.skill.md +40 -3
- package/templates/skills/waygraph-pilot.skill.md +4 -5
- package/dist/browser-path.d.ts +0 -5
- package/dist/browser-path.js +0 -18
- package/dist/runner/overlay-present.d.ts +0 -8
- package/dist/runner/overlay-present.js +0 -57
- package/dist/ui/components/banner.d.ts +0 -2
- package/dist/ui/components/banner.js +0 -10
- package/dist/ui/components/cursor.d.ts +0 -2
- package/dist/ui/components/cursor.js +0 -19
- package/templates/scaffold/playwright.config.ts +0 -12
- package/templates/scaffold/src/flows/example.flow.ts +0 -29
- package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -85
- package/templates/scaffold/src/flows/shop.flow.ts +0 -74
package/dist/env-file.js
CHANGED
|
@@ -28,31 +28,44 @@ function parseEnvFile(content) {
|
|
|
28
28
|
}
|
|
29
29
|
return out;
|
|
30
30
|
}
|
|
31
|
+
/** The env mode: `--env <mode>` / `WAYGRAPH_ENV`, else `NODE_ENV`, else "development". */
|
|
32
|
+
export function envMode() {
|
|
33
|
+
return process.env.WAYGRAPH_ENV || process.env.NODE_ENV || "development";
|
|
34
|
+
}
|
|
35
|
+
/** Removes `--env <mode>` / `--env=<mode>` from argv and records it as `WAYGRAPH_ENV`, so it never reaches a forwarded command. */
|
|
36
|
+
export function takeEnvFlag(argv) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const a = argv[i];
|
|
40
|
+
if (a === "--env" && argv[i + 1] !== undefined)
|
|
41
|
+
process.env.WAYGRAPH_ENV = argv[++i];
|
|
42
|
+
else if (a.startsWith("--env="))
|
|
43
|
+
process.env.WAYGRAPH_ENV = a.slice("--env=".length);
|
|
44
|
+
else
|
|
45
|
+
out.push(a);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
31
49
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* whatever the shell actually exported - always wins over every file; a `.env` file only fills
|
|
36
|
-
* a gap, it never silently overrides something the caller explicitly set. Safe to call more
|
|
37
|
-
* than once (e.g. once eagerly at `cwd`, again once the real project dir is known) - loading
|
|
38
|
-
* twice is a no-op the second time for any key already filled.
|
|
50
|
+
* Loads `.env` / `.env.local` / `.env.<mode>` / `.env.<mode>.local` from a project's root, the layering
|
|
51
|
+
* Vite and Next use: each later file overrides the earlier ones (`.env.<mode>.local` wins). A value the
|
|
52
|
+
* shell already exported always beats every file, so a file only fills gaps. Calling it twice is harmless.
|
|
39
53
|
*/
|
|
40
|
-
export function loadEnvFiles(projectDir, mode =
|
|
41
|
-
const
|
|
42
|
-
for (const name of
|
|
54
|
+
export function loadEnvFiles(projectDir, mode = envMode()) {
|
|
55
|
+
const merged = {};
|
|
56
|
+
for (const name of [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`]) {
|
|
43
57
|
const p = join(projectDir, name);
|
|
44
58
|
if (!existsSync(p))
|
|
45
59
|
continue;
|
|
46
|
-
let parsed;
|
|
47
60
|
try {
|
|
48
|
-
|
|
61
|
+
Object.assign(merged, parseEnvFile(readFileSync(p, "utf-8")));
|
|
49
62
|
}
|
|
50
63
|
catch {
|
|
51
64
|
continue; // unreadable file - never fail a run over an optional .env
|
|
52
65
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
}
|
|
67
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
68
|
+
if (process.env[key] === undefined)
|
|
69
|
+
process.env[key] = value;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A known name (`"hoppscotch"`), a Chrome Web Store id, a path to an unpacked extension, or `{ id, sha256 }`. */
|
|
2
|
+
export type ExtensionSpec = string | {
|
|
3
|
+
id?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
sha256?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const KNOWN_EXTENSIONS: Readonly<Record<string, string>>;
|
|
8
|
+
/** Unpacks a .crx and pins the manifest `key`, so the extension keeps its store id (its origin is then stable, e.g. for an API allowlist). */
|
|
9
|
+
export declare function unpackCrx(crx: Buffer, dest: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Returns an unpacked directory for one config entry, downloading it on first use. An installed
|
|
12
|
+
* extension is never silently replaced - pass `update: true` (`waygraph extensions install --update`) to refetch.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveExtension(spec: ExtensionSpec, projectDir: string, opts?: {
|
|
15
|
+
update?: boolean;
|
|
16
|
+
}): Promise<string>;
|
|
17
|
+
export declare function resolveExtensions(specs: readonly ExtensionSpec[] | undefined, projectDir: string): Promise<string[]>;
|
|
18
|
+
export declare function extensionInfo(dir: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Browser extensions for waygraph runs: resolve a config entry to an unpacked directory Chromium can load.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
6
|
+
import { inflateRawSync } from "node:zlib";
|
|
7
|
+
export const KNOWN_EXTENSIONS = {
|
|
8
|
+
hoppscotch: "amknoiejhlmhancpahfcfcfhllgkpbld",
|
|
9
|
+
};
|
|
10
|
+
const STORE_ID = /^[a-p]{32}$/;
|
|
11
|
+
function cacheDir() {
|
|
12
|
+
const base = process.env.WAYGRAPH_CACHE_DIR ?? join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "waygraph");
|
|
13
|
+
return join(base, "extensions");
|
|
14
|
+
}
|
|
15
|
+
/** Reads the end-of-central-directory record and extracts every entry; rejects paths that escape `dest`. */
|
|
16
|
+
function unzipTo(zip, dest) {
|
|
17
|
+
let eocd = zip.length - 22;
|
|
18
|
+
while (eocd >= 0 && zip.readUInt32LE(eocd) !== 0x06054b50)
|
|
19
|
+
eocd--;
|
|
20
|
+
if (eocd < 0)
|
|
21
|
+
throw new Error("not a zip archive");
|
|
22
|
+
const count = zip.readUInt16LE(eocd + 10);
|
|
23
|
+
let p = zip.readUInt32LE(eocd + 16);
|
|
24
|
+
const root = resolve(dest);
|
|
25
|
+
for (let i = 0; i < count; i++) {
|
|
26
|
+
if (zip.readUInt32LE(p) !== 0x02014b50)
|
|
27
|
+
throw new Error("corrupt zip directory");
|
|
28
|
+
const method = zip.readUInt16LE(p + 10);
|
|
29
|
+
const size = zip.readUInt32LE(p + 20);
|
|
30
|
+
const nameLen = zip.readUInt16LE(p + 28);
|
|
31
|
+
const extraLen = zip.readUInt16LE(p + 30);
|
|
32
|
+
const commentLen = zip.readUInt16LE(p + 32);
|
|
33
|
+
const local = zip.readUInt32LE(p + 42);
|
|
34
|
+
const name = zip.toString("utf8", p + 46, p + 46 + nameLen);
|
|
35
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
36
|
+
const target = resolve(root, name);
|
|
37
|
+
if (target !== root && !target.startsWith(root + sep))
|
|
38
|
+
throw new Error(`unsafe path in archive: ${name}`);
|
|
39
|
+
if (name.endsWith("/")) {
|
|
40
|
+
mkdirSync(target, { recursive: true });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const dataStart = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28);
|
|
44
|
+
const raw = zip.subarray(dataStart, dataStart + size);
|
|
45
|
+
if (method !== 0 && method !== 8)
|
|
46
|
+
throw new Error(`unsupported zip compression (${method}) for ${name}`);
|
|
47
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
48
|
+
writeFileSync(target, method === 0 ? raw : inflateRawSync(raw));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** A .crx is a signed header followed by a normal zip. The signature is not checked here - pin `sha256` to do that. */
|
|
52
|
+
function crxToZip(crx) {
|
|
53
|
+
if (crx.toString("latin1", 0, 4) !== "Cr24")
|
|
54
|
+
throw new Error("not a Chrome extension (.crx) file");
|
|
55
|
+
const version = crx.readUInt32LE(4);
|
|
56
|
+
if (version === 3)
|
|
57
|
+
return crx.subarray(12 + crx.readUInt32LE(8));
|
|
58
|
+
if (version === 2)
|
|
59
|
+
return crx.subarray(16 + crx.readUInt32LE(8) + crx.readUInt32LE(12));
|
|
60
|
+
throw new Error(`unsupported .crx version ${version}`);
|
|
61
|
+
}
|
|
62
|
+
/** Reads the length-delimited fields of one protobuf message as [fieldNumber, bytes] pairs; other wire types are skipped. */
|
|
63
|
+
function protoFields(buf) {
|
|
64
|
+
const out = [];
|
|
65
|
+
let p = 0;
|
|
66
|
+
const varint = () => {
|
|
67
|
+
let n = 0;
|
|
68
|
+
for (let shift = 0;; shift += 7) {
|
|
69
|
+
const b = buf[p++];
|
|
70
|
+
n += (b & 0x7f) * 2 ** shift;
|
|
71
|
+
if (!(b & 0x80))
|
|
72
|
+
return n;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
while (p < buf.length) {
|
|
76
|
+
const tag = varint();
|
|
77
|
+
const wire = tag & 7;
|
|
78
|
+
if (wire === 2) {
|
|
79
|
+
const len = varint();
|
|
80
|
+
out.push([tag >>> 3, buf.subarray(p, p + len)]);
|
|
81
|
+
p += len;
|
|
82
|
+
}
|
|
83
|
+
else if (wire === 0)
|
|
84
|
+
varint();
|
|
85
|
+
else if (wire === 1)
|
|
86
|
+
p += 8;
|
|
87
|
+
else if (wire === 5)
|
|
88
|
+
p += 4;
|
|
89
|
+
else
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/** The extension's public key (DER), from which Chrome derives its id: with it in the manifest the id equals the store's. */
|
|
95
|
+
function crxPublicKey(crx) {
|
|
96
|
+
const version = crx.readUInt32LE(4);
|
|
97
|
+
if (version === 2)
|
|
98
|
+
return crx.subarray(16, 16 + crx.readUInt32LE(8));
|
|
99
|
+
const header = protoFields(crx.subarray(12, 12 + crx.readUInt32LE(8)));
|
|
100
|
+
const signed = header.find(([n]) => n === 10000)?.[1];
|
|
101
|
+
const crxId = signed ? protoFields(signed).find(([n]) => n === 1)?.[1] : undefined;
|
|
102
|
+
for (const [n, proof] of header) {
|
|
103
|
+
if (n !== 2 && n !== 3)
|
|
104
|
+
continue;
|
|
105
|
+
const key = protoFields(proof).find(([f]) => f === 1)?.[1];
|
|
106
|
+
if (key && crxId && createHash("sha256").update(key).digest().subarray(0, 16).equals(crxId))
|
|
107
|
+
return key;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/** Unpacks a .crx and pins the manifest `key`, so the extension keeps its store id (its origin is then stable, e.g. for an API allowlist). */
|
|
112
|
+
export function unpackCrx(crx, dest) {
|
|
113
|
+
unzipTo(crxToZip(crx), dest);
|
|
114
|
+
const key = crxPublicKey(crx);
|
|
115
|
+
if (!key)
|
|
116
|
+
return;
|
|
117
|
+
const file = join(dest, "manifest.json");
|
|
118
|
+
const manifest = JSON.parse(readFileSync(file, "utf-8"));
|
|
119
|
+
manifest.key = key.toString("base64");
|
|
120
|
+
writeFileSync(file, JSON.stringify(manifest, null, 2));
|
|
121
|
+
}
|
|
122
|
+
function readManifest(dir) {
|
|
123
|
+
const file = join(dir, "manifest.json");
|
|
124
|
+
if (!existsSync(file))
|
|
125
|
+
throw new Error(`no manifest.json in ${dir}`);
|
|
126
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
127
|
+
}
|
|
128
|
+
async function download(id, sha256) {
|
|
129
|
+
const url = "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0.0.0" +
|
|
130
|
+
`&acceptformat=crx2,crx3&x=id%3D${id}%26uc`;
|
|
131
|
+
const res = await fetch(url);
|
|
132
|
+
const body = Buffer.from(await res.arrayBuffer());
|
|
133
|
+
if (!res.ok || body.length < 16 || body.toString("latin1", 0, 4) !== "Cr24") {
|
|
134
|
+
throw new Error(`extension ${id} was not found in the Chrome Web Store (HTTP ${res.status})`);
|
|
135
|
+
}
|
|
136
|
+
if (sha256 && createHash("sha256").update(body).digest("hex") !== sha256.toLowerCase()) {
|
|
137
|
+
throw new Error(`extension ${id}: downloaded file does not match the pinned sha256`);
|
|
138
|
+
}
|
|
139
|
+
return body;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns an unpacked directory for one config entry, downloading it on first use. An installed
|
|
143
|
+
* extension is never silently replaced - pass `update: true` (`waygraph extensions install --update`) to refetch.
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveExtension(spec, projectDir, opts = {}) {
|
|
146
|
+
const entry = typeof spec === "string" ? (spec.startsWith(".") || spec.includes("/") ? { path: spec } : { id: spec }) : spec;
|
|
147
|
+
if (entry.path) {
|
|
148
|
+
const dir = resolve(projectDir, entry.path);
|
|
149
|
+
readManifest(dir);
|
|
150
|
+
return dir;
|
|
151
|
+
}
|
|
152
|
+
const id = KNOWN_EXTENSIONS[entry.id ?? ""] ?? entry.id ?? "";
|
|
153
|
+
if (!STORE_ID.test(id)) {
|
|
154
|
+
throw new Error(`unknown extension "${entry.id}" - use a known name (${Object.keys(KNOWN_EXTENSIONS).join(", ")}), a Chrome Web Store id, or { path }`);
|
|
155
|
+
}
|
|
156
|
+
const dir = join(cacheDir(), id);
|
|
157
|
+
if (!opts.update && existsSync(join(dir, "manifest.json")))
|
|
158
|
+
return dir;
|
|
159
|
+
const tmp = `${dir}.tmp-${process.pid}`;
|
|
160
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
161
|
+
try {
|
|
162
|
+
unpackCrx(await download(id, entry.sha256), tmp);
|
|
163
|
+
readManifest(tmp);
|
|
164
|
+
rmSync(dir, { recursive: true, force: true });
|
|
165
|
+
renameSync(tmp, dir);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
return dir;
|
|
171
|
+
}
|
|
172
|
+
export async function resolveExtensions(specs, projectDir) {
|
|
173
|
+
return Promise.all((specs ?? []).map((s) => resolveExtension(s, projectDir)));
|
|
174
|
+
}
|
|
175
|
+
export function extensionInfo(dir) {
|
|
176
|
+
const m = readManifest(dir);
|
|
177
|
+
return { name: m.name ?? dir, version: m.version ?? "?" };
|
|
178
|
+
}
|
|
@@ -10,7 +10,6 @@ export declare const SLIDE_DURATION_MS_DEFAULT = 2000;
|
|
|
10
10
|
/** @deprecated Prefer FIXTURE_DURATION_FAST_MS_DEFAULT */
|
|
11
11
|
export declare const SLIDE_DURATION_FAST_MS_DEFAULT = 600;
|
|
12
12
|
export declare function normalizeDemoPace(raw: unknown): DemoPace;
|
|
13
|
-
/** Named / numeric pace -> scale vs normal (1 = normal). Absolute ms (>20) maps via default dwell. */
|
|
14
13
|
export declare function demoPaceScale(pace: DemoPace): number;
|
|
15
14
|
export declare function demoPaceIsBlitz(pace: DemoPace): boolean;
|
|
16
15
|
/** True when pace should use the short-gate / fast fixture path (not slow authored). */
|
|
@@ -34,6 +34,24 @@ export function normalizeDemoPace(raw) {
|
|
|
34
34
|
return "normal";
|
|
35
35
|
}
|
|
36
36
|
/** Named / numeric pace -> scale vs normal (1 = normal). Absolute ms (>20) maps via default dwell. */
|
|
37
|
+
/**
|
|
38
|
+
* What each named pace means, as a multiplier of normal dwell (1 = normal). `demo.speeds` in the config (or
|
|
39
|
+
* `WAYGRAPH_PACE_SPEEDS`, a JSON object) overrides any of them; the defaults are blitz 0.05, fast 0.33, slow 2.
|
|
40
|
+
*/
|
|
41
|
+
function paceSpeeds() {
|
|
42
|
+
const out = { blitz: 0.05, fast: 0.33, slow: 2 };
|
|
43
|
+
try {
|
|
44
|
+
const raw = process.env.WAYGRAPH_PACE_SPEEDS ? JSON.parse(process.env.WAYGRAPH_PACE_SPEEDS) : {};
|
|
45
|
+
for (const k of ["blitz", "fast", "slow"]) {
|
|
46
|
+
if (typeof raw[k] === "number" && raw[k] > 0)
|
|
47
|
+
out[k] = Math.min(10, raw[k]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* ignore a malformed value: defaults apply */
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
37
55
|
export function demoPaceScale(pace) {
|
|
38
56
|
if (typeof pace === "number") {
|
|
39
57
|
if (pace > 20)
|
|
@@ -41,11 +59,11 @@ export function demoPaceScale(pace) {
|
|
|
41
59
|
return Math.min(10, Math.max(0.05, pace));
|
|
42
60
|
}
|
|
43
61
|
if (pace === "blitz")
|
|
44
|
-
return
|
|
62
|
+
return paceSpeeds().blitz;
|
|
45
63
|
if (pace === "fast")
|
|
46
|
-
return
|
|
64
|
+
return paceSpeeds().fast;
|
|
47
65
|
if (pace === "slow")
|
|
48
|
-
return
|
|
66
|
+
return paceSpeeds().slow;
|
|
49
67
|
return 1;
|
|
50
68
|
}
|
|
51
69
|
export function demoPaceIsBlitz(pace) {
|
|
@@ -73,6 +91,9 @@ export function demoPaceIsSlow(pace) {
|
|
|
73
91
|
}
|
|
74
92
|
/** Autoplay / Next-gate ms for this pace (base is WAYGRAPH_AUTOPLAY_MS or 1800). */
|
|
75
93
|
export function demoPaceGateMs(pace, baseAutoplayMs) {
|
|
94
|
+
if (pace === "blitz" && process.env.WAYGRAPH_PACE_SPEEDS) {
|
|
95
|
+
return Math.min(30000, Math.max(20, Math.round(baseAutoplayMs * paceSpeeds().blitz)));
|
|
96
|
+
}
|
|
76
97
|
if (demoPaceIsBlitz(pace))
|
|
77
98
|
return Math.min(80, baseAutoplayMs);
|
|
78
99
|
if (typeof pace === "number" && pace > 20) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,13 +6,13 @@ export { css, byText, byRole } from "./selector.js";
|
|
|
6
6
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
|
|
7
7
|
export type { WaygraphErrorCode } from "./errors.js";
|
|
8
8
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
9
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
9
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, withDialog, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
10
10
|
export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from "./core/branch-regression.js";
|
|
11
11
|
export type { ApiBlockOptions, ApiCallResult } from "./core/blocks/api.js";
|
|
12
|
-
export type { MapStepOpts } from "./core/map-builder.js";
|
|
12
|
+
export type { MapStepOpts, MapFlowOpts } from "./core/map-builder.js";
|
|
13
13
|
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";
|
|
14
14
|
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";
|
|
15
|
-
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 "./core.js";
|
|
15
|
+
export type { Flow, FlowOptions, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./core.js";
|
|
16
16
|
export type { ActionPage } from "./types.js";
|
|
17
17
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
|
|
18
18
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./analysis/graph.js";
|
|
@@ -30,7 +30,9 @@ export type { AutoSessionInit, SessionSnapshot, SessionSnapshotEdge, SessionSnap
|
|
|
30
30
|
export { pilotStart } from "./session/pilot.js";
|
|
31
31
|
export type { PilotStartResult } from "./session/pilot.js";
|
|
32
32
|
export { defineConfig, resolveBaseUrl } from "./config.js";
|
|
33
|
-
export type { WaygraphConfig } from "./config.js";
|
|
33
|
+
export type { WaygraphConfig, ConfigContext, VideoSettings, RunSettings, DemoSettings } from "./config.js";
|
|
34
34
|
export { withWebServer } from "./webserver.js";
|
|
35
|
-
export { chromiumExecutablePath } from "./browser
|
|
35
|
+
export { chromiumExecutablePath } from "./browser.js";
|
|
36
|
+
export { resolveExtension, KNOWN_EXTENSIONS } from "./extensions.js";
|
|
37
|
+
export type { ExtensionSpec } from "./extensions.js";
|
|
36
38
|
export type { WebServerConfig } from "./webserver.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
|
|
|
4
4
|
export { css, byText, byRole } from "./selector.js";
|
|
5
5
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
|
|
6
6
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
7
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
7
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, withDialog, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.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 { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
|
|
10
10
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
@@ -16,4 +16,5 @@ export { AutoSession, stubFilePath } from "./auto-session.js";
|
|
|
16
16
|
export { pilotStart } from "./session/pilot.js";
|
|
17
17
|
export { defineConfig, resolveBaseUrl } from "./config.js";
|
|
18
18
|
export { withWebServer } from "./webserver.js";
|
|
19
|
-
export { chromiumExecutablePath } from "./browser
|
|
19
|
+
export { chromiumExecutablePath } from "./browser.js";
|
|
20
|
+
export { resolveExtension, KNOWN_EXTENSIONS } from "./extensions.js";
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import type { Page } from "@playwright/test";
|
|
2
|
-
type Box = {
|
|
3
|
-
x: number;
|
|
4
|
-
y: number;
|
|
5
|
-
width: number;
|
|
6
|
-
height: number;
|
|
7
|
-
};
|
|
8
|
-
/** Paint the gray "auto" ring (Playwright is about to act here) at a viewport rectangle. */
|
|
9
|
-
export declare function showPilotRect(page: Page, box: Box, label: string, tone?: string): Promise<void>;
|
|
10
2
|
/**
|
|
11
3
|
* Every Playwright instruction a Block runs against this session's page gets a gray (auto) ring + robot
|
|
12
4
|
* label first, so a human watching a pilot window sees WHAT is being driven. `dwellMs` holds the ring
|
|
13
5
|
* long enough to read before the action fires (0 for headless).
|
|
14
6
|
*/
|
|
15
7
|
export declare function instrumentPilotActions(page: Page, dwellMs: number): void;
|
|
16
|
-
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ensureInstalled } from "./shell.js";
|
|
2
2
|
/** Paint the gray "auto" ring (Playwright is about to act here) at a viewport rectangle. */
|
|
3
|
-
|
|
3
|
+
async function showPilotRect(page, box, label, tone = "auto") {
|
|
4
4
|
await ensureInstalled(page);
|
|
5
5
|
await page
|
|
6
6
|
.evaluate(({ box, label, tone }) => {
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { StubPhaseResult } from "../highlights.js";
|
|
2
2
|
import type { PilotHighlightFixtures } from "./fixtures.js";
|
|
3
|
-
/**
|
|
4
|
-
* Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
|
|
5
|
-
* stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
|
|
6
|
-
* (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
|
|
7
|
-
* Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
|
|
8
|
-
*/
|
|
9
|
-
export declare function overlayFixturesToPhase(f: PilotHighlightFixtures): Promise<StubPhaseResult>;
|
|
10
3
|
/**
|
|
11
4
|
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
12
5
|
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
@@ -1,48 +1,3 @@
|
|
|
1
|
-
import { runStubPhase } from "../highlights.js";
|
|
2
|
-
/**
|
|
3
|
-
* Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
|
|
4
|
-
* stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
|
|
5
|
-
* (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
|
|
6
|
-
* Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
|
|
7
|
-
*/
|
|
8
|
-
export async function overlayFixturesToPhase(f) {
|
|
9
|
-
const lifecycle = (ctx) => {
|
|
10
|
-
if (f.clear) {
|
|
11
|
-
ctx.hideTodos();
|
|
12
|
-
ctx.hideDevice();
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
if (f.title !== undefined)
|
|
16
|
-
ctx.title(f.title, f.titlePos ? { pos: f.titlePos } : undefined);
|
|
17
|
-
else if (f.titlePos)
|
|
18
|
-
ctx.titlePos(f.titlePos);
|
|
19
|
-
if (f.bannerUi)
|
|
20
|
-
ctx.bannerUi(f.bannerUi);
|
|
21
|
-
if (f.todos !== undefined) {
|
|
22
|
-
ctx.todos("pilot", f.todos, {
|
|
23
|
-
title: f.todoTitle ?? "Plan",
|
|
24
|
-
index: f.todoIndex ?? 0,
|
|
25
|
-
pos: f.todoPos === "left" ? "left" : "right",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
const ui = f.todoDockUi ?? f.todoUi;
|
|
29
|
-
if (ui)
|
|
30
|
-
ctx.todoDockUi(ui);
|
|
31
|
-
if (f.device)
|
|
32
|
-
ctx.device(f.device);
|
|
33
|
-
if (typeof f.zoom === "number" && Number.isFinite(f.zoom) && f.zoom > 0)
|
|
34
|
-
ctx.zoom(f.zoom);
|
|
35
|
-
if (f.zoomOut !== undefined)
|
|
36
|
-
ctx.zoomOut(f.zoomOut);
|
|
37
|
-
if (f.rings?.length) {
|
|
38
|
-
f.rings.forEach((r, i) => {
|
|
39
|
-
const { selector, label, ...rest } = r;
|
|
40
|
-
ctx.ring(`ring${i + 1}`, { selector, label, ...rest });
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
return runStubPhase({ name: "overlay-fixtures", stubBefore: lifecycle }, "stubBefore");
|
|
45
|
-
}
|
|
46
1
|
/**
|
|
47
2
|
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
48
3
|
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SessionSnapshot } from "../auto-session.js";
|
|
2
2
|
import type { Page } from "@playwright/test";
|
|
3
3
|
/** Minimal shape needed to render the graph tree - matches WaygraphGraph. */
|
|
4
|
-
|
|
4
|
+
interface PilotOverlayGraph {
|
|
5
5
|
nodes: readonly {
|
|
6
6
|
checkpoint: string;
|
|
7
7
|
}[];
|
|
@@ -32,3 +32,4 @@ export interface PilotOverlayInfo {
|
|
|
32
32
|
* operation over.
|
|
33
33
|
*/
|
|
34
34
|
export declare function updatePilotOverlay(page: Page, info: PilotOverlayInfo): Promise<void>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PlaywrightTestConfig } from "@playwright/test";
|
|
2
|
+
import type { WaygraphConfig } from "./config.js";
|
|
3
|
+
/** The test runner's config for a project, built from its waygraph config. `waygraph test` uses this when there is no playwright.config. */
|
|
4
|
+
export declare function playwrightConfigFrom(config: WaygraphConfig, projectDir: string, baseURL: string | undefined): PlaywrightTestConfig;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
/** The test runner's config for a project, built from its waygraph config. `waygraph test` uses this when there is no playwright.config. */
|
|
3
|
+
export function playwrightConfigFrom(config, projectDir, baseURL) {
|
|
4
|
+
const { use, ...rest } = config.playwright ?? {};
|
|
5
|
+
const webServers = [config.webServer ?? []].flat().map((s) => ({ ...s, cwd: join(projectDir, s.cwd ?? ".") }));
|
|
6
|
+
return {
|
|
7
|
+
testDir: join(projectDir, config.testDir ?? "tests"),
|
|
8
|
+
outputDir: join(projectDir, "test-results"),
|
|
9
|
+
fullyParallel: true,
|
|
10
|
+
reporter: [["list"], ["html", { open: "never", outputFolder: join(projectDir, "playwright-report") }]],
|
|
11
|
+
...(webServers.length > 0 ? { webServer: webServers } : {}),
|
|
12
|
+
...rest,
|
|
13
|
+
use: { baseURL, trace: "retain-on-failure", ...use },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Loaded by `waygraph test` (playwright test -c) for projects that keep their settings in config/waygraph.config.ts.
|
|
2
|
+
import { defineConfig } from "@playwright/test";
|
|
3
|
+
import { loadWaygraphConfig, resolveBaseUrl } from "./config.js";
|
|
4
|
+
import { playwrightConfigFrom } from "./playwright-config.js";
|
|
5
|
+
const projectDir = process.env.WAYGRAPH_PROJECT_DIR ?? process.cwd();
|
|
6
|
+
export default defineConfig(playwrightConfigFrom((await loadWaygraphConfig(projectDir)) ?? {}, projectDir, await resolveBaseUrl(projectDir)));
|
package/dist/runner/demo-log.js
CHANGED
|
@@ -109,14 +109,8 @@ function demoTs() {
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
let logSurface = "demo";
|
|
113
|
-
/** Which surface is talking (demo | auto | browser | pilot) - same log format everywhere, only the tag differs. */
|
|
114
|
-
export function setDemoLogSurface(name) {
|
|
115
|
-
logSurface = String(name || "demo");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
112
|
function demoPrefix() {
|
|
119
|
-
return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph
|
|
113
|
+
return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph demo: ";
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
export function demoLog(msg) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** In-page installer (page.evaluate target) - see core.js's own header. Installs the shared ring
|
|
2
|
-
* primitive (__wgEnsureRingEl/__wgPaintRingAt
|
|
2
|
+
* primitive (__wgEnsureRingEl/__wgPaintRingAt), cursor, click-pulse, focus veil,
|
|
3
3
|
* and the #wg-ring singleton every surface's "current action" ring uses. */
|
|
4
4
|
export function installCore(args: Record<string, unknown>): void;
|
|
@@ -72,7 +72,14 @@ export function installCore({ title, favicon, bannerPos, todoPos, envAutoplay, t
|
|
|
72
72
|
ring.style.width = width + "px";
|
|
73
73
|
ring.style.height = height + "px";
|
|
74
74
|
ring.style.opacity = "1";
|
|
75
|
-
|
|
75
|
+
if (labelId === "wg-ring-label") {
|
|
76
|
+
// The demo ring's caption is drawn from data-label (CSS ::after), not DOM text, so the page's own
|
|
77
|
+
// text selectors (getByText, text=...) never match waygraph's overlay twice.
|
|
78
|
+
ringLabel.dataset.label = label || "";
|
|
79
|
+
ringLabel.textContent = "";
|
|
80
|
+
} else {
|
|
81
|
+
ringLabel.textContent = label || "";
|
|
82
|
+
}
|
|
76
83
|
ringLabel.style.opacity = "1";
|
|
77
84
|
const lw = ringLabel.offsetWidth;
|
|
78
85
|
const lh = ringLabel.offsetHeight;
|
|
@@ -104,12 +111,6 @@ export function installCore({ title, favicon, bannerPos, todoPos, envAutoplay, t
|
|
|
104
111
|
ringLabel.style.top = labelTop + "px";
|
|
105
112
|
return [ring, ringLabel];
|
|
106
113
|
};
|
|
107
|
-
window.__wgHideRingAt = (ringId, labelId) => {
|
|
108
|
-
const ring = __wgById(ringId);
|
|
109
|
-
const label = __wgById(labelId);
|
|
110
|
-
if (ring) ring.style.opacity = "0";
|
|
111
|
-
if (label) label.style.opacity = "0";
|
|
112
|
-
};
|
|
113
114
|
window.__wgEnsureRingEl("wg-ring", "wg-ring-label");
|
|
114
115
|
if (!__wgById("wg-cursor")) {
|
|
115
116
|
const cursor = document.createElement("div");
|
|
@@ -21,7 +21,36 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
21
21
|
// (shorter Next/autoplay gates) and must NOT skip theater, or the whole demo
|
|
22
22
|
// looks like plain waygraph run.
|
|
23
23
|
const skipTheater = () => !!pacing.skipTheater;
|
|
24
|
-
|
|
24
|
+
// `slowMo` here is the demo's own beat: ms to rest after each action (once per call, never per keystroke). The browser
|
|
25
|
+
// itself is launched with slowMo 0 in step mode, so a step whose pace is blitz really does run at full speed.
|
|
26
|
+
const beat = () => (skipTheater() ? 0 : slowMo || 0);
|
|
27
|
+
const pause = (ms) => (ms > 0 ? new Promise((res) => setTimeout(res, ms)) : undefined);
|
|
28
|
+
// Per-character typing delay (WAYGRAPH_TYPING_MS, default 30). 0 for blitz steps.
|
|
29
|
+
const typingMs = () => {
|
|
30
|
+
if (skipTheater()) return 0;
|
|
31
|
+
const n = Number(process.env.WAYGRAPH_TYPING_MS);
|
|
32
|
+
return process.env.WAYGRAPH_TYPING_MS !== undefined && Number.isFinite(n) && n >= 0 ? n : 30;
|
|
33
|
+
};
|
|
34
|
+
const typeDelay = typingMs;
|
|
35
|
+
const keyboard = page.keyboard;
|
|
36
|
+
if (!keyboard.__wgBeatPatched) {
|
|
37
|
+
keyboard.__wgBeatPatched = true;
|
|
38
|
+
const originalType = keyboard.type.bind(keyboard);
|
|
39
|
+
keyboard.type = async (text, options) => {
|
|
40
|
+
await originalType(text, { delay: typingMs(), ...(options || {}) });
|
|
41
|
+
await pause(beat());
|
|
42
|
+
};
|
|
43
|
+
const originalPress = keyboard.press.bind(keyboard);
|
|
44
|
+
keyboard.press = async (key, options) => {
|
|
45
|
+
await originalPress(key, options);
|
|
46
|
+
await pause(beat());
|
|
47
|
+
};
|
|
48
|
+
const originalInsertText = keyboard.insertText.bind(keyboard);
|
|
49
|
+
keyboard.insertText = async (text) => {
|
|
50
|
+
await originalInsertText(text);
|
|
51
|
+
await pause(beat());
|
|
52
|
+
};
|
|
53
|
+
}
|
|
25
54
|
// Click is a single action, not per-character, so it doesn't compound
|
|
26
55
|
// the same way - but slowMo still adds its own pause around the actual
|
|
27
56
|
// click, so trim our own explicit "pop" pauses when it's already active
|
|
@@ -155,6 +184,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
155
184
|
// it - it was sticking around through the whole rest of the step.
|
|
156
185
|
await hideRing(page);
|
|
157
186
|
await restoreTheaterAfterNavigation(page, todoDockRef, deviceRef, stubBeforeRef);
|
|
187
|
+
await pause(beat());
|
|
158
188
|
return result;
|
|
159
189
|
};
|
|
160
190
|
}
|
|
@@ -285,6 +315,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
285
315
|
await hideRing(page);
|
|
286
316
|
// Full document navigation drops #wg-todo-dock + rings; restore carry.
|
|
287
317
|
await restoreTheaterAfterNavigation(page, todoDockRef, deviceRef, stubBeforeRef);
|
|
318
|
+
await pause(beat());
|
|
288
319
|
return result;
|
|
289
320
|
};
|
|
290
321
|
}
|