waygraph 0.7.0 → 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 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. Once Blocks are composed into a `Flow`, `flow.withBlockVerify`/
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "main": "./dist/index.js",