waygraph 0.7.1 → 0.7.2
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 +21 -1
- package/dist/engine.js +2 -1
- package/dist/trait.d.ts +10 -0
- package/dist/trait.js +17 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,7 +80,22 @@ takes a structured `URLPatternInit` (`{ pathname, hostname, search, hash, ... }`
|
|
|
80
80
|
shape the standard `URLPattern` Web API takes), not a hand-written regex - whichever
|
|
81
81
|
components you leave out default to "match anything," so `Trait.url({ pathname:
|
|
82
82
|
"/inventory.html" })` already ignores whatever query params a hybrid SPA tacks on, with no
|
|
83
|
-
separate flag needed for that.
|
|
83
|
+
separate flag needed for that. A hand-written `Trait` isn't limited to the page, either - its
|
|
84
|
+
`check(page, mem)` receives `mem` too, for the same-Checkpoint case: an action that doesn't
|
|
85
|
+
navigate anywhere at all (clicking "+" to bump a cart item's quantity, say) has `In === Out`,
|
|
86
|
+
which `defineBlock`/`defineFlow` already accept with no special-casing - `act()` updates `mem`
|
|
87
|
+
alongside the real click (`mem.set(Quantity, mem.get(Quantity) + 1)`), and a bespoke `verify`
|
|
88
|
+
Trait confirms the page's own displayed value actually matches what `mem` now expects
|
|
89
|
+
(`shown === String(mem.get(Quantity))`), not just that *some* number is showing. `resolve()`
|
|
90
|
+
itself stays exactly as pure as ever - it never sees `mem`, only `verify` does. `verify` only
|
|
91
|
+
ever confirms what a Block itself just did, though - nothing previously checked whether the
|
|
92
|
+
page was STILL there by the time the NEXT Block's `act()` starts. A `precondition` (same
|
|
93
|
+
`Trait[]`/`(input) => Trait[]` shape as `verify`, checked automatically by `connect()`/
|
|
94
|
+
`runGraph` right before `act()` runs, never called directly) closes that gap: a session
|
|
95
|
+
timeout, a redirect, an interstitial popup - anything that changes the page in the moment
|
|
96
|
+
between one Block finishing and the next one starting - fails loud with a clear "trait X
|
|
97
|
+
failed before Y," instead of a confusing error deep inside `act()` trying to click something
|
|
98
|
+
that's no longer there. Once Blocks are composed into a `Flow`, `flow.withBlockVerify`/
|
|
84
99
|
`flow.modBlockVerify` patch one Block's verify from outside - a spec that only imports the
|
|
85
100
|
finished flow, addressed by the Block reference (preferred), its name, or its numeric
|
|
86
101
|
position in the flow - without editing the flow's own file. `composeBlock(name, steps)`
|
|
@@ -135,6 +150,11 @@ npx create-waygraph my-project && cd my-project && npm install && npm test
|
|
|
135
150
|
project (package.json, tsconfig, playwright.config, one working example flow) in one
|
|
136
151
|
command, published on npm - the above runs a real green test with no manual edits.
|
|
137
152
|
|
|
153
|
+
**Consumer layout (Next.js App Router):** block folders mirror `app/` page routes (`/`
|
|
154
|
+
at the namespace root; no invented `landing/`/`root/`; sidebar = chrome + edges). See
|
|
155
|
+
`create-waygraph/README.md` "Consumer layout" and mesh handout
|
|
156
|
+
`WAYGRAPH-CONSUMER-CONVENTION.md` - separate from this package API doc.
|
|
157
|
+
|
|
138
158
|
Deliberately **not yet implemented** (tracked on the project board):
|
|
139
159
|
- Interstitials/Watchers - background overlay handling (cookie banners, popups).
|
|
140
160
|
|
package/dist/engine.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chromium, firefox, webkit } from "@playwright/test";
|
|
2
2
|
import { connect, checkpoint } from "./types.js";
|
|
3
3
|
import { MemPage } from "./mem-page.js";
|
|
4
|
-
import { runVerify } from "./trait.js";
|
|
4
|
+
import { runVerify, runPrecondition } from "./trait.js";
|
|
5
5
|
const LAUNCHERS = { chromium, firefox, webkit };
|
|
6
6
|
/**
|
|
7
7
|
* Resolves which `BrowserType` actually launches for a `mem`-only run (no
|
|
@@ -64,6 +64,7 @@ export async function runGraph(entry, terminals, context, mem, maxSteps = 5000,
|
|
|
64
64
|
if (++steps > maxSteps) {
|
|
65
65
|
throw new Error(`runGraph: exceeded ${maxSteps} steps - check for an unintended self-loop`);
|
|
66
66
|
}
|
|
67
|
+
await runPrecondition(current.instruction.precondition, input, page, mem, current.name);
|
|
67
68
|
await current.instruction.act(page, input, mem);
|
|
68
69
|
const observed = current.instruction.observe
|
|
69
70
|
? await current.instruction.observe(page, mem)
|
package/dist/trait.d.ts
CHANGED
|
@@ -60,3 +60,13 @@ export declare const Trait: {
|
|
|
60
60
|
* directly.
|
|
61
61
|
*/
|
|
62
62
|
export declare function runVerify<Out>(verify: Trait[] | ((out: Out) => Trait[]) | undefined, out: Out, page: Page, mem: MemPage, blockName: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Runs every Trait for a Block that's ABOUT to run, throwing on the first
|
|
65
|
+
* failure with the failing Trait's own name and the Block's name - before
|
|
66
|
+
* `act` ever touches the page, not after. `precondition` may be a flat list
|
|
67
|
+
* or a function of the incoming Checkpoint (same shape `verify` already
|
|
68
|
+
* takes of the resolved one). Called automatically by `connect()`/`runGraph`
|
|
69
|
+
* right before a Block's own `act` - not something a Block normally calls
|
|
70
|
+
* directly.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runPrecondition<In>(precondition: Trait[] | ((input: In) => Trait[]) | undefined, input: In, page: Page, mem: MemPage, blockName: string): Promise<void>;
|
package/dist/trait.js
CHANGED
|
@@ -78,3 +78,20 @@ export async function runVerify(verify, out, page, mem, blockName) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Runs every Trait for a Block that's ABOUT to run, throwing on the first
|
|
83
|
+
* failure with the failing Trait's own name and the Block's name - before
|
|
84
|
+
* `act` ever touches the page, not after. `precondition` may be a flat list
|
|
85
|
+
* or a function of the incoming Checkpoint (same shape `verify` already
|
|
86
|
+
* takes of the resolved one). Called automatically by `connect()`/`runGraph`
|
|
87
|
+
* right before a Block's own `act` - not something a Block normally calls
|
|
88
|
+
* directly.
|
|
89
|
+
*/
|
|
90
|
+
export async function runPrecondition(precondition, input, page, mem, blockName) {
|
|
91
|
+
const traits = typeof precondition === "function" ? precondition(input) : (precondition ?? []);
|
|
92
|
+
for (const trait of traits) {
|
|
93
|
+
if (!(await trait.check(page, mem))) {
|
|
94
|
+
throw new Error(`trait "${trait.name}" failed before "${blockName}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -54,6 +54,19 @@ export type ActionPage = Omit<Page, "goto" | "reload" | "goBack" | "goForward">
|
|
|
54
54
|
export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoint<string>, Observed = void> {
|
|
55
55
|
/** Drives the browser only, this tab only. Never touches `context` - see `observe` for that. */
|
|
56
56
|
act(page: Page, input: In, mem: MemPage): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Optional. Runs once, right before `act` starts - confirms the page is
|
|
59
|
+
* still in the state this Block is about to assume, before touching it.
|
|
60
|
+
* `verify` only ever confirms what a Block ITSELF just did; nothing
|
|
61
|
+
* previously checked whether the page is STILL there by the time the
|
|
62
|
+
* NEXT Block starts (a redirect, a popup, a session timeout between two
|
|
63
|
+
* Blocks - the gap `verify` alone can't see, since it only ever runs
|
|
64
|
+
* right after the PREVIOUS Block's own resolve, not right before THIS
|
|
65
|
+
* one's act). A function form picks different checks per incoming `In`,
|
|
66
|
+
* same as `verify` does per resolved `Out`.
|
|
67
|
+
* @example precondition: [Trait.visible("#checkout-form")]
|
|
68
|
+
*/
|
|
69
|
+
precondition?: Trait[] | ((input: In) => Trait[]);
|
|
57
70
|
/**
|
|
58
71
|
* Optional. Gathers evidence for `resolve` to classify, runs assertions,
|
|
59
72
|
* writes memory - the only phase allowed to look at the DOM before a
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runVerify } from "./trait.js";
|
|
1
|
+
import { runVerify, runPrecondition } from "./trait.js";
|
|
2
2
|
/**
|
|
3
3
|
* Builds a Checkpoint - spells `__state` for you so no call site does it by hand.
|
|
4
4
|
* @example resolve: () => checkpoint("LoggedIn")
|
|
@@ -21,12 +21,14 @@ export function connect(a, b) {
|
|
|
21
21
|
...(requires.length > 0 ? { requires } : {}),
|
|
22
22
|
instruction: {
|
|
23
23
|
async act(page, input, mem) {
|
|
24
|
+
await runPrecondition(a.instruction.precondition, input, page, mem, a.name);
|
|
24
25
|
await a.instruction.act(page, input, mem);
|
|
25
26
|
const aObserved = a.instruction.observe
|
|
26
27
|
? await a.instruction.observe(page, mem)
|
|
27
28
|
: undefined;
|
|
28
29
|
const mid = await a.instruction.resolve(aObserved);
|
|
29
30
|
await runVerify(a.instruction.verify, mid, page, mem, a.name);
|
|
31
|
+
await runPrecondition(b.instruction.precondition, mid, page, mem, b.name);
|
|
30
32
|
await b.instruction.act(page, mid, mem);
|
|
31
33
|
},
|
|
32
34
|
resolve: (observed) => b.instruction.resolve(observed),
|