waygraph 0.4.0 → 0.5.0
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 +19 -0
- package/dist/cli.js +82 -0
- package/dist/engine.d.ts +53 -3
- package/dist/engine.js +60 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ flat pile of ad hoc helper functions.
|
|
|
6
6
|
This repo is the `waygraph` npm package itself. Consumer projects proving it against real
|
|
7
7
|
sites/apps (saucedemo.com, zsign-app) live in the sibling `projects_waygraph/` repo, not here.
|
|
8
8
|
|
|
9
|
+
See [ROADMAP.md](./ROADMAP.md) for what's shipped, spec'd, and planned by version, and
|
|
10
|
+
`openspec/changes/` for the actual planning artifacts behind each roadmap item.
|
|
11
|
+
|
|
9
12
|
## Install
|
|
10
13
|
|
|
11
14
|
```bash
|
|
@@ -100,6 +103,22 @@ a fresh tab (the run won't close a page you handed it unless you say so), and
|
|
|
100
103
|
so the caller can keep driving it - hands a run tab back instead of losing it. See
|
|
101
104
|
"Recipes" for capture-on-popup, which stays a documented pattern until a second real use
|
|
102
105
|
case promotes it to an engine API.
|
|
106
|
+
`defineNavBlock({ name, checkpoint, url })` builds a Block whose only possible action is
|
|
107
|
+
navigating - `url` accepts a plain string or `(mem) => string` for parameterized routes,
|
|
108
|
+
and the generated `act()` is always exactly `page.goto(url)`, never author-supplied. A
|
|
109
|
+
regular `defineBlock`'s `act()` receives its `page` typed as `ActionPage` instead of raw
|
|
110
|
+
`Page` - structurally identical, every method still fully present and callable, except
|
|
111
|
+
`goto`/`reload`/`goBack`/`goForward` are re-declared `@deprecated`, so a TypeScript-aware
|
|
112
|
+
editor shows navigation struck through the instant it's typed in a regular Block, pointing
|
|
113
|
+
at `defineNavBlock` - nothing is blocked, no build ever fails, existing code that already
|
|
114
|
+
navigates from a regular Block keeps compiling and running unchanged. `waygraph check
|
|
115
|
+
[project]` is the complementary whole-project sweep for contexts with no editor watching
|
|
116
|
+
(CI, generated code) - same warning, on demand, across every `*.block.ts` file at once.
|
|
117
|
+
`new Engine({ browsers: { chromium, firefox, webkit } })` overrides which `BrowserType`
|
|
118
|
+
actually launches for a `mem`-only run, per browser name - waygraph is deliberately "just
|
|
119
|
+
an opinionated Playwright," so a stealth-patched or otherwise customized launcher (e.g.
|
|
120
|
+
`playwright-extra` plus a stealth plugin) drops in unmodified, since those already expose
|
|
121
|
+
the same `.launch()` shape as Playwright's own `chromium`/`firefox`/`webkit`.
|
|
103
122
|
|
|
104
123
|
```bash
|
|
105
124
|
npx create-waygraph my-project && cd my-project && npm install && npm test
|
package/dist/cli.js
CHANGED
|
@@ -1687,6 +1687,64 @@ async function validateFlows(projectDir) {
|
|
|
1687
1687
|
return results;
|
|
1688
1688
|
}
|
|
1689
1689
|
// ---------------------------------------------------------------------------
|
|
1690
|
+
// `check` - secondary/complementary to the ActionPage @deprecated warning
|
|
1691
|
+
// (src/types.ts). That's the primary, always-on signal an editor shows the
|
|
1692
|
+
// instant a regular Block's act() calls a navigation method; this command
|
|
1693
|
+
// sweeps a whole project in one shot for contexts with no editor watching
|
|
1694
|
+
// (CI, an autonomous agent writing Block files without a language server).
|
|
1695
|
+
// See openspec/changes/nav-block-and-check/ for the full rationale.
|
|
1696
|
+
// ---------------------------------------------------------------------------
|
|
1697
|
+
function discoverBlocks(projectDir) {
|
|
1698
|
+
return walkDir(projectDir, /\.block\.ts$/);
|
|
1699
|
+
}
|
|
1700
|
+
function isBlockLike(val) {
|
|
1701
|
+
if (val === null || typeof val !== "object")
|
|
1702
|
+
return false;
|
|
1703
|
+
const obj = val;
|
|
1704
|
+
if (typeof obj.name !== "string")
|
|
1705
|
+
return false;
|
|
1706
|
+
const instruction = obj.instruction;
|
|
1707
|
+
return instruction !== null && typeof instruction === "object" && typeof instruction?.act === "function";
|
|
1708
|
+
}
|
|
1709
|
+
/** Set by `defineNavBlock` (non-enumerable) - see src/engine.ts. */
|
|
1710
|
+
function isNavBlockMarked(val) {
|
|
1711
|
+
return val.__waygraphKind === "nav";
|
|
1712
|
+
}
|
|
1713
|
+
const NAV_METHOD_CALLS = ["page.goto(", "page.reload(", "page.goBack(", "page.goForward("];
|
|
1714
|
+
/**
|
|
1715
|
+
* Walks every `*.block.ts` file under `projectDir`, imports each to find its
|
|
1716
|
+
* exported Blocks, and for any Block NOT built via `defineNavBlock`, scans
|
|
1717
|
+
* that file's own source text for a navigation call. Warning only - never
|
|
1718
|
+
* throws, never changes the caller's exit code on its own account (a Block
|
|
1719
|
+
* file that fails to import for unrelated reasons is silently skipped here;
|
|
1720
|
+
* `validate` is the command that reports import failures).
|
|
1721
|
+
*/
|
|
1722
|
+
async function checkCommand(projectDir) {
|
|
1723
|
+
const files = discoverBlocks(projectDir);
|
|
1724
|
+
const warnings = [];
|
|
1725
|
+
for (const file of files) {
|
|
1726
|
+
let mod;
|
|
1727
|
+
try {
|
|
1728
|
+
mod = await importModule(file);
|
|
1729
|
+
}
|
|
1730
|
+
catch {
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
let src;
|
|
1734
|
+
for (const [exportName, exported] of Object.entries(mod)) {
|
|
1735
|
+
if (!isBlockLike(exported))
|
|
1736
|
+
continue;
|
|
1737
|
+
if (isNavBlockMarked(exported))
|
|
1738
|
+
continue;
|
|
1739
|
+
src ??= readFileSync(file, "utf-8");
|
|
1740
|
+
if (NAV_METHOD_CALLS.some((needle) => src.includes(needle))) {
|
|
1741
|
+
warnings.push({ file, blockName: exported.name, exportName });
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return warnings;
|
|
1746
|
+
}
|
|
1747
|
+
// ---------------------------------------------------------------------------
|
|
1690
1748
|
// `run`
|
|
1691
1749
|
// ---------------------------------------------------------------------------
|
|
1692
1750
|
async function runFlow(projectDir, flowName) {
|
|
@@ -1725,6 +1783,13 @@ Usage:
|
|
|
1725
1783
|
waygraph validate [project] Import and validate all flows
|
|
1726
1784
|
waygraph run <flow> [project] Run a named flow
|
|
1727
1785
|
waygraph chain <spec> [project] Run one or more Blocks by name, ad hoc
|
|
1786
|
+
waygraph check [project] Warn about navigation outside a NavBlock
|
|
1787
|
+
|
|
1788
|
+
"check" is a secondary sweep, complementary to the always-on TypeScript
|
|
1789
|
+
warning every regular Block's act() already gets (a struck-through
|
|
1790
|
+
page.goto/reload/goBack/goForward, pointing at defineNavBlock) - useful
|
|
1791
|
+
where nothing is watching in an editor (CI, generated code). Warning
|
|
1792
|
+
only, never fails - see openspec/changes/nav-block-and-check/.
|
|
1728
1793
|
|
|
1729
1794
|
"chain" needs no .flow.ts file - reference Blocks (by their own
|
|
1730
1795
|
runtime .name, or their export identifier) straight from the command
|
|
@@ -1844,6 +1909,23 @@ async function main() {
|
|
|
1844
1909
|
await runChain(proj, spec);
|
|
1845
1910
|
break;
|
|
1846
1911
|
}
|
|
1912
|
+
case "check": {
|
|
1913
|
+
const proj = resolve(args[1] ?? process.cwd());
|
|
1914
|
+
const warnings = await checkCommand(proj);
|
|
1915
|
+
if (warnings.length === 0) {
|
|
1916
|
+
console.log(`waygraph check: no navigation found outside NavBlocks under ${proj}`);
|
|
1917
|
+
}
|
|
1918
|
+
else {
|
|
1919
|
+
for (const w of warnings) {
|
|
1920
|
+
const rel = relative(proj, w.file);
|
|
1921
|
+
console.warn(`waygraph check: ${rel} (${w.blockName}) calls page.goto/reload/goBack/goForward outside a NavBlock - consider defineNavBlock instead`);
|
|
1922
|
+
}
|
|
1923
|
+
console.log(`waygraph check: ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`);
|
|
1924
|
+
}
|
|
1925
|
+
// Warning only - never fails the command, matching the spec's
|
|
1926
|
+
// "never a build failure" requirement.
|
|
1927
|
+
break;
|
|
1928
|
+
}
|
|
1847
1929
|
default:
|
|
1848
1930
|
console.error(`waygraph: unknown command "${command}"`);
|
|
1849
1931
|
process.exit(1);
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { BrowserContext, Page, Locator } from "@playwright/test";
|
|
2
|
-
import type { Block, Checkpoint, Instruction, DefinedBlock } from "./types.js";
|
|
1
|
+
import type { BrowserContext, BrowserType, Page, Locator } from "@playwright/test";
|
|
2
|
+
import type { Block, Checkpoint, Instruction, DefinedBlock, ActionPage, WaygraphHighlight } from "./types.js";
|
|
3
3
|
import { MemPage } from "./mem-page.js";
|
|
4
|
+
import type { MemKey } from "./mem-page.js";
|
|
4
5
|
import type { Trait } from "./trait.js";
|
|
5
6
|
/**
|
|
6
7
|
* Checks `block.requires` against `mem` and throws, naming every missing key at
|
|
@@ -313,6 +314,18 @@ export interface EngineConfig {
|
|
|
313
314
|
browserName?: "chromium" | "firefox" | "webkit";
|
|
314
315
|
/** Milliseconds Playwright pauses before each operation - for watching a non-headless run with your own eyes, not for real runs. Default `0`. */
|
|
315
316
|
slowMo?: number;
|
|
317
|
+
/**
|
|
318
|
+
* Overrides which `BrowserType` actually launches, per browser name -
|
|
319
|
+
* default is the real `@playwright/test` export for whichever one
|
|
320
|
+
* `browserName` picks. Accepts anything shaped like Playwright's own
|
|
321
|
+
* `chromium`/`firefox`/`webkit` (same `.launch()` signature) - a
|
|
322
|
+
* stealth-patched or otherwise customized launcher (e.g. `playwright-extra`
|
|
323
|
+
* plus a stealth plugin) drops in here unmodified. Puppeteer is a
|
|
324
|
+
* different `Page`/`BrowserContext` shape entirely and isn't supported by
|
|
325
|
+
* this seam.
|
|
326
|
+
* @example new Engine({ browsers: { chromium: stealthChromium } })
|
|
327
|
+
*/
|
|
328
|
+
browsers?: Partial<Record<"chromium" | "firefox" | "webkit", BrowserType>>;
|
|
316
329
|
}
|
|
317
330
|
type S = Checkpoint<"__start__">;
|
|
318
331
|
/**
|
|
@@ -385,11 +398,48 @@ export declare function branch<In extends Checkpoint<string>, Out extends Checkp
|
|
|
385
398
|
export declare function defineBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(base: {
|
|
386
399
|
name: string;
|
|
387
400
|
description?: Block<In, Out>["description"];
|
|
388
|
-
instruction: Instruction<In, Out, any
|
|
401
|
+
instruction: Omit<Instruction<In, Out, any>, "act"> & {
|
|
402
|
+
act(page: ActionPage, input: In, mem: MemPage): Promise<void>;
|
|
403
|
+
};
|
|
389
404
|
next?: Block<In, Out>["next"];
|
|
390
405
|
requires?: Block<In, Out>["requires"];
|
|
391
406
|
routes?: Block<In, Out>["routes"];
|
|
392
407
|
}): DefinedBlock<In, Out>;
|
|
408
|
+
/**
|
|
409
|
+
* A Block whose only possible action is navigating to a URL - built via
|
|
410
|
+
* {@link defineNavBlock}, never with a hand-authored `act()`. Extends `Block`
|
|
411
|
+
* (same shape `DefinedBlock`/`ComposedBlock` already use), so it drops into
|
|
412
|
+
* `defineFlow([...])`, `connect()`, `composeBlock()` exactly like any other
|
|
413
|
+
* Block - no special-casing needed anywhere that only expects a `Block`.
|
|
414
|
+
* See `openspec/changes/nav-block-and-check/` for why this exists.
|
|
415
|
+
*/
|
|
416
|
+
export type NavBlock<Out extends Checkpoint<string>> = DefinedBlock<Checkpoint<string>, Out>;
|
|
417
|
+
/**
|
|
418
|
+
* Builds a {@link NavBlock}. `url` accepts a plain string or a function
|
|
419
|
+
* `(mem) => string` for parameterized routes (e.g. `/dashboard/requests/:id`
|
|
420
|
+
* with a real id read from mem). `checkpoint` is the tag this NavBlock
|
|
421
|
+
* resolves to once navigation completes - a NavBlock never branches on
|
|
422
|
+
* observed evidence the way a regular Block can, so there's no `resolve`
|
|
423
|
+
* logic to author; it always just declares "arrived."
|
|
424
|
+
*
|
|
425
|
+
* The generated `act()` is always exactly `page.goto(url)` - never anything
|
|
426
|
+
* else, never author-supplied. Callers get a real `Page` here internally
|
|
427
|
+
* (the one legitimate place navigation belongs); `defineBlock`'s own
|
|
428
|
+
* `ActionPage` narrowing is what discourages navigation everywhere else,
|
|
429
|
+
* and never applies to this generated function since no author ever writes
|
|
430
|
+
* it by hand.
|
|
431
|
+
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
432
|
+
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
433
|
+
*/
|
|
434
|
+
export declare function defineNavBlock<Out extends Checkpoint<string>>(options: {
|
|
435
|
+
name: string;
|
|
436
|
+
description?: string;
|
|
437
|
+
checkpoint: Out["__state"];
|
|
438
|
+
url: string | ((mem: MemPage) => string);
|
|
439
|
+
requires?: readonly MemKey<any>[];
|
|
440
|
+
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
441
|
+
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
442
|
+
}): NavBlock<Out>;
|
|
393
443
|
/**
|
|
394
444
|
* Returns a new Block that runs exactly like `block` - same act/observe/resolve,
|
|
395
445
|
* same routing - but with `verify` replaced. For giving one flow stricter (or
|
package/dist/engine.js
CHANGED
|
@@ -3,6 +3,20 @@ import { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
import { MemPage } from "./mem-page.js";
|
|
4
4
|
import { runVerify } from "./trait.js";
|
|
5
5
|
const LAUNCHERS = { chromium, firefox, webkit };
|
|
6
|
+
/**
|
|
7
|
+
* Resolves which `BrowserType` actually launches for a `mem`-only run (no
|
|
8
|
+
* caller-supplied context) - `config.browsers[browserName]` if given,
|
|
9
|
+
* otherwise the real `@playwright/test` export. Waygraph is deliberately
|
|
10
|
+
* "just an opinionated Playwright" - it doesn't own browser automation
|
|
11
|
+
* itself, so it shouldn't force every consumer onto the vanilla launcher.
|
|
12
|
+
* A stealth-patched or otherwise customized launcher (e.g. `playwright-extra`
|
|
13
|
+
* plus a stealth plugin) is a drop-in replacement for `chromium`/`firefox`/
|
|
14
|
+
* `webkit` - same `.launch()` shape - so accepting one here costs nothing for
|
|
15
|
+
* a consumer who never sets it.
|
|
16
|
+
*/
|
|
17
|
+
function resolveLauncher(browserName, config) {
|
|
18
|
+
return config?.browsers?.[browserName] ?? LAUNCHERS[browserName];
|
|
19
|
+
}
|
|
6
20
|
/**
|
|
7
21
|
* Checks `block.requires` against `mem` and throws, naming every missing key at
|
|
8
22
|
* once, if any are unset. `connect()` unions each Block's `requires` into the
|
|
@@ -218,7 +232,7 @@ function buildFlow(middle, engineConfig) {
|
|
|
218
232
|
// run(context, mem) already has via an existing context.
|
|
219
233
|
preflight(mem, chain);
|
|
220
234
|
const browserName = config.browserName ?? "chromium";
|
|
221
|
-
const launch =
|
|
235
|
+
const launch = resolveLauncher(browserName, config);
|
|
222
236
|
// If CHROME_PATH/CHROMIUM_PATH is set in the environment (e.g. a system
|
|
223
237
|
// or Flatpak Chromium), launch that instead of Playwright's own bundled
|
|
224
238
|
// binary - only for chromium (the env var names a Chromium build, not a
|
|
@@ -391,7 +405,7 @@ export function chainFlow(...flows) {
|
|
|
391
405
|
// the point of sequencing them (a fresh browser is itself a full
|
|
392
406
|
// session reset, silently making withSessionReset a no-op).
|
|
393
407
|
const browserName = config?.browserName ?? "chromium";
|
|
394
|
-
const launch =
|
|
408
|
+
const launch = resolveLauncher(browserName, config);
|
|
395
409
|
const executablePath = browserName === "chromium" ? process.env.CHROME_PATH || process.env.CHROMIUM_PATH : undefined;
|
|
396
410
|
const browser = await launch.launch({
|
|
397
411
|
headless: config?.headless ?? true,
|
|
@@ -568,6 +582,50 @@ export function defineBlock(base) {
|
|
|
568
582
|
modVerifyAll: (patches) => modVerifyAll(plain, patches),
|
|
569
583
|
};
|
|
570
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Builds a {@link NavBlock}. `url` accepts a plain string or a function
|
|
587
|
+
* `(mem) => string` for parameterized routes (e.g. `/dashboard/requests/:id`
|
|
588
|
+
* with a real id read from mem). `checkpoint` is the tag this NavBlock
|
|
589
|
+
* resolves to once navigation completes - a NavBlock never branches on
|
|
590
|
+
* observed evidence the way a regular Block can, so there's no `resolve`
|
|
591
|
+
* logic to author; it always just declares "arrived."
|
|
592
|
+
*
|
|
593
|
+
* The generated `act()` is always exactly `page.goto(url)` - never anything
|
|
594
|
+
* else, never author-supplied. Callers get a real `Page` here internally
|
|
595
|
+
* (the one legitimate place navigation belongs); `defineBlock`'s own
|
|
596
|
+
* `ActionPage` narrowing is what discourages navigation everywhere else,
|
|
597
|
+
* and never applies to this generated function since no author ever writes
|
|
598
|
+
* it by hand.
|
|
599
|
+
* @example defineNavBlock({ name: "nav-web-login", checkpoint: "LoginForm", url: "/login" })
|
|
600
|
+
* @example defineNavBlock({ name: "nav-web-request-detail", checkpoint: "RequestDetail", url: (mem) => `/dashboard/requests/${mem.get(RequestId.key)}` })
|
|
601
|
+
*/
|
|
602
|
+
export function defineNavBlock(options) {
|
|
603
|
+
const built = defineBlock({
|
|
604
|
+
name: options.name,
|
|
605
|
+
...(options.description ? { description: options.description } : {}),
|
|
606
|
+
...(options.requires ? { requires: options.requires } : {}),
|
|
607
|
+
instruction: {
|
|
608
|
+
async act(page, _input, mem) {
|
|
609
|
+
const url = typeof options.url === "function" ? options.url(mem) : options.url;
|
|
610
|
+
await page.goto(url);
|
|
611
|
+
},
|
|
612
|
+
resolve: () => checkpoint(options.checkpoint),
|
|
613
|
+
...(options.verify ? { verify: options.verify } : {}),
|
|
614
|
+
...(options.highlights ? { highlights: options.highlights } : {}),
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
// Non-enumerable so it never shows up in Object.keys/JSON/autocomplete -
|
|
618
|
+
// purely a runtime marker `waygraph check` (or anything else walking a
|
|
619
|
+
// project's Blocks) can test for to tell a NavBlock apart from a regular
|
|
620
|
+
// one, since the TypeScript type alone (NavBlock = DefinedBlock) doesn't
|
|
621
|
+
// survive to runtime.
|
|
622
|
+
Object.defineProperty(built, "__waygraphKind", {
|
|
623
|
+
value: "nav",
|
|
624
|
+
enumerable: false,
|
|
625
|
+
configurable: false,
|
|
626
|
+
});
|
|
627
|
+
return built;
|
|
628
|
+
}
|
|
571
629
|
/**
|
|
572
630
|
* Returns a new Block that runs exactly like `block` - same act/observe/resolve,
|
|
573
631
|
* same routing - but with `verify` replaced. For giving one flow stricter (or
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start } from "./type
|
|
|
2
2
|
export { connect, checkpoint } from "./types.js";
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, composeBlock, preflight, withSessionReset, withTitle, chainFlow, } from "./engine.js";
|
|
6
|
-
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo } from "./engine.js";
|
|
5
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, composeBlock, preflight, withSessionReset, withTitle, chainFlow, } from "./engine.js";
|
|
6
|
+
export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock } from "./engine.js";
|
|
7
|
+
export type { ActionPage } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { connect, checkpoint } from "./types.js";
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, composeBlock, preflight, withSessionReset, withTitle, chainFlow, } from "./engine.js";
|
|
4
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, composeBlock, preflight, withSessionReset, withTitle, chainFlow, } from "./engine.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,29 @@ export declare function checkpoint<Tag extends string>(tag: Tag): Checkpoint<Tag
|
|
|
23
23
|
* @example export const LoginBlock: Block<Start, LoggedIn> = { ... }
|
|
24
24
|
*/
|
|
25
25
|
export type Start = Checkpoint<"__start__">;
|
|
26
|
+
/**
|
|
27
|
+
* The `page` a regular Block's `act()` is typed against via `defineBlock` -
|
|
28
|
+
* structurally identical to Playwright's own `Page` (the same real object is
|
|
29
|
+
* passed at runtime; this is a type-only narrowing), except `goto`, `reload`,
|
|
30
|
+
* `goBack`, and `goForward` are re-declared under `@deprecated`. Nothing is
|
|
31
|
+
* blocked - every method is still fully present and callable, and this never
|
|
32
|
+
* causes a build to fail - but a TypeScript-aware editor shows the call
|
|
33
|
+
* struck through with a hover warning the instant it's typed, pointing at
|
|
34
|
+
* `defineNavBlock` instead. `NavBlock`'s own generated `act()` (the one place
|
|
35
|
+
* navigation actually belongs) uses the real `Page`, not this type - that
|
|
36
|
+
* distinction lives entirely inside `defineNavBlock` and never reaches a
|
|
37
|
+
* NavBlock's own author, who never writes its `act()` by hand.
|
|
38
|
+
*/
|
|
39
|
+
export type ActionPage = Omit<Page, "goto" | "reload" | "goBack" | "goForward"> & {
|
|
40
|
+
/** @deprecated Navigation doesn't belong in a regular Block's act() - use defineNavBlock instead. */
|
|
41
|
+
goto: Page["goto"];
|
|
42
|
+
/** @deprecated Navigation doesn't belong in a regular Block's act() - use defineNavBlock instead. */
|
|
43
|
+
reload: Page["reload"];
|
|
44
|
+
/** @deprecated Navigation doesn't belong in a regular Block's act() - use defineNavBlock instead. */
|
|
45
|
+
goBack: Page["goBack"];
|
|
46
|
+
/** @deprecated Navigation doesn't belong in a regular Block's act() - use defineNavBlock instead. */
|
|
47
|
+
goForward: Page["goForward"];
|
|
48
|
+
};
|
|
26
49
|
/**
|
|
27
50
|
* The four-phase pipeline every Block runs. Most Blocks only need `act` + a
|
|
28
51
|
* one-line `resolve` - add `observe` only once a Block genuinely branches, add
|