waygraph 0.16.1 → 0.16.4
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 +12 -0
- package/dist/analysis/practices-check.d.ts +1 -1
- package/dist/analysis/practices-check.js +36 -0
- package/dist/beta.d.ts +6 -0
- package/dist/beta.js +5 -0
- package/dist/cli/commands/codemod-flows.js +2 -2
- package/dist/cli/commands/codemod-stubs.js +132 -2
- package/dist/cli/commands/pilot.js +3 -16
- package/dist/cli/commands/session-ops.js +26 -1
- package/dist/cli/flags.d.ts +1 -1
- package/dist/cli/flags.js +1 -1
- package/dist/cli/run-chain.js +2 -2
- package/dist/cli/usage.js +6 -0
- package/dist/cli/util.js +1 -1
- package/dist/demo/narration/demo-pace.d.ts +1 -1
- package/dist/demo/narration/demo-pace.js +1 -1
- package/dist/demo/narration/merge.d.ts +1 -1
- package/dist/demo/narration/merge.js +1 -1
- package/dist/demo/narration/narration.d.ts +2 -6
- package/dist/demo/narration/narration.js +5 -17
- package/dist/demo/narration/stub-ctx.js +126 -124
- package/dist/demo/overlay/cursor.js +1 -1
- package/dist/demo/overlay/inpage/banner.js +5 -0
- package/dist/demo/overlay/inpage/core.js +1 -6
- package/dist/demo/overlay/inpage/network.js +1 -1
- package/dist/demo/overlay/inpage/todos.js +0 -15
- package/dist/demo/overlay/network-capture.js +2 -2
- package/dist/demo/overlay/overlay-css.js +19 -76
- package/dist/demo/overlay/overlay-stage.js +1 -1
- package/dist/demo/overlay/overlay.js +3 -2
- package/dist/demo/overlay/phase-fixtures.js +5 -5
- package/dist/demo/overlay/pilot/shell.js +7 -5
- package/dist/demo/overlay/rings.d.ts +0 -8
- package/dist/demo/overlay/rings.js +0 -15
- package/dist/demo/overlay/ui/components/ring.js +2 -2
- package/dist/demo/overlay/ui/overlay.css +136 -33
- package/dist/demo/overlay/ui/shadow.js +69 -2
- package/dist/demo/stepper/after.js +4 -17
- package/dist/demo/stepper/before.js +3 -1
- package/dist/demo/stepper/demo-log.js +8 -8
- package/dist/demo/stepper/interaction-patch.js +3 -15
- package/dist/demo/stepper/panel-after.js +1 -1
- package/dist/demo/stepper/slides.d.ts +3 -0
- package/dist/demo/stepper/slides.js +18 -2
- package/dist/demo/stepper/stepper.js +1 -1
- package/dist/demo/stepper/teardown.js +0 -1
- package/dist/demo/verbose-log.js +1 -1
- package/dist/engine/block/block.d.ts +1 -12
- package/dist/engine/block/kind.d.ts +3 -0
- package/dist/engine/block/kind.js +11 -0
- package/dist/engine/block/kinds/method.d.ts +0 -2
- package/dist/engine/block/kinds/method.js +0 -1
- package/dist/engine/block/kinds/nav.js +1 -1
- package/dist/engine/errors.d.ts +12 -0
- package/dist/engine/errors.js +12 -0
- package/dist/engine/read/read-block.d.ts +61 -0
- package/dist/engine/read/read-block.js +94 -0
- package/dist/engine/read/read-key.d.ts +32 -0
- package/dist/engine/read/read-key.js +34 -0
- package/dist/engine/read/read-page.d.ts +19 -0
- package/dist/engine/read/read-page.js +50 -0
- package/dist/engine/read/readers.d.ts +82 -0
- package/dist/engine/read/readers.js +43 -0
- package/dist/engine/read/run-read.d.ts +17 -0
- package/dist/engine/read/run-read.js +156 -0
- package/dist/engine/run/connect.js +3 -2
- package/dist/engine/run/events.d.ts +28 -10
- package/dist/engine/run/events.js +52 -38
- package/dist/engine/run/run.d.ts +1 -19
- package/dist/engine/run/run.js +21 -97
- package/dist/engine/stub/ring.d.ts +2 -4
- package/dist/engine/stub/ring.js +1 -0
- package/dist/engine/stub/stub.d.ts +38 -25
- package/dist/index.d.ts +9 -7
- package/dist/index.js +11 -6
- package/dist/live/auto/auto.js +2 -3
- package/dist/live/auto/menu.js +3 -2
- package/dist/live/auto-session/auto-session.d.ts +9 -2
- package/dist/live/auto-session/auto-session.js +23 -20
- package/dist/live/auto-session/feed.d.ts +16 -0
- package/dist/live/auto-session/feed.js +71 -0
- package/dist/live/auto-session/helpers.d.ts +7 -0
- package/dist/live/auto-session/helpers.js +9 -0
- package/dist/live/auto-session/ipc.d.ts +15 -47
- package/dist/live/auto-session/ipc.js +34 -4
- package/dist/live/auto-session/protocol.d.ts +127 -0
- package/dist/live/auto-session/protocol.js +6 -0
- package/dist/live/auto-session/read.d.ts +25 -0
- package/dist/live/auto-session/read.js +50 -0
- package/dist/live/traverse/worker.d.ts +1 -0
- package/dist/live/traverse/worker.js +6 -1
- package/dist/manifest/manifest.d.ts +22 -2
- package/dist/manifest/manifest.js +27 -1
- package/dist/manifest/project.d.ts +10 -1
- package/dist/manifest/project.js +9 -12
- package/dist/manifest/read-blocks.d.ts +2 -0
- package/dist/manifest/read-blocks.js +23 -0
- package/dist/manifest/scan.d.ts +3 -2
- package/dist/manifest/scan.js +8 -4
- package/dist/runner/plan/find.js +2 -2
- package/dist/runner/plan/plan.js +1 -1
- package/dist/runner/plan/seed-mem.js +5 -5
- package/dist/runner/plan/spec.js +1 -1
- package/dist/runner/runner.d.ts +0 -1
- package/dist/runner/runner.js +6 -6
- package/dist/runtime/full-launch.d.ts +2 -1
- package/dist/runtime/full-launch.js +29 -27
- package/dist/runtime/run-log.d.ts +2 -0
- package/dist/runtime/run-log.js +13 -6
- package/examples/saucedemo/README.md +2 -2
- package/examples/saucedemo/src/blocks/SITE-MAP.md +3 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.read.block.ts +26 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/examples/saucedemo/src/blocks/saucedemo-web/header.read.block.ts +25 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.read.block.ts +28 -0
- package/examples/saucedemo/src/flows/api-demo.flow.ts +1 -1
- package/examples/saucedemo/src/flows/cart-bulk.flow.ts +1 -1
- package/examples/saucedemo/src/flows/cart-continue.flow.ts +1 -1
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +3 -2
- package/examples/saucedemo/src/flows/checkout.flow.ts +13 -12
- package/examples/saucedemo/src/flows/login.flow.ts +1 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +3 -2
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +1 -1
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
- package/examples/saucedemo/src/states/checkout.reads.ts +9 -0
- package/examples/saucedemo/src/stubs/device-showcase.ts +70 -76
- package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
- package/examples/saucedemo/src/stubs/login.ts +5 -5
- package/package.json +5 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/templates/quickstart/src/flows/api-demo.flow.ts +1 -1
- package/templates/quickstart/src/flows/cart-bulk.flow.ts +1 -1
- package/templates/quickstart/src/flows/cart-continue.flow.ts +1 -1
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +3 -2
- package/templates/quickstart/src/flows/checkout.flow.ts +13 -12
- package/templates/quickstart/src/flows/login.flow.ts +1 -1
- package/templates/quickstart/src/flows/shop.flow.ts +3 -2
- package/templates/quickstart/src/flows/stub-on-error.flow.ts +1 -1
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
- package/templates/quickstart/src/stubs/device-showcase.ts +70 -76
- package/templates/quickstart/src/stubs/finish-order.ts +38 -0
- package/templates/quickstart/src/stubs/login.ts +5 -5
- package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +1 -1
- package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +1 -1
- package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +1 -1
- package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +4 -4
- package/templates/scaffold/src/flows/README.md +1 -1
- package/templates/scaffold/src/flows/home/cart.flow.ts +7 -7
- package/templates/scaffold/src/flows/home/hello.flow.ts +10 -7
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
- package/templates/skills/waygraph-convention.skill.md +10 -2
- package/templates/skills/waygraph-pilot.skill.md +7 -0
package/dist/runner/plan/find.js
CHANGED
|
@@ -32,11 +32,11 @@ export async function findBlock(projectDir, ref) {
|
|
|
32
32
|
if (byRuntimeName.length === 1)
|
|
33
33
|
return byRuntimeName[0];
|
|
34
34
|
if (byRuntimeName.length > 1) {
|
|
35
|
-
throw new Error("waygraph
|
|
35
|
+
throw new Error("waygraph run --blocks: \"" + ref + "\" matches " + byRuntimeName.length + " Blocks by name - " +
|
|
36
36
|
byRuntimeName.map((b) => b.exportName + " (" + b.file + ")").join(", ") +
|
|
37
37
|
". Use the export name to disambiguate.");
|
|
38
38
|
}
|
|
39
|
-
throw new Error("waygraph
|
|
39
|
+
throw new Error("waygraph run --blocks: no Block named \"" + ref + "\" found under " + projectDir);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Finds an already-defined Flow by its export name (e.g. loginFlow) among
|
package/dist/runner/plan/plan.js
CHANGED
|
@@ -24,7 +24,7 @@ async function findBareFlow(projectDir, spec) {
|
|
|
24
24
|
function parseSegments(spec) {
|
|
25
25
|
const segments = parseChainSpec(spec);
|
|
26
26
|
if (segments.length === 0)
|
|
27
|
-
throw new Error("waygraph
|
|
27
|
+
throw new Error("waygraph run --blocks: empty spec - give at least one block name");
|
|
28
28
|
return segments;
|
|
29
29
|
}
|
|
30
30
|
async function resolveSegment(projectDir, seg, mem, options) {
|
|
@@ -4,7 +4,7 @@ const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v)
|
|
|
4
4
|
function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
5
5
|
if (requires.length === 0) {
|
|
6
6
|
if (json !== undefined) {
|
|
7
|
-
throw new Error("waygraph
|
|
7
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" takes no input (empty requires) but got a payload: " + json);
|
|
8
8
|
}
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
@@ -22,7 +22,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
22
22
|
haveParsed = true;
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
25
|
-
throw new Error("waygraph
|
|
25
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" payload is not valid JSON - " + String(err));
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
if (!haveParsed) {
|
|
@@ -30,7 +30,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
30
30
|
// this flow; when it is, fall through with an empty object so the
|
|
31
31
|
// per-key loop below gets a chance to fill each one from the registry.
|
|
32
32
|
if (!stubActive) {
|
|
33
|
-
throw new Error("waygraph
|
|
33
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.map((k) => k.name).join(", ") +
|
|
34
34
|
" - give a JSON payload (inline blockName({...}) or --data '{...}'), or enable --mem-stub" +
|
|
35
35
|
" with registerMemStub for these keys");
|
|
36
36
|
}
|
|
@@ -54,7 +54,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
if (!isRecord(parsed)) {
|
|
57
|
-
throw new Error("waygraph
|
|
57
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.length + " keys (" +
|
|
58
58
|
requires.map((k) => k.name).join(", ") + ") - payload must be an object keyed by each key's name");
|
|
59
59
|
}
|
|
60
60
|
const payload = parsed;
|
|
@@ -72,7 +72,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
|
|
|
72
72
|
stillMissing.push(k);
|
|
73
73
|
}
|
|
74
74
|
if (stillMissing.length > 0) {
|
|
75
|
-
throw new Error("waygraph
|
|
75
|
+
throw new Error("waygraph run --blocks: \"" + label + "\" payload is missing required key(s): " +
|
|
76
76
|
stillMissing.map((k) => "\"" + k.name + "\"").join(", "));
|
|
77
77
|
}
|
|
78
78
|
}
|
package/dist/runner/plan/spec.js
CHANGED
|
@@ -8,7 +8,7 @@ export function parseChainSpec(spec) {
|
|
|
8
8
|
.map((s) => {
|
|
9
9
|
const m = /^([A-Za-z_$][\w-]*)\s*(?:\(([\s\S]*)\))?$/.exec(s);
|
|
10
10
|
if (!m) {
|
|
11
|
-
throw new Error("waygraph
|
|
11
|
+
throw new Error("waygraph run --blocks: could not parse segment \"" + s + "\" - expected \"blockName\" or \"blockName({...json...})\"");
|
|
12
12
|
}
|
|
13
13
|
const ref = m[1];
|
|
14
14
|
const json = m[2] ? m[2].trim() : undefined;
|
package/dist/runner/runner.d.ts
CHANGED
package/dist/runner/runner.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Playwright is used) with the project dir and the spec ("cartFlow", "loginFlow then add-item"). Every mode plays the
|
|
3
3
|
// same steps through the same engine; the mode decides what happens around each step.
|
|
4
4
|
import { MemPage } from "../engine/mem/mem.js";
|
|
5
|
-
import {
|
|
5
|
+
import { inRun } from "../engine/run/events.js";
|
|
6
6
|
import { withWebServer } from "../runtime/webserver.js";
|
|
7
|
-
import "../runtime/run-log.js";
|
|
7
|
+
import { installRunLog } from "../runtime/run-log.js";
|
|
8
|
+
import { loadReadBlocks } from "../manifest/read-blocks.js";
|
|
8
9
|
import { planSteps } from "./plan/plan.js";
|
|
9
10
|
import { readOptions, slowMoFor } from "./options.js";
|
|
10
11
|
import { openSession } from "./session.js";
|
|
@@ -16,6 +17,7 @@ import { reportMode } from "./modes/report.js";
|
|
|
16
17
|
export async function play(projectDir, spec) {
|
|
17
18
|
const options = readOptions();
|
|
18
19
|
const mem = new MemPage();
|
|
20
|
+
await loadReadBlocks(projectDir);
|
|
19
21
|
const { steps, flows } = await planSteps(projectDir, spec, mem, options);
|
|
20
22
|
if (options.mode === "branches")
|
|
21
23
|
return playBranches(flows, mem, options);
|
|
@@ -52,12 +54,10 @@ function finish(outcome) {
|
|
|
52
54
|
}
|
|
53
55
|
/** The process entry the CLI's bootstrap calls: one run (and one run log) per invocation. */
|
|
54
56
|
export function startRunner() {
|
|
57
|
+
installRunLog();
|
|
55
58
|
const projectDir = process.argv[2] ?? process.cwd();
|
|
56
59
|
const spec = process.argv[3] ?? "";
|
|
57
|
-
|
|
58
|
-
withWebServer(projectDir, () => play(projectDir, spec))
|
|
59
|
-
.then(() => endRun(opened, {}), (err) => {
|
|
60
|
-
endRun(opened, { error: err });
|
|
60
|
+
inRun(spec, () => withWebServer(projectDir, () => play(projectDir, spec))).catch((err) => {
|
|
61
61
|
console.error(err && err.stack ? err.stack : String(err));
|
|
62
62
|
process.exitCode = 1;
|
|
63
63
|
});
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
/** How an Engine launches its own browser: the selected config (--config), the Chromium override, the extensions. Called by the package entry. */
|
|
2
|
+
export declare function installFullLaunch(): void;
|
|
@@ -4,31 +4,33 @@ import { chromiumExecutablePath, extensionPathsFromEnv, launchContext } from "./
|
|
|
4
4
|
import { resolveExtensions } from "./extensions.js";
|
|
5
5
|
import { loadWaygraphConfig } from "./waygraph-config.js";
|
|
6
6
|
import { envString } from "../engine/env.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
/** How an Engine launches its own browser: the selected config (--config), the Chromium override, the extensions. Called by the package entry. */
|
|
8
|
+
export function installFullLaunch() {
|
|
9
|
+
setOwnContextLauncher(async (engineConfig) => {
|
|
10
|
+
// A config chosen with `--config` / WAYGRAPH_CONFIG wins over the one the Engine was built with.
|
|
11
|
+
const chosen = envString("WAYGRAPH_CONFIG") ? await loadWaygraphConfig(process.cwd()) : undefined;
|
|
12
|
+
const config = chosen
|
|
13
|
+
? {
|
|
14
|
+
...engineConfig,
|
|
15
|
+
...(chosen.baseUrl !== undefined ? { baseUrl: chosen.baseUrl } : {}),
|
|
16
|
+
...(chosen.extensions !== undefined ? { extensions: chosen.extensions } : {}),
|
|
17
|
+
}
|
|
18
|
+
: engineConfig;
|
|
19
|
+
const browserName = config?.browserName ?? "chromium";
|
|
20
|
+
const executablePath = browserName === "chromium" ? chromiumExecutablePath() : undefined;
|
|
21
|
+
const baseURL = config?.baseUrl ?? envString("WAYGRAPH_BASE_URL");
|
|
22
|
+
return launchContext({
|
|
23
|
+
launcher: resolveLauncher(browserName, config),
|
|
24
|
+
launch: {
|
|
25
|
+
headless: config?.headless ?? true,
|
|
26
|
+
...(config?.slowMo !== undefined ? { slowMo: config.slowMo } : {}),
|
|
27
|
+
...(executablePath ? { executablePath } : {}),
|
|
28
|
+
},
|
|
29
|
+
context: {
|
|
30
|
+
...(baseURL ? { baseURL } : {}),
|
|
31
|
+
...(config?.recordVideo ? { recordVideo: config.recordVideo } : {}),
|
|
32
|
+
},
|
|
33
|
+
extensions: await resolveExtensions(config?.extensions ?? extensionPathsFromEnv(), process.cwd()),
|
|
34
|
+
});
|
|
33
35
|
});
|
|
34
|
-
}
|
|
36
|
+
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** Starts writing run events to the run log. Called by the package entry and the runner; a second call does nothing. */
|
|
2
|
+
export declare function installRunLog(): void;
|
|
1
3
|
/** The folder run logs go to for a project unless the config or the environment says otherwise. */
|
|
2
4
|
export declare function defaultRunLogDir(projectDir: string): string;
|
package/dist/runtime/run-log.js
CHANGED
|
@@ -4,13 +4,20 @@ import { appendFileSync, mkdirSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { onRunEvent } from "../engine/run/events.js";
|
|
6
6
|
import { envString } from "../engine/env.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
let installed = false;
|
|
8
|
+
/** Starts writing run events to the run log. Called by the package entry and the runner; a second call does nothing. */
|
|
9
|
+
export function installRunLog() {
|
|
10
|
+
if (installed)
|
|
10
11
|
return;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
installed = true;
|
|
13
|
+
onRunEvent((event) => {
|
|
14
|
+
const dir = envString("WAYGRAPH_RUN_LOG");
|
|
15
|
+
if (!dir || dir === "0")
|
|
16
|
+
return;
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
appendFileSync(join(dir, `${event.runId}.jsonl`), JSON.stringify(event) + "\n");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
14
21
|
/** The folder run logs go to for a project unless the config or the environment says otherwise. */
|
|
15
22
|
export function defaultRunLogDir(projectDir) {
|
|
16
23
|
return join(projectDir, ".waygraph", "runs");
|
|
@@ -13,7 +13,7 @@ the **reference** for waygraph consumer conventions:
|
|
|
13
13
|
- `defineApiBlock` - a real backend/HTTP check as one step in the same Flow as
|
|
14
14
|
a UI step, with its own live network-activity panel during a demo - see
|
|
15
15
|
`apiDemoFlow` (`src/flows/api-demo.flow.ts`)
|
|
16
|
-
- `ctx.caption()` (bottom subtitle-bar) + `ctx.titlePos()` (banner
|
|
16
|
+
- `ctx.ui.caption()` (bottom subtitle-bar) + `ctx.ui.titlePos()` (banner
|
|
17
17
|
left/center/right) - also on `checkoutFlow`
|
|
18
18
|
|
|
19
19
|
**GitHub Pages walkthrough:**
|
|
@@ -48,7 +48,7 @@ npx playwright install chromium
|
|
|
48
48
|
| **Device showcase** (mobile/tablet + portrait/landscape; narration in `src/stubs/device-showcase.ts`) | `npm run demo:device` |
|
|
49
49
|
| Device showcase + `.webm` | `npm run demo:video` |
|
|
50
50
|
| **Cart bulk** demo (add-all → remove-all) | `npm run demo:bulk` |
|
|
51
|
-
| **Owner checkout** (inline `{ ff: true }`, `ctx.caption()`, banner position) | `npm run demo:checkout` |
|
|
51
|
+
| **Owner checkout** (inline `{ ff: true }`, `ctx.ui.caption()`, banner position) | `npm run demo:checkout` |
|
|
52
52
|
| **FE + BE in one Flow** (`defineApiBlock`, live network panel) | `npm run demo:api` |
|
|
53
53
|
| Record (headless `.webm`) | `npm run record` |
|
|
54
54
|
| List flows | `npm run flows` |
|
|
@@ -9,6 +9,7 @@ src/blocks/
|
|
|
9
9
|
saucedemo-web/ # URL /
|
|
10
10
|
NAV.md
|
|
11
11
|
nav-login.block.ts # Page definePageBlock url:/
|
|
12
|
+
header.read.block.ts # Read defineReadBlock (beta, every page: title, cart count)
|
|
12
13
|
methods/
|
|
13
14
|
fill-username.method.block.ts # Method defineMethodBlock (self-loop on LoginPage)
|
|
14
15
|
fill-password.method.block.ts # Method defineMethodBlock (self-loop on LoginPage)
|
|
@@ -18,6 +19,7 @@ src/blocks/
|
|
|
18
19
|
login.sel.ts # LoginSel (not a Block)
|
|
19
20
|
inventory/ # URL /inventory.html
|
|
20
21
|
inventory.page.block.ts # Page definePageBlock
|
|
22
|
+
inventory.read.block.ts # Read defineReadBlock (beta: products, prices, in cart, sort)
|
|
21
23
|
NAV.md
|
|
22
24
|
nav-item-detail.block.ts # Nav defineMemNavBlock (instance row)
|
|
23
25
|
methods/
|
|
@@ -36,6 +38,7 @@ src/blocks/
|
|
|
36
38
|
NAV.md
|
|
37
39
|
cart.sel.ts # CartSel (not a Block)
|
|
38
40
|
nav-cart.block.ts # Nav click header cart
|
|
41
|
+
cart.read.block.ts # Read defineReadBlock (beta: the cart's lines)
|
|
39
42
|
nav-checkout-info.block.ts
|
|
40
43
|
checkout-step-one/ # URL /checkout-step-one.html
|
|
41
44
|
NAV.md
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineReadBlock } from "waygraph/beta";
|
|
2
|
+
import { CartPage } from "../../../states/checkout.states.js";
|
|
3
|
+
import { Cart } from "../../../states/checkout.reads.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kind: Read (beta)
|
|
7
|
+
* Helper: defineReadBlock
|
|
8
|
+
* Route: saucedemo-web/cart/ (= /cart.html)
|
|
9
|
+
*
|
|
10
|
+
* The cart's lines: name, quantity and price.
|
|
11
|
+
*/
|
|
12
|
+
export const CartReadBlock = defineReadBlock({
|
|
13
|
+
name: "read-cart",
|
|
14
|
+
description: "Each line in the cart: name, quantity, price.",
|
|
15
|
+
from: CartPage,
|
|
16
|
+
instruction: {
|
|
17
|
+
read: (ctx) =>
|
|
18
|
+
Cart({
|
|
19
|
+
lines: ctx.page.list(".cart_item", (row) => ({
|
|
20
|
+
name: row.text(".inventory_item_name"),
|
|
21
|
+
qty: row.number(".cart_quantity"),
|
|
22
|
+
price: row.number(".inventory_item_price"),
|
|
23
|
+
})),
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineMethodBlock, Trait
|
|
1
|
+
import { defineMethodBlock, Trait } from "waygraph";
|
|
2
2
|
import {
|
|
3
3
|
CheckoutOverviewPage,
|
|
4
4
|
OrderComplete,
|
|
@@ -21,9 +21,7 @@ export const FinishOrderBlock = defineMethodBlock({
|
|
|
21
21
|
instruction: {
|
|
22
22
|
async act(page) {
|
|
23
23
|
const finishButton = page.locator(CheckoutOverviewSel.finishBtn);
|
|
24
|
-
await
|
|
25
|
-
finishButton.click(),
|
|
26
|
-
);
|
|
24
|
+
await finishButton.click();
|
|
27
25
|
},
|
|
28
26
|
verify: [
|
|
29
27
|
Trait.text(
|
|
@@ -31,35 +29,5 @@ export const FinishOrderBlock = defineMethodBlock({
|
|
|
31
29
|
"Thank you for your order!",
|
|
32
30
|
),
|
|
33
31
|
],
|
|
34
|
-
// Shown AFTER act (order already placed) - selectors must match the
|
|
35
|
-
// completion page, not #finish on the overview.
|
|
36
|
-
slides: [
|
|
37
|
-
{
|
|
38
|
-
caption: "Checkout is a short pipeline",
|
|
39
|
-
detail: "Cart -> info -> overview -> done. You just placed the order.",
|
|
40
|
-
tag: "YAP",
|
|
41
|
-
tone: "info",
|
|
42
|
-
duration: true,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
caption: "Completion page is the durable proof",
|
|
46
|
-
detail:
|
|
47
|
-
"tone: success - the thank-you header is what verify asserts on.",
|
|
48
|
-
tag: "YAP",
|
|
49
|
-
tone: "success",
|
|
50
|
-
selector: CheckoutOverviewSel.completeHeader,
|
|
51
|
-
duration: 2500,
|
|
52
|
-
fastMode: 700,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
caption: "Episode continues after this yap",
|
|
56
|
-
detail: "Next step rings the same banner, then you move on.",
|
|
57
|
-
tag: "YAP",
|
|
58
|
-
tone: "planned",
|
|
59
|
-
selector: CheckoutOverviewSel.completeHeader,
|
|
60
|
-
duration: true,
|
|
61
|
-
fastMode: 500,
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
32
|
},
|
|
65
33
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineReadBlock } from "waygraph/beta";
|
|
2
|
+
import { Header } from "../../states/checkout.reads.js";
|
|
3
|
+
import { InventorySel } from "./inventory/methods/inventory-items.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kind: Read (beta)
|
|
7
|
+
* Helper: defineReadBlock
|
|
8
|
+
* Route: saucedemo-web/ (every signed-in page)
|
|
9
|
+
*
|
|
10
|
+
* The header every page shares: the page title and how many items are in the cart (0 when the badge is not shown).
|
|
11
|
+
*/
|
|
12
|
+
export const HeaderReadBlock = defineReadBlock({
|
|
13
|
+
name: "read-header",
|
|
14
|
+
description: "The page title and the cart count.",
|
|
15
|
+
instruction: {
|
|
16
|
+
read: (ctx) =>
|
|
17
|
+
Header({
|
|
18
|
+
title: ctx.page.text(".title"),
|
|
19
|
+
cartCount: ctx.page.fn(async (page) => {
|
|
20
|
+
const badge = page.locator(InventorySel.cartBadge);
|
|
21
|
+
return (await badge.count()) ? Number(await badge.first().innerText()) : 0;
|
|
22
|
+
}),
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineReadBlock } from "waygraph/beta";
|
|
2
|
+
import { ItemInCart, LoggedIn } from "../../../states/checkout.states.js";
|
|
3
|
+
import { Catalog } from "../../../states/checkout.reads.js";
|
|
4
|
+
import { InventorySel } from "./methods/inventory-items.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kind: Read (beta)
|
|
8
|
+
* Helper: defineReadBlock
|
|
9
|
+
* Route: saucedemo-web/inventory/ (= /inventory.html)
|
|
10
|
+
*
|
|
11
|
+
* The catalogue as data: every product with its price and whether it is in the cart, and the sort order.
|
|
12
|
+
*/
|
|
13
|
+
export const InventoryReadBlock = defineReadBlock({
|
|
14
|
+
name: "read-inventory",
|
|
15
|
+
description: "Every product, its price and whether it is in the cart; the sort order.",
|
|
16
|
+
from: [LoggedIn, ItemInCart],
|
|
17
|
+
instruction: {
|
|
18
|
+
read: (ctx) =>
|
|
19
|
+
Catalog({
|
|
20
|
+
items: ctx.page.list(InventorySel.card, (row) => ({
|
|
21
|
+
name: row.text(InventorySel.name),
|
|
22
|
+
price: row.number(".inventory_item_price"),
|
|
23
|
+
inCart: row.visible(InventorySel.removeBtnPrefix),
|
|
24
|
+
})),
|
|
25
|
+
sort: ctx.page.value(".product_sort_container"),
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -32,7 +32,7 @@ export const apiDemoEpisode = engine
|
|
|
32
32
|
.start()
|
|
33
33
|
.method(
|
|
34
34
|
CheckBackendHealth.stubBefore((ctx) => {
|
|
35
|
-
ctx.title("Checking backend health · real HTTP request, no browser");
|
|
35
|
+
ctx.ui.title("Checking backend health · real HTTP request, no browser");
|
|
36
36
|
}),
|
|
37
37
|
)
|
|
38
38
|
.end();
|
|
@@ -7,7 +7,7 @@ export const cartContinueFlow = engine
|
|
|
7
7
|
.map()
|
|
8
8
|
.start()
|
|
9
9
|
.gotoPage(
|
|
10
|
-
NavCartBlock.stubBefore((ctx) => ctx.title("Cart then continue shopping")),
|
|
10
|
+
NavCartBlock.stubBefore((ctx) => ctx.ui.title("Cart then continue shopping")),
|
|
11
11
|
)
|
|
12
12
|
.gotoPage(NavContinueShoppingBlock)
|
|
13
13
|
.end();
|
|
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
12
12
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
13
13
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
14
14
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
15
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
15
16
|
import { NavBackToProductsBlock } from "../blocks/saucedemo-web/inventory-item/nav-back-to-products.block.js";
|
|
16
17
|
|
|
17
18
|
/** Full checkout, then the "Back Home" click-based Nav Block returns to the catalogue. */
|
|
@@ -20,7 +21,7 @@ export const checkoutThenBrowseAgainFlow = engine
|
|
|
20
21
|
.start()
|
|
21
22
|
.gotoPage(
|
|
22
23
|
NavLoginBlock.stubBefore((ctx) =>
|
|
23
|
-
ctx.title(
|
|
24
|
+
ctx.ui.title(
|
|
24
25
|
"Owner: Checkout, then click Back Home (click-based NavBlock demo)",
|
|
25
26
|
),
|
|
26
27
|
),
|
|
@@ -35,6 +36,6 @@ export const checkoutThenBrowseAgainFlow = engine
|
|
|
35
36
|
.method(FillLastNameBlock)
|
|
36
37
|
.method(FillPostalCodeBlock)
|
|
37
38
|
.method(SubmitCheckoutInfoBlock)
|
|
38
|
-
.method(FinishOrderBlock)
|
|
39
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
39
40
|
.gotoPage(NavBackToProductsBlock)
|
|
40
41
|
.end();
|
|
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
12
12
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
13
13
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
14
14
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
15
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
15
16
|
import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
|
|
16
17
|
|
|
17
18
|
export const checkoutFlow = engine
|
|
@@ -20,7 +21,7 @@ export const checkoutFlow = engine
|
|
|
20
21
|
.gotoPage(NavLoginBlock, { ff: true })
|
|
21
22
|
.method(
|
|
22
23
|
FillUsernameBlock.stubBefore((ctx) => {
|
|
23
|
-
ctx.ring("username", {
|
|
24
|
+
ctx.ui.ring("username", {
|
|
24
25
|
selector: LoginSel.username,
|
|
25
26
|
label: "Demo user",
|
|
26
27
|
detail:
|
|
@@ -34,7 +35,7 @@ export const checkoutFlow = engine
|
|
|
34
35
|
)
|
|
35
36
|
.method(
|
|
36
37
|
FillPasswordBlock.stubBefore((ctx) => {
|
|
37
|
-
ctx.ring("password", {
|
|
38
|
+
ctx.ui.ring("password", {
|
|
38
39
|
selector: LoginSel.password,
|
|
39
40
|
label: "Password",
|
|
40
41
|
detail: "Filled from Mem credentials.",
|
|
@@ -46,7 +47,7 @@ export const checkoutFlow = engine
|
|
|
46
47
|
)
|
|
47
48
|
.method(
|
|
48
49
|
SubmitLoginForFlow.stubBefore((ctx) => {
|
|
49
|
-
ctx.ring("submit", {
|
|
50
|
+
ctx.ui.ring("submit", {
|
|
50
51
|
selector: LoginSel.loginButton,
|
|
51
52
|
label: "Sign in",
|
|
52
53
|
tag: "GATE",
|
|
@@ -58,27 +59,27 @@ export const checkoutFlow = engine
|
|
|
58
59
|
}),
|
|
59
60
|
{ ff: true },
|
|
60
61
|
)
|
|
61
|
-
.method(AddToCartBlock.stubBefore((ctx) => ctx.title("Owner: Full Checkout")))
|
|
62
|
+
.method(AddToCartBlock.stubBefore((ctx) => ctx.ui.title("Owner: Full Checkout")))
|
|
62
63
|
.gotoPage(NavCartBlock)
|
|
63
64
|
.gotoPage(NavCheckoutInfoBlock)
|
|
64
65
|
.method(
|
|
65
66
|
FillFirstNameBlock.stubBefore((ctx) => {
|
|
66
|
-
ctx.caption("Enter the first name on the order");
|
|
67
|
-
ctx.titlePos("left");
|
|
67
|
+
ctx.ui.caption("Enter the first name on the order");
|
|
68
|
+
ctx.ui.titlePos("left");
|
|
68
69
|
}),
|
|
69
70
|
)
|
|
70
71
|
.method(
|
|
71
72
|
FillLastNameBlock.stubBefore((ctx) => {
|
|
72
|
-
ctx.caption("Enter the last name on the order");
|
|
73
|
-
ctx.titlePos("center");
|
|
73
|
+
ctx.ui.caption("Enter the last name on the order");
|
|
74
|
+
ctx.ui.titlePos("center");
|
|
74
75
|
}),
|
|
75
76
|
)
|
|
76
77
|
.method(
|
|
77
78
|
FillPostalCodeBlock.stubBefore((ctx) => {
|
|
78
|
-
ctx.caption("Enter a postal code to calculate shipping");
|
|
79
|
-
ctx.titlePos("right");
|
|
79
|
+
ctx.ui.caption("Enter a postal code to calculate shipping");
|
|
80
|
+
ctx.ui.titlePos("right");
|
|
80
81
|
}),
|
|
81
82
|
)
|
|
82
|
-
.method(SubmitCheckoutInfoBlock.stubBefore((ctx) => ctx.hideCaption()))
|
|
83
|
-
.method(FinishOrderBlock)
|
|
83
|
+
.method(SubmitCheckoutInfoBlock.stubBefore((ctx) => ctx.ui.hideCaption()))
|
|
84
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
84
85
|
.end();
|
|
@@ -9,7 +9,7 @@ import { SubmitLoginForFlow } from "../blocks/saucedemo-web/methods/submit-login
|
|
|
9
9
|
export const loginFlow = engine
|
|
10
10
|
.map({ memStub: true })
|
|
11
11
|
.start()
|
|
12
|
-
.gotoPage(NavLoginBlock.stubBefore((ctx) => ctx.title("Sign In")))
|
|
12
|
+
.gotoPage(NavLoginBlock.stubBefore((ctx) => ctx.ui.title("Sign In")))
|
|
13
13
|
.method(FillUsernameBlock)
|
|
14
14
|
.method(FillPasswordBlock)
|
|
15
15
|
.method(SubmitLoginForFlow)
|
|
@@ -8,17 +8,18 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
|
|
|
8
8
|
import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
|
|
9
9
|
import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
|
|
10
10
|
import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
|
|
11
|
+
import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
|
|
11
12
|
|
|
12
13
|
/** Add the selected item, check out and place the order. Starts signed in (run it after loginFlow). */
|
|
13
14
|
export const shopFlow = engine
|
|
14
15
|
.map({ memStub: true })
|
|
15
16
|
.start()
|
|
16
|
-
.method(AddToCartBlock.stubBefore((ctx) => ctx.title("Shop & Checkout")))
|
|
17
|
+
.method(AddToCartBlock.stubBefore((ctx) => ctx.ui.title("Shop & Checkout")))
|
|
17
18
|
.gotoPage(NavCartBlock)
|
|
18
19
|
.gotoPage(NavCheckoutInfoBlock)
|
|
19
20
|
.method(FillFirstNameBlock)
|
|
20
21
|
.method(FillLastNameBlock)
|
|
21
22
|
.method(FillPostalCodeBlock)
|
|
22
23
|
.method(SubmitCheckoutInfoBlock)
|
|
23
|
-
.method(FinishOrderBlock)
|
|
24
|
+
.method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
|
|
24
25
|
.end();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { reads } from "waygraph/beta";
|
|
2
|
+
|
|
3
|
+
// What the store's pages show, by namespace: the Read Blocks fill these, `pilot read` returns them as JSON.
|
|
4
|
+
export const Catalog = reads("catalog")<{
|
|
5
|
+
items: { name: string; price: number; inCart: boolean }[];
|
|
6
|
+
sort: string;
|
|
7
|
+
}>();
|
|
8
|
+
export const Cart = reads("cart")<{ lines: { name: string; qty: number; price: number }[] }>();
|
|
9
|
+
export const Header = reads("header")<{ title: string; cartCount: number }>();
|