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
|
@@ -4,17 +4,7 @@ import type { MailpitInbox, MailpitMessageOpen } from "../../../../states/demo.s
|
|
|
4
4
|
import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
|
|
5
5
|
import { MailpitSel } from "../_sel.js";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Kind: Method
|
|
9
|
-
* Helper: defineMethodBlock
|
|
10
|
-
* Route: demo-external/mailpit/_methods/
|
|
11
|
-
*
|
|
12
|
-
* Opens the newest inbox row addressed to the mem-set recipient (not bare
|
|
13
|
-
* inbox position - a prior run's leftover mail for a different recipient
|
|
14
|
-
* would otherwise pick the wrong row). UI click only, no mail-catcher REST
|
|
15
|
-
* API - the row itself is a real link (`page.waitForURL` sees the client-side
|
|
16
|
-
* route change the same as any other in-page navigation).
|
|
17
|
-
*/
|
|
7
|
+
/** Opens the newest message for the expected recipient (matched by email, not position). */
|
|
18
8
|
export const OpenMessageBlock = defineMethodBlock<MailpitInbox, MailpitMessageOpen>({
|
|
19
9
|
name: "open-message",
|
|
20
10
|
description: "Opens the newest message addressed to the expected recipient.",
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { defineNavBlock, Trait } from "waygraph";
|
|
2
2
|
import type { MailpitInbox } from "../../../states/demo.states.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Kind: Nav
|
|
6
|
-
* Helper: defineNavBlock
|
|
7
|
-
* Route: (external)/mailpit/ (cross-origin - a real Mailpit web UI, not this app)
|
|
8
|
-
*
|
|
9
|
-
* Cross-origin tooling lives under (external)/, never (app_base)/ - this is
|
|
10
|
-
* a dev-inbox screen, not part of the app under test. `WAYGRAPH_MAIL_URL`
|
|
11
|
-
* defaults to Mailpit's own usual local port.
|
|
12
|
-
*/
|
|
4
|
+
// Cross-origin, so it lives under (external)/ and the URL is a function.
|
|
13
5
|
const mailpitUrl = (): string => process.env.WAYGRAPH_MAIL_URL ?? "http://127.0.0.1:8025";
|
|
14
6
|
|
|
15
7
|
export const NavMailpitInboxBlock = defineNavBlock<MailpitInbox>({
|
|
@@ -5,17 +5,6 @@ import { OpenMessageBlock } from "./_methods/open-message.method.block.js";
|
|
|
5
5
|
import { AssertEmailContentBlock } from "./_methods/assert-email-content.method.block.js";
|
|
6
6
|
import { ExtractEmailLinkBlock } from "./_methods/extract-email-link.method.block.js";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Kind: Page
|
|
10
|
-
* Helper: definePageBlock
|
|
11
|
-
* Route: (external)/mailpit/ (= http://127.0.0.1:8025/ by default)
|
|
12
|
-
*
|
|
13
|
-
* Hub for the Mailpit dev-inbox screen. Arrival-only (no url/click) -
|
|
14
|
-
* nav-mailpit-inbox owns the goto. Methods: confirm the email arrived, open
|
|
15
|
-
* it, confirm its content, then read its link - all mem-driven (recipient,
|
|
16
|
-
* link pattern, expected content), so this one page's methods serve any
|
|
17
|
-
* number of different email scenarios, not just one.
|
|
18
|
-
*/
|
|
19
8
|
export const MailpitInboxPageBlock = definePageBlock<MailpitInbox>({
|
|
20
9
|
name: "page-mailpit-inbox",
|
|
21
10
|
description: "Mailpit dev inbox hub - confirm arrival/content, open the expected message, read its link.",
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
import { key, keyGroup } from "waygraph";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Which catalog row `add-item` / `remove-item` act on.
|
|
5
|
-
* `waygraph auto` fills this via Effect `instanceOptions` (one menu row per live button).
|
|
6
|
-
*/
|
|
3
|
+
/** The catalog row `add-item` / `remove-item` act on; `waygraph auto` fills it from `instanceOptions`. */
|
|
7
4
|
export const SelectedItem = keyGroup<{ id: string; name: string }>("demo.selectedItem");
|
|
8
5
|
|
|
9
6
|
/** The link read out of the opened message - set by extract-email-link. */
|
|
10
7
|
export const EmailLink = keyGroup<{ url: string }>("demo.emailLink");
|
|
11
8
|
|
|
12
|
-
/**
|
|
13
|
-
* The recipient open-message looks for in the inbox. Mailpit lists newest
|
|
14
|
-
* first, but `.first()` alone picks the wrong row once more than one
|
|
15
|
-
* recipient's mail is sitting in the same inbox - match by mem, not position.
|
|
16
|
-
*/
|
|
9
|
+
/** Matched by email, not inbox position, so one inbox can hold several recipients' mail. */
|
|
17
10
|
export const ExpectedRecipient = keyGroup<{ email: string }>("demo.expectedRecipient");
|
|
18
11
|
|
|
19
|
-
/**
|
|
20
|
-
* Substring the link's `href` must contain - lets the same extract-email-link
|
|
21
|
-
* Block serve any number of different email templates/scenarios in one
|
|
22
|
-
* project without a new Block per scenario. Only the mem values change per
|
|
23
|
-
* run, not the Blocks.
|
|
24
|
-
*/
|
|
12
|
+
/** Substring the link's `href` must contain; mem varies per scenario, the Blocks do not. */
|
|
25
13
|
export const ExpectedLinkPattern = key<string>("demo.expectedLinkPattern");
|
|
26
14
|
|
|
27
|
-
/**
|
|
28
|
-
* The substring the open message's body must contain - same reuse reasoning
|
|
29
|
-
* as ExpectedLinkPattern: one Block, many scenarios, driven by mem.
|
|
30
|
-
*/
|
|
15
|
+
/** Substring the open message's body must contain. */
|
|
31
16
|
export const ExpectedEmailContent = key<string>("demo.expectedEmailContent");
|
|
17
|
+
|
|
18
|
+
export const RequestMethod = key<string>("demo.requestMethod");
|
|
19
|
+
export const RequestUrl = key<string>("demo.requestUrl");
|
|
20
|
+
export const RequestHeaders = key<Record<string, string>>("demo.requestHeaders");
|
|
21
|
+
export const RequestParams = key<Record<string, string>>("demo.requestParams");
|
|
22
|
+
export const RequestBearerToken = key<string>("demo.requestBearerToken");
|
|
23
|
+
/** Raw JSON text typed into the request body editor. */
|
|
24
|
+
export const RequestBody = key<string>("demo.requestBody");
|
|
25
|
+
export const ExpectedStatus = key<number>("demo.expectedStatus");
|
|
26
|
+
/** Substring the response body must contain. */
|
|
27
|
+
export const ExpectedResponseText = key<string>("demo.expectedResponseText");
|
|
28
|
+
/** The raw response body, set by read-response-body. */
|
|
29
|
+
export const ResponseBody = key<string>("demo.responseBody");
|
|
30
|
+
/** Response headers (lower-cased names), set by read-response-headers. */
|
|
31
|
+
export const ResponseHeaders = key<Record<string, string>>("demo.responseHeaders");
|
|
32
|
+
export const ExpectedResponseHeader = keyGroup<{ name: string; value: string }>("demo.expectedResponseHeader");
|
|
@@ -15,5 +15,9 @@ export type MailpitInbox = Checkpoint<"MailpitInbox">;
|
|
|
15
15
|
/** The message addressed to the expected recipient is open; preview iframe visible. */
|
|
16
16
|
export type MailpitMessageOpen = Checkpoint<"MailpitMessageOpen">;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// --- Hoppscotch (external API client, src/map/(external)/hoppscotch/) ---
|
|
19
|
+
|
|
20
|
+
export type HoppscotchSettings = Checkpoint<"HoppscotchSettings">;
|
|
21
|
+
export type HoppscotchRest = Checkpoint<"HoppscotchRest">;
|
|
22
|
+
|
|
19
23
|
export type Docs = Checkpoint<"Docs">;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { test, expect } from "@playwright/test";
|
|
2
|
-
import { MemPage, checkpoint } from "waygraph";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { SelectedItem } from "../src/states/demo.mem-keys.js";
|
|
2
|
+
import { MemPage, checkpoint, seedMemStub } from "waygraph";
|
|
3
|
+
import { homeHelloFlow } from "../src/flows/home/hello.flow.js";
|
|
4
|
+
import { homeCartFlow } from "../src/flows/home/cart.flow.js";
|
|
6
5
|
|
|
7
6
|
test("example flow reaches Docs (home -> assert -> sibling docs/ page)", async ({ context }) => {
|
|
8
|
-
const result = await
|
|
7
|
+
const result = await homeHelloFlow.run(context, new MemPage());
|
|
9
8
|
expect(result).toEqual(checkpoint("Docs"));
|
|
10
9
|
});
|
|
11
10
|
|
|
12
11
|
test("shop flow adds then clears (Effect + Method)", async ({ context }) => {
|
|
13
12
|
const mem = new MemPage();
|
|
14
|
-
mem
|
|
15
|
-
const result = await
|
|
13
|
+
seedMemStub(mem, homeCartFlow);
|
|
14
|
+
const result = await homeCartFlow.run(context, mem);
|
|
16
15
|
expect(result).toEqual(checkpoint("CartEmpty"));
|
|
17
16
|
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test";
|
|
2
|
+
import { createServer, type Server } from "node:http";
|
|
3
|
+
import { MemPage, checkpoint } from "waygraph";
|
|
4
|
+
import config from "../config/waygraph.config.js";
|
|
5
|
+
import { hoppscotchGetFlow } from "../src/flows/(external)/hoppscotch/get.flow.js";
|
|
6
|
+
import { hoppscotchAuthFlow } from "../src/flows/(external)/hoppscotch/auth.flow.js";
|
|
7
|
+
import { hoppscotchPostFlow } from "../src/flows/(external)/hoppscotch/post.flow.js";
|
|
8
|
+
import {
|
|
9
|
+
ExpectedResponseHeader,
|
|
10
|
+
ExpectedResponseText,
|
|
11
|
+
ExpectedStatus,
|
|
12
|
+
RequestBearerToken,
|
|
13
|
+
RequestBody,
|
|
14
|
+
RequestHeaders,
|
|
15
|
+
RequestMethod,
|
|
16
|
+
RequestParams,
|
|
17
|
+
RequestUrl,
|
|
18
|
+
} from "../src/states/demo.mem-keys.js";
|
|
19
|
+
|
|
20
|
+
// The API under test: no CORS headers, so only the extension can reach it from hoppscotch.io.
|
|
21
|
+
let server: Server;
|
|
22
|
+
let apiUrl: string;
|
|
23
|
+
|
|
24
|
+
test.beforeAll(async () => {
|
|
25
|
+
server = createServer((req, res) => {
|
|
26
|
+
let body = "";
|
|
27
|
+
req.on("data", (chunk) => (body += chunk));
|
|
28
|
+
req.on("end", () => {
|
|
29
|
+
res.statusCode = req.method === "POST" ? 201 : 200;
|
|
30
|
+
res.setHeader("content-type", "application/json");
|
|
31
|
+
res.setHeader("x-request-id", "req-42");
|
|
32
|
+
res.end(JSON.stringify({ method: req.method, url: req.url, token: req.headers["x-token"], auth: req.headers.authorization, body }));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
36
|
+
apiUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}/items`;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test.afterAll(() => server.close());
|
|
40
|
+
|
|
41
|
+
test.skip(!config.extensions?.includes("hoppscotch"), 'add extensions: ["hoppscotch"] to config/waygraph.config.ts to run these');
|
|
42
|
+
test.setTimeout(120_000);
|
|
43
|
+
|
|
44
|
+
test("GET returns 200", async () => {
|
|
45
|
+
const mem = new MemPage();
|
|
46
|
+
mem.set(RequestUrl, apiUrl);
|
|
47
|
+
mem.set(ExpectedStatus, 200);
|
|
48
|
+
expect(await hoppscotchGetFlow.run(mem, {})).toEqual(checkpoint("HoppscotchRest"));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("POST sends the header and JSON body, returns 201", async () => {
|
|
52
|
+
const mem = new MemPage();
|
|
53
|
+
mem.set(RequestMethod, "POST");
|
|
54
|
+
mem.set(RequestUrl, apiUrl);
|
|
55
|
+
mem.set(RequestHeaders, { "x-token": "abc123" });
|
|
56
|
+
mem.set(RequestBody, '{"name":"waygraph"}');
|
|
57
|
+
mem.set(ExpectedStatus, 201);
|
|
58
|
+
mem.set(ExpectedResponseText, '"token":"abc123"');
|
|
59
|
+
expect(await hoppscotchPostFlow.run(mem, {})).toEqual(checkpoint("HoppscotchRest"));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("a wrong expected status fails the flow, naming assert-status", async () => {
|
|
63
|
+
const mem = new MemPage();
|
|
64
|
+
mem.set(RequestUrl, apiUrl);
|
|
65
|
+
mem.set(ExpectedStatus, 404);
|
|
66
|
+
await expect(hoppscotchGetFlow.run(mem, {})).rejects.toThrow(/assert-status/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a response body without the expected text fails the flow, naming assert-response-body", async () => {
|
|
70
|
+
const mem = new MemPage();
|
|
71
|
+
mem.set(RequestMethod, "POST");
|
|
72
|
+
mem.set(RequestUrl, apiUrl);
|
|
73
|
+
mem.set(RequestHeaders, { "x-token": "abc123" });
|
|
74
|
+
mem.set(RequestBody, '{"name":"waygraph"}');
|
|
75
|
+
mem.set(ExpectedStatus, 201);
|
|
76
|
+
mem.set(ExpectedResponseText, '"token":"nope"');
|
|
77
|
+
await expect(hoppscotchPostFlow.run(mem, {})).rejects.toThrow(/assert-response-body/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const authMem = (headerValue: string): MemPage => {
|
|
81
|
+
const mem = new MemPage();
|
|
82
|
+
mem.set(RequestUrl, apiUrl);
|
|
83
|
+
mem.set(RequestParams, { q: "waygraph" });
|
|
84
|
+
mem.set(RequestBearerToken, "tok-9");
|
|
85
|
+
mem.set(ExpectedStatus, 200);
|
|
86
|
+
mem.set(ExpectedResponseText, '"url":"/items?q=waygraph","auth":"Bearer tok-9"');
|
|
87
|
+
mem.set(ExpectedResponseHeader({ name: "X-Request-Id", value: headerValue }));
|
|
88
|
+
return mem;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
test("query params and a bearer token reach the API; a response header is checked", async () => {
|
|
92
|
+
expect(await hoppscotchAuthFlow.run(authMem("req-42"), {})).toEqual(checkpoint("HoppscotchRest"));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a wrong response header value fails the flow, naming assert-response-header", async () => {
|
|
96
|
+
await expect(hoppscotchAuthFlow.run(authMem("other"), {})).rejects.toThrow(/assert-response-header/);
|
|
97
|
+
});
|
|
@@ -2,21 +2,12 @@ import { test, expect } from "@playwright/test";
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import { MemPage, checkpoint } from "waygraph";
|
|
5
|
-
import {
|
|
5
|
+
import { emailVerificationFlow } from "../src/flows/(user-stories)/email-verification.flow.js";
|
|
6
6
|
import { ExpectedRecipient, ExpectedLinkPattern, ExpectedEmailContent } from "../src/states/demo.mem-keys.js";
|
|
7
7
|
import { HOME_URL } from "../src/map/(app_base)/home/_sel.js";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* app sent a verification email" themselves: spin up a real, throwaway
|
|
12
|
-
* Mailpit container, send a real SMTP message, then run mailVerifyFlow for
|
|
13
|
-
* real - a real cross-origin browser hop into Mailpit's own web UI and
|
|
14
|
-
* back, no mail-catcher REST API anywhere. Covers the happy path, both
|
|
15
|
-
* failure paths its QA-facing asserts exist for (no email arrived, wrong
|
|
16
|
-
* content), and - the actual point of making the mail-reading Blocks
|
|
17
|
-
* mem-driven - a second, entirely different email scenario running through
|
|
18
|
-
* the exact same four Blocks with zero new code.
|
|
19
|
-
*/
|
|
9
|
+
// Plays "the app sent a verification email": starts a throwaway Mailpit container, sends a real SMTP message,
|
|
10
|
+
// then runs emailVerificationFlow against it, including the no-email and wrong-content failure paths.
|
|
20
11
|
|
|
21
12
|
function runMailpit(): { containerId: string; smtpPort: number; httpPort: number } {
|
|
22
13
|
const run = spawnSync("docker", ["run", "--rm", "-d", "-p", "0:1025", "-p", "0:8025", "axllent/mailpit"], {
|
|
@@ -89,7 +80,7 @@ async function withMailpit<T>(fn: (opts: { httpPort: number; smtpPort: number })
|
|
|
89
80
|
|
|
90
81
|
const VERIFICATION_CONTENT = "Please confirm your email address to finish setting up your account.";
|
|
91
82
|
|
|
92
|
-
test("
|
|
83
|
+
test("emailVerificationFlow: confirms arrival, confirms content, reads the real link, navigates to it", async ({
|
|
93
84
|
context,
|
|
94
85
|
}) => {
|
|
95
86
|
test.skip(!dockerAvailable(), "Docker not reachable in this environment");
|
|
@@ -109,7 +100,7 @@ test("mailVerifyFlow: confirms arrival, confirms content, reads the real link, n
|
|
|
109
100
|
mem.set(ExpectedRecipient({ email: recipient }));
|
|
110
101
|
mem.set(ExpectedLinkPattern, "verified=1");
|
|
111
102
|
mem.set(ExpectedEmailContent, VERIFICATION_CONTENT);
|
|
112
|
-
const result = await
|
|
103
|
+
const result = await emailVerificationFlow.run(context, mem);
|
|
113
104
|
expect(result).toEqual(checkpoint("HomeVerified"));
|
|
114
105
|
});
|
|
115
106
|
});
|
|
@@ -139,7 +130,7 @@ test("the same four mail-reading Blocks handle a second, unrelated email scenari
|
|
|
139
130
|
mem.set(ExpectedRecipient({ email: recipient }));
|
|
140
131
|
mem.set(ExpectedLinkPattern, "verified=1");
|
|
141
132
|
mem.set(ExpectedEmailContent, resetContent);
|
|
142
|
-
const result = await
|
|
133
|
+
const result = await emailVerificationFlow.run(context, mem);
|
|
143
134
|
expect(result).toEqual(checkpoint("HomeVerified"));
|
|
144
135
|
});
|
|
145
136
|
});
|
|
@@ -158,7 +149,7 @@ test("assert-email-received fails loud, naming itself, when no email ever arrive
|
|
|
158
149
|
// not just the leaf Block - real, useful QA context, not a bug - so this
|
|
159
150
|
// matches the accumulated name ending at assert-email-received, not an
|
|
160
151
|
// exact "failed after \"assert-email-received\"" alone.
|
|
161
|
-
await expect(
|
|
152
|
+
await expect(emailVerificationFlow.run(context, mem)).rejects.toThrow(
|
|
162
153
|
/trait "email-received" failed after ".*assert-email-received"/,
|
|
163
154
|
);
|
|
164
155
|
});
|
|
@@ -184,7 +175,7 @@ test("assert-email-content fails loud, naming itself, when the email's copy does
|
|
|
184
175
|
mem.set(ExpectedRecipient({ email: recipient }));
|
|
185
176
|
mem.set(ExpectedLinkPattern, "verified=1");
|
|
186
177
|
mem.set(ExpectedEmailContent, VERIFICATION_CONTENT);
|
|
187
|
-
await expect(
|
|
178
|
+
await expect(emailVerificationFlow.run(context, mem)).rejects.toThrow(
|
|
188
179
|
/trait "body-contains-expected-text" failed after ".*assert-email-content"/,
|
|
189
180
|
);
|
|
190
181
|
});
|
|
@@ -17,6 +17,9 @@ against a live site, use `--skill-pilot-blind`.
|
|
|
17
17
|
2. **Navigation only inside Nav.** `page.goto` / NavBlock `click` live in
|
|
18
18
|
`defineNavBlock` / `defineNavClickBlock` only. Method/Effect `act` uses ActionPage
|
|
19
19
|
(no goto) even when the app changes URL as a side effect of a button click.
|
|
20
|
+
A Nav for a page that only exists after something else (every in-app page after sign-in)
|
|
21
|
+
declares `from: [...]` with the Checkpoints it is valid from (a `SIGNED_IN` list in
|
|
22
|
+
`src/states/`); without it, routing jumps there from anywhere, skipping sign-in.
|
|
20
23
|
3. **Selectors in `*Sel`, never inline.** DOM strings live in a `*Sel` next to the page - `_sel.ts`
|
|
21
24
|
in the Map layout (one per page folder, next to `_nav`/`_page`/`_methods`), `<name>.sel.ts` in a
|
|
22
25
|
freeform layout (one per concern) - never a fresh file-naming convention per project (`_sel.ts`,
|
|
@@ -39,7 +42,7 @@ against a live site, use `--skill-pilot-blind`.
|
|
|
39
42
|
5. **`requires` = externally supplied.** A key an earlier Block in the same chain produces
|
|
40
43
|
is never listed under `requires` - preflight checks the whole chain up front. For a
|
|
41
44
|
genuinely arbitrary key (a username, an email) that a demo/dev run shouldn't need
|
|
42
|
-
hand-supplied every time, `registerMemStub(key, fake)` + `
|
|
45
|
+
hand-supplied every time, `registerMemStub(key, fake)` + `memStub: true` on the flow (or
|
|
43
46
|
`--mem-stub`) fills it from a registry instead - a key with no registration still fails
|
|
44
47
|
preflight exactly as before, so this narrows what needs real input, never removes the guard.
|
|
45
48
|
6. **Keep Checkpoints typed.** Block helpers take concrete Checkpoint types from `src/states/`
|
|
@@ -63,6 +66,41 @@ against a live site, use `--skill-pilot-blind`.
|
|
|
63
66
|
when a *different* Block right after it in the `.flow.ts` is what actually confirms the
|
|
64
67
|
transition (e.g. an `AssertXBlock`) - `// waygraph-ignore: empty-verify`.
|
|
65
68
|
|
|
69
|
+
## Flows, stubs and config (the scaffold's shape)
|
|
70
|
+
|
|
71
|
+
- **Build flows with `map()`** (`engine.map({ memStub }).start().gotoPage(Nav).method(B, { pace }).assert(A).end()`;
|
|
72
|
+
`.gotoExternal` for another origin), never a raw `defineFlow([start, ...])`. Flow settings go in `map({ … })` before the chain
|
|
73
|
+
and `.end()` takes nothing. The flow's title is `ctx.title(...)` in a stub. **Nest flows with `.flow(sub, { reset: true })`**,
|
|
74
|
+
not `chainFlow` / `withSessionReset`. No `withMemStub(withTitle(...))` wrappers.
|
|
75
|
+
- **`src/flows/` holds use cases and mirrors the front end's endpoints**: a folder per page (`flows/auth/login/`) with one
|
|
76
|
+
file per scenario named for its outcome (`success.flow.ts`, `wrong-password.flow.ts`, `empty-fields.flow.ts`), and
|
|
77
|
+
`(user-stories)/` (any `(group)`) for flows that cross pages. Export names are `<page><Outcome>Flow`. Not one file per
|
|
78
|
+
Block chain, not a flat folder of unrelated flows.
|
|
79
|
+
- **Blocks carry no stubs; the flow does.** Demo highlights are ctx stubs chained on the Block where the flow uses it:
|
|
80
|
+
`AddItemBlock.stubAfter((ctx) => { ctx.ring("cart", { selector: DemoSel.cartCount, label: "AC · item in cart" }); })`.
|
|
81
|
+
Not a static `withHighlightFixtures({ "add-item": { stubAfter: {...} } })` map, and not an inline
|
|
82
|
+
`instruction: { stubAfter() {} }`. A Block used twice gets one `const AddShown = AddItemBlock.stubAfter(...)`.
|
|
83
|
+
- **Reading a value into `mem` happens in `act`** (`mem.set(Key, value)`), like Mailpit's `extract-email-link`.
|
|
84
|
+
`observe` is only for a Block that branches (it feeds `resolve`); an empty `act(){}` plus an `observe` that
|
|
85
|
+
writes `mem` is a misuse.
|
|
86
|
+
- **Demo pace is a map option or config, never a wrapper.** `.method(SetBearerTokenBlock, { pace: "blitz" })` speeds one step,
|
|
87
|
+
`map({ demoPace: "fast" })` a flow, `demo: { pace, typingMs, slowMo }` in the config the whole project. Not `withBlockPace(...)`.
|
|
88
|
+
- **Per-environment values** (a different `baseUrl` for staging) come from `.env` files read in the config function:
|
|
89
|
+
`defineConfig(({ env }) => ({ baseUrl: env.BASE_URL }))`; `--env staging` loads `.env.staging`. Never edit the config to switch.
|
|
90
|
+
- **More than one config** (staging, another app) is another file, `config/waygraph.<name>.config.ts`, chosen with `--config <name>`.
|
|
91
|
+
The engine a config exports, and every engine in a flow, follows the selected config.
|
|
92
|
+
- **A browser dialog (`confirm`, `beforeunload`) is answered with `withDialog(block, { accept, into })`**, not inside the
|
|
93
|
+
Block's `act`; the click Block stays a plain click.
|
|
94
|
+
- **Project settings live in `config/waygraph.config.ts`** (`defineConfig({ baseUrl, testDir, webServer,
|
|
95
|
+
extensions, playwright: {...} })`). The config file also exports `engine = new Engine(config)`; flows `import { engine } from "../../config/waygraph.config.js"`; a project needs no
|
|
96
|
+
`playwright.config.ts` - `waygraph test` builds one from it. A leftover `playwright.config.*` is deprecated.
|
|
97
|
+
- **Another origin the test needs is a Map external** under `src/map/(external)/<tool>/`. For REST API
|
|
98
|
+
tests, `(external)/hoppscotch` (list `extensions: ["hoppscotch"]` in the config) drives hoppscotch.io's client
|
|
99
|
+
with the extension, so requests skip CORS: `set-method`, `set-url`, `set-headers`, `set-json-body`,
|
|
100
|
+
`send-request`, `assert-status`, `read-response-body`, `assert-response-body`.
|
|
101
|
+
- **Updating an older project:** `npm install waygraph@latest`, then `waygraph upgrade` (dry run) to see new
|
|
102
|
+
and differing scaffold files; copy the paths you want with `--apply`. `waygraph upgrade --skills` refreshes these skills.
|
|
103
|
+
|
|
66
104
|
## Block kinds (file convention)
|
|
67
105
|
|
|
68
106
|
| Kind | File | Helper | `page.goto`? |
|
|
@@ -113,8 +151,7 @@ src/map/
|
|
|
113
151
|
- `(group)` parentheses and leading-underscore segments (`_methods/`, `_page…`) are
|
|
114
152
|
**not** URL segments. `waygraph map` excludes them when checking folder-vs-URL.
|
|
115
153
|
- A bare non-underscore folder always means a real child Checkpoint/URL segment.
|
|
116
|
-
-
|
|
117
|
-
it rejects Blocks that didn't come from a real factory (no silent teleporting).
|
|
154
|
+
- `map()` rejects Blocks that didn't come from a real factory (no silent teleporting).
|
|
118
155
|
|
|
119
156
|
Mail / external tools: under `*-external/<tool>/` or `map/(external)/<tool>/`, never under
|
|
120
157
|
the site's `*-web/` tree. Browser-driven (DOM / iframe Traits), not the catcher's REST API.
|
|
@@ -104,13 +104,12 @@ thing that differs from demo is *who* triggers the paint:
|
|
|
104
104
|
|
|
105
105
|
| Mode | Who paints | How |
|
|
106
106
|
|------|------------|-----|
|
|
107
|
-
| `waygraph demo` | Engine, automatically |
|
|
107
|
+
| `waygraph demo` | Engine, automatically | ctx stubs chained in the flow run each step |
|
|
108
108
|
| Pilot / `auto --detach` | **You (the agent)** | `waygraph auto highlight <sessionId> '<json>'` before (or between) sends |
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`auto dom`). Paint, then `auto send` the real Block. Clear when done.
|
|
110
|
+
The ctx stubs a flow chains onto Blocks show up in Pilot's `auto trace` as metadata, but they do **not**
|
|
111
|
+
auto-draw. You re-express the same intent with `auto highlight` (copy selectors/labels from the flow's stubs or
|
|
112
|
+
from `auto dom`). Paint, then `auto send` the real Block. Clear when done.
|
|
114
113
|
|
|
115
114
|
```
|
|
116
115
|
# Headful watch session (rings visible to a human audience):
|
package/dist/browser-path.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CHROME_PATH / CHROMIUM_PATH, if set and the file exists. A stale path (browser uninstalled)
|
|
3
|
-
* falls back to Playwright's bundled Chromium with a one-time warning instead of failing the run.
|
|
4
|
-
*/
|
|
5
|
-
export declare function chromiumExecutablePath(): string | undefined;
|
package/dist/browser-path.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
let warned = false;
|
|
3
|
-
/**
|
|
4
|
-
* CHROME_PATH / CHROMIUM_PATH, if set and the file exists. A stale path (browser uninstalled)
|
|
5
|
-
* falls back to Playwright's bundled Chromium with a one-time warning instead of failing the run.
|
|
6
|
-
*/
|
|
7
|
-
export function chromiumExecutablePath() {
|
|
8
|
-
const path = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
|
|
9
|
-
if (!path)
|
|
10
|
-
return undefined;
|
|
11
|
-
if (existsSync(path))
|
|
12
|
-
return path;
|
|
13
|
-
if (!warned) {
|
|
14
|
-
warned = true;
|
|
15
|
-
console.error(`waygraph: CHROME_PATH/CHROMIUM_PATH "${path}" does not exist - using Playwright's bundled Chromium`);
|
|
16
|
-
}
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Page } from "@playwright/test";
|
|
2
|
-
import type { StubPhaseResult } from "../highlights/types.js";
|
|
3
|
-
|
|
4
|
-
export interface SessionOverlay {
|
|
5
|
-
present(tag: string, phase: Partial<StubPhaseResult>): Promise<void>;
|
|
6
|
-
restore(): Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
export function createSessionOverlay(page: Page, surface: string): SessionOverlay;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// Presents a resolved stub phase ("waygraph overlay fixtures": ctx.todos / device / title / banner ...) on a LIVE page
|
|
2
|
-
// for the session surfaces (auto / browser / pilot). Same OverlayStage + same in-page installers + same verbose log
|
|
3
|
-
// lines as `waygraph demo`, so an overlay fixture behaves identically on every surface. Rings are painted separately
|
|
4
|
-
// (pilot holds them; demo cycles them) - that is the only intended difference.
|
|
5
|
-
import { installOverlay } from "./overlay-install.js";
|
|
6
|
-
import { createOverlayStage } from "./overlay-stage.js";
|
|
7
|
-
import { demoLog, logStubPhaseFixtures, logTodoDockFull, setDemoLogSurface, summarizeDevice } from "./demo-log.js";
|
|
8
|
-
import { logBannerDom, probeBanner } from "./banner-log.js";
|
|
9
|
-
import { probeTodoDocksOnPage } from "./todo-dock.js";
|
|
10
|
-
|
|
11
|
-
export function createSessionOverlay(page, surface) {
|
|
12
|
-
setDemoLogSurface(surface);
|
|
13
|
-
const stage = createOverlayStage(page, { todoDockRef: { current: undefined }, deviceRef: { current: undefined } });
|
|
14
|
-
let title = undefined;
|
|
15
|
-
let painted = false;
|
|
16
|
-
|
|
17
|
-
const paintChrome = async (phaseParallel) => {
|
|
18
|
-
await installOverlay(page, title, stage.state.lastBannerUi);
|
|
19
|
-
await stage.syncAllTodoDocks({ parallel: !!phaseParallel });
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
stage,
|
|
24
|
-
/** Apply one phase (tag = before | after | error | highlight) and log the resulting overlay state. */
|
|
25
|
-
async present(tag, phase) {
|
|
26
|
-
setDemoLogSurface(surface);
|
|
27
|
-
logStubPhaseFixtures(tag, phase);
|
|
28
|
-
await stage.pushTodoDockUi(phase.todoDockUi);
|
|
29
|
-
const { appliedTodo, appliedDevice } = await stage.applyPhase(phase, { reapplyDeviceOnKeep: true });
|
|
30
|
-
if (phase.title && String(phase.title).trim()) title = String(phase.title).trim();
|
|
31
|
-
const wantsChrome = !!(title || stage.state.lastTodoDock || phase.todoSync === "set");
|
|
32
|
-
if (wantsChrome) {
|
|
33
|
-
painted = true;
|
|
34
|
-
await paintChrome(phase.todoParallel);
|
|
35
|
-
}
|
|
36
|
-
logTodoDockFull(stage.state.lastTodoDock, " " + tag);
|
|
37
|
-
demoLog(" " + tag + " device=" + summarizeDevice(stage.state.lastDevice) + " deviceSync=" + appliedDevice.sync);
|
|
38
|
-
demoLog(" " + tag + " todoSync=" + appliedTodo.sync);
|
|
39
|
-
const docks = await probeTodoDocksOnPage(page);
|
|
40
|
-
if (!docks.length && stage.state.lastTodoDock && appliedTodo.sync !== "clear") {
|
|
41
|
-
demoLog(" WARN " + tag + ": todos authored but dock-dom empty (not painted?)");
|
|
42
|
-
}
|
|
43
|
-
for (const d of docks) {
|
|
44
|
-
demoLog(
|
|
45
|
-
" " + tag + " dock-dom " + d.key + "@" + d.pos + " " + d.left + "," + d.top + " " + d.w + "x" + d.h +
|
|
46
|
-
(d.onScreen ? "" : " OFFSCREEN") + " n=" + d.n + (d.current ? ' cur="' + d.current + '"' : ""),
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
if (title || painted) logBannerDom(tag, await probeBanner(page));
|
|
50
|
-
},
|
|
51
|
-
/** A navigation wipes the overlay DOM: rebuild what was authored (same job as the demo's post-navigation restore). */
|
|
52
|
-
async restore() {
|
|
53
|
-
if (!painted) return;
|
|
54
|
-
await paintChrome(false);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { ACCENT_SOFT, SURFACE } from "../tokens.js";
|
|
2
|
-
/** Top-left demo banner (`#wg-banner`). */
|
|
3
|
-
export function bannerCss() {
|
|
4
|
-
return ("#wg-banner{position:fixed;z-index:2147483647;top:14px;left:14px;" +
|
|
5
|
-
`background:${SURFACE};color:#fff;border-radius:12px;padding:10px 16px;` +
|
|
6
|
-
"font:14px/1.4 system-ui,sans-serif;box-shadow:0 8px 20px rgba(0,0,0,.3);" +
|
|
7
|
-
"border:1px solid rgba(124,58,237,.4);}" +
|
|
8
|
-
`#wg-banner .wg-banner-tag{display:block;font-size:10px;font-weight:700;color:${ACCENT_SOFT};` +
|
|
9
|
-
"letter-spacing:.05em;text-transform:uppercase;margin-bottom:2px;}");
|
|
10
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { TONES } from "../tokens.js";
|
|
2
|
-
/** Travelling mouse cursor + click ripple (`#wg-cursor`, `#wg-click-pulse`). */
|
|
3
|
-
export function cursorCss() {
|
|
4
|
-
const p = TONES.planned;
|
|
5
|
-
const pulseTones = ["auto", "info", "warning", "danger", "success"]
|
|
6
|
-
.map((n) => `#wg-click-pulse[data-tone=${n}]{border-color:${TONES[n].ring};background:${TONES[n].pulse};}`)
|
|
7
|
-
.join("");
|
|
8
|
-
return ("#wg-cursor{position:fixed;z-index:2147483647;width:24px;height:24px;pointer-events:none;" +
|
|
9
|
-
"left:0;top:0;opacity:0;margin:0;" +
|
|
10
|
-
// Travel time is JS-driven via --wg-cursor-ms (a hardcoded duration would make speed a no-op).
|
|
11
|
-
"transition:transform var(--wg-cursor-ms,600ms) cubic-bezier(.22,1,.36,1),opacity .2s ease;" +
|
|
12
|
-
"filter:drop-shadow(0 2px 4px rgba(12,12,26,.4));}" +
|
|
13
|
-
"#wg-click-pulse{position:fixed;z-index:2147483647;width:14px;height:14px;" +
|
|
14
|
-
"margin-left:-7px;margin-top:-7px;border-radius:50%;pointer-events:none;opacity:0;" +
|
|
15
|
-
`border:2px solid ${p.ring};background:${p.pulse};}` +
|
|
16
|
-
pulseTones +
|
|
17
|
-
"#wg-click-pulse.wg-pulse{animation:wg-pulse .5s ease-out;}" +
|
|
18
|
-
"@keyframes wg-pulse{0%{opacity:.9;transform:scale(.4);}100%{opacity:0;transform:scale(2.4);}}");
|
|
19
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
testDir: "./tests",
|
|
5
|
-
fullyParallel: true,
|
|
6
|
-
reporter: [["list"], ["html", { open: "never" }]],
|
|
7
|
-
use: {
|
|
8
|
-
// Demo pages open via file:// - no server. Filled in by `waygraph init`.
|
|
9
|
-
baseURL: "__APP_BASE_URL__",
|
|
10
|
-
trace: "retain-on-failure",
|
|
11
|
-
},
|
|
12
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
|
|
2
|
-
import { NavHomeBlock } from "../map/(app_base)/home/_nav.block.js";
|
|
3
|
-
import { AssertHelloBlock } from "../map/(app_base)/home/_methods/assert-hello.method.block.js";
|
|
4
|
-
import { NavDocsBlock } from "../map/(app_base)/docs/_nav.block.js";
|
|
5
|
-
// Page hubs registered for auto/docs grouping (arrival-only after nav).
|
|
6
|
-
import "../map/(app_base)/home/_page.block.js";
|
|
7
|
-
import "../map/(app_base)/docs/_page.block.js";
|
|
8
|
-
|
|
9
|
-
const engine = new Engine();
|
|
10
|
-
|
|
11
|
-
/** Smoke: nav + assert (no mem), then nav to the sibling docs/ page. Green offline. */
|
|
12
|
-
export const exampleFlow = withHighlightFixtures(
|
|
13
|
-
withTitle(
|
|
14
|
-
engine.defineFlow([start, NavHomeBlock, AssertHelloBlock, NavDocsBlock, end]),
|
|
15
|
-
"Scaffold: Hello Waygraph",
|
|
16
|
-
),
|
|
17
|
-
{
|
|
18
|
-
"assert-hello": {
|
|
19
|
-
stubAfter: {
|
|
20
|
-
title: {
|
|
21
|
-
label: "AC · Hello visible",
|
|
22
|
-
detail: "Replace this fixture copy with your ticket lines.",
|
|
23
|
-
tag: "AC",
|
|
24
|
-
duration: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
);
|