waygraph 0.15.17 → 0.15.19
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 +77 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/practices-check.d.ts +1 -1
- package/dist/practices-check.js +36 -0
- package/dist/trait.d.ts +16 -0
- package/dist/trait.js +30 -0
- package/examples/saucedemo/.waygraph-auto/7eded669.log +0 -0
- package/examples/saucedemo/.waygraph-auto/9f0287e6.json +8 -0
- package/examples/saucedemo/.waygraph-auto/9f0287e6.log +0 -0
- package/examples/saucedemo/package-lock.json +498 -32
- package/examples/saucedemo/tests/auto-graph.spec.ts +16 -4
- package/examples/saucedemo/tests/chain-flow-episodes.spec.ts +5 -0
- package/examples/saucedemo/tests/flow-block-verify.spec.ts +7 -4
- package/package.json +1 -1
- package/templates/scaffold/src/map/(external)/mailpit/_methods/open-message.method.block.ts +1 -0
- package/templates/skills/waygraph-convention.skill.md +31 -0
package/README.md
CHANGED
|
@@ -298,10 +298,26 @@ code, an autonomous agent authoring Blocks) - it warns, never fails the process.
|
|
|
298
298
|
|
|
299
299
|
**`waygraph typecheck [project]`** runs `tsc --noEmit` then the same **bad-practice** scan
|
|
300
300
|
(wildcard `Checkpoint<string>`, assert blocks missing type args, multi-`.fill()` Methods,
|
|
301
|
-
fill+click in one Method
|
|
302
|
-
skip the scan. Opt out per
|
|
303
|
-
`// waygraph-ignore: multi-input, combined-action
|
|
304
|
-
`waygraph --skill-convention`.
|
|
301
|
+
fill+click in one Method, empty `verify` on a transition Block or `defineAssertBlock`).
|
|
302
|
+
Practices warn only unless `tsc` fails; use `--no-practices` to skip the scan. Opt out per
|
|
303
|
+
file: `// waygraph-ignore-practices` or `// waygraph-ignore: multi-input, combined-action,
|
|
304
|
+
empty-verify`. Print full agent guidance: `waygraph --skill-convention`.
|
|
305
|
+
|
|
306
|
+
**Empty verify is the one that matters most for codegen'd Blocks.** A framework can enforce
|
|
307
|
+
that a check *exists* and stays structurally honest (typed, wired, right file) - it cannot
|
|
308
|
+
author the semantic content of that check, since only someone who knows what "success" means
|
|
309
|
+
for this specific app can write it. That's a correct place to draw the line, but it shifts
|
|
310
|
+
all the risk onto whether anything catches a Block left with a placeholder `verify: []` by
|
|
311
|
+
the time it ships - which would otherwise pass every static check, the orphan scan, and
|
|
312
|
+
`tsc`, and run green in CI forever, because an empty array isn't a wrong check, it's the
|
|
313
|
+
*absence* of one. `waygraph check`/`typecheck` now flag exactly that: a `defineMethodBlock`/
|
|
314
|
+
`defineEffectBlock` whose `In`/`Out` Checkpoints actually differ (a real transition, not a
|
|
315
|
+
self-loop like `fill-username`) with no `verify` key or a literal `verify: []`, and the same
|
|
316
|
+
for `defineAssertBlock` (whose entire job is checking). Silence it deliberately, the same as
|
|
317
|
+
any other practice warning, with `// waygraph-ignore: empty-verify` - e.g. when a downstream
|
|
318
|
+
`defineAssertBlock` right after it in the `.flow.ts` is what actually confirms the transition
|
|
319
|
+
(see `templates/scaffold`'s own `open-message.method.block.ts`, confirmed by
|
|
320
|
+
`AssertEmailContentBlock` right after it, not by its own `verify`).
|
|
305
321
|
|
|
306
322
|
### Demo narration
|
|
307
323
|
|
|
@@ -412,6 +428,36 @@ engine.defineFlow([start, NavHomeBlock, AddItemBlock, AssertItemAddedBlock, Clea
|
|
|
412
428
|
Live reference: `templates/scaffold/src/map/(app_base)/home/_methods/assert-item-added.method.block.ts`,
|
|
413
429
|
wired into `shop.flow.ts`.
|
|
414
430
|
|
|
431
|
+
**An assert Block also names a feature *state*, not just a result or static content.** A
|
|
432
|
+
submit button greyed out until required fields are filled, a "Save" button re-enabling
|
|
433
|
+
after a successful edit, a field-level validation error appearing under one specific input
|
|
434
|
+
- these are conditions the UI is *in*, worth a nameable Checkpoint of their own the same way
|
|
435
|
+
`AssertItemAddedBlock` names "did add-item work." There's no built-in `Trait.disabled` -
|
|
436
|
+
write the state check as a custom Trait against the real DOM condition (`isDisabled()`, a
|
|
437
|
+
class, an `aria-disabled` attribute - whatever the app actually uses), the same pattern as
|
|
438
|
+
`removeButtonVisibleForSelectedItem` above:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const submitGatedUntilFieldsComplete: Trait = {
|
|
442
|
+
name: "submit-gated-until-fields-complete",
|
|
443
|
+
async check(page) {
|
|
444
|
+
return page.locator(CheckoutSel.submitButton).isDisabled();
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export const AssertSubmitGatedBlock = defineAssertBlock<CheckoutInfoPage>({
|
|
449
|
+
name: "assert-submit-gated",
|
|
450
|
+
checkpoint: "CheckoutInfoPage",
|
|
451
|
+
verify: [submitGatedUntilFieldsComplete],
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// wired right after the fields that gate it, before the ones that clear the gate:
|
|
455
|
+
engine.defineFlow([start, NavCheckoutInfoBlock, AssertSubmitGatedBlock, FillFirstNameBlock, ...])
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Same rule as any other assert Block: give it an explicit type argument when it sits
|
|
459
|
+
mid-chain (see above), and keep the selector in a `*Sel` object, never inlined in the Trait.
|
|
460
|
+
|
|
415
461
|
**`methods/` folder:** on-page work lives next to the route, not free-floating.
|
|
416
462
|
|
|
417
463
|
**`*Sel`:** DOM selectors only (static strings + `(id) => ...` for item-no-1 style). Mem
|
|
@@ -748,6 +794,22 @@ actually shows. Doesn't crash or corrupt anything if you never need it - it's a
|
|
|
748
794
|
gap (confirmed by reading the exact code path, `src/auto-session.ts`'s `currentMenu()`), fixed
|
|
749
795
|
outright rather than left as a caveat.
|
|
750
796
|
|
|
797
|
+
**Coverage gaps `waygraph check`/`graph` can't see, because there's no Block to scan yet.**
|
|
798
|
+
Static tools only find gaps in Blocks that already exist - a real link or button on the live
|
|
799
|
+
page with *no Block written for it at all* has nothing in source to catch it. Every live
|
|
800
|
+
`auto`/`browser`/`pilot` session watches the real page instead and warns (once per element,
|
|
801
|
+
via `console.warn`) on a same-origin `<a href>` no `defineNavBlock` covers, or a visible
|
|
802
|
+
`button`/`[role=button]`/submit input no Method/NavClick Block selector matches:
|
|
803
|
+
|
|
804
|
+
```text
|
|
805
|
+
[waygraph] unmapped nav link on this page: /settings - no NavBlock covers this URL; add defineNavBlock
|
|
806
|
+
[waygraph] unmapped button on this page: #save-draft - no Method/NavClick Block selector matches; consider defineMethodBlock or defineNavClickBlock
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Read them back with `auto console <sessionId>` - the same channel real console/network errors
|
|
810
|
+
already come through, not a separate command (`src/coverage-gap.ts`, wired into every session
|
|
811
|
+
in `src/auto-session.ts`).
|
|
812
|
+
|
|
751
813
|
**On-page indicator.** A headful (`--non-headless`) session looks like an ordinary browser
|
|
752
814
|
tab otherwise - no sign anything is driving it, which matters once a human might be watching
|
|
753
815
|
or co-driving the same visible session. Every session injects a small badge (bottom-right
|
|
@@ -952,25 +1014,22 @@ really a Nav/Page/Assert/Method from this package's own factories.
|
|
|
952
1014
|
|------|---------|
|
|
953
1015
|
| Watch Sauce Demo step-through (temp dir only) | `npx waygraph try demo` |
|
|
954
1016
|
| Interactive explore (Add/Remove/Open details from live page) | `cd examples/saucedemo && npm i && npx waygraph auto` |
|
|
955
|
-
| Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx
|
|
1017
|
+
| Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx waygraph init my-app` |
|
|
956
1018
|
| Install coding-agent defs for diving an app into Blocks | `npx waygraph agent-dive --loop claude` (also `opencode` / `cursor` / `vscode`) |
|
|
957
1019
|
| Print a packaged agent skill to stdout | `npx waygraph --skill` (list) / `--skill-pilot` / `--skill-pilot-blind` / `--skill-convention` |
|
|
958
1020
|
| Add waygraph to an existing repo | `npm install waygraph @playwright/test` |
|
|
959
1021
|
|
|
960
|
-
**Scaffold is not hidden inside waygraph alone** - the offline starter also ships as
|
|
961
|
-
[`create-waygraph`](https://github.com/deviate-dv8/create-waygraph) on npm (same template
|
|
962
|
-
as `waygraph init` since 0.7.5). Use whichever entry you already have:
|
|
963
|
-
`npx create-waygraph my-app` when reading the GitHub Pages docs, or `npx waygraph init my-app`
|
|
964
|
-
when the CLI is already installed. After either:
|
|
965
|
-
|
|
966
1022
|
```bash
|
|
1023
|
+
npx waygraph init my-app
|
|
967
1024
|
cd my-app && npm install && npx playwright install chromium && npm test
|
|
968
1025
|
```
|
|
969
1026
|
|
|
970
1027
|
`try demo` is different: live saucedemo.com, step-through **Sign In -> Shop & Checkout ->
|
|
971
1028
|
blocked Viewer login**, then headless tests. Permanent full example:
|
|
972
|
-
[`examples/saucedemo`](./examples/saucedemo). Offline empty scaffold: `init
|
|
973
|
-
|
|
1029
|
+
[`examples/saucedemo`](./examples/saucedemo). Offline empty scaffold: `init`.
|
|
1030
|
+
|
|
1031
|
+
**`create-waygraph` (npm) is deprecated** - it was the identical `init` template shipped as
|
|
1032
|
+
a second package for no real benefit. Use `npx waygraph init my-app` instead.
|
|
974
1033
|
|
|
975
1034
|
```bash
|
|
976
1035
|
# From this checkout, after npm run build:
|
|
@@ -1044,8 +1103,8 @@ suite - it forwards to the local `node_modules/.bin/playwright` (falling back to
|
|
|
1044
1103
|
`npx playwright` if the project hasn't installed it locally yet), so `waygraph test` behaves
|
|
1045
1104
|
exactly like `playwright test`, and anything after it (`--grep`, a spec path, `--headed`, …)
|
|
1046
1105
|
passes straight through. `waygraph test ui` (or `waygraph test --ui`) launches Playwright's
|
|
1047
|
-
interactive UI Mode the same way. Scaffolded projects (`waygraph init
|
|
1048
|
-
|
|
1106
|
+
interactive UI Mode the same way. Scaffolded projects (`waygraph init`) wire this up as
|
|
1107
|
+
`npm test` / `npm run test:ui`, and ship `trace: "retain-on-failure"` plus
|
|
1049
1108
|
the `html` reporter in `playwright.config.ts`, so a failing `waygraph test` already has a
|
|
1050
1109
|
trace waiting - `waygraph test report` opens the HTML report, and
|
|
1051
1110
|
`waygraph test show-trace <trace.zip>` opens one trace directly (both forward to Playwright's
|
|
@@ -1084,6 +1143,8 @@ Flows are files (0.10.5+):
|
|
|
1084
1143
|
| Scope either to one element | `waygraph auto dom <sessionId> --selector ".inventory_list"` |
|
|
1085
1144
|
| Detached session with a real visible browser | `waygraph auto --cli --detach --non-headless` |
|
|
1086
1145
|
| Read the session's Checkpoint/Block history | `waygraph auto trace <sessionId>` |
|
|
1146
|
+
| Read real console/network errors + coverage-gap warnings | `waygraph auto console <sessionId>` |
|
|
1147
|
+
| Read localStorage/sessionStorage + service workers | `waygraph auto storage <sessionId>` |
|
|
1087
1148
|
| Whole-project nav-escape + inline-selector + orphan sweep | `waygraph check [project]` |
|
|
1088
1149
|
| `tsc --noEmit` + bad-practice warnings | `waygraph typecheck [project]` (`--no-practices` to skip scan) |
|
|
1089
1150
|
| Waygraph Map enforcement (verbatim url-vs-folder, exits 1 on a violation) | `waygraph map [project]` |
|
|
@@ -1273,7 +1334,7 @@ src/
|
|
|
1273
1334
|
index.ts public barrel
|
|
1274
1335
|
examples/saucedemo/ live Sauce Demo (Page + methods/ + Sel)
|
|
1275
1336
|
templates/quickstart/ try demo / init template (mirrors sauce)
|
|
1276
|
-
templates/scaffold/ offline
|
|
1337
|
+
templates/scaffold/ offline waygraph init scaffold template
|
|
1277
1338
|
templates/agents/ waygraph agent-dive coding-agent definitions
|
|
1278
1339
|
docs/ GitHub Pages static HTML + docs/proposals/ RFCs
|
|
1279
1340
|
openspec/ spec-driven planning: specs/ (current), changes/ (in-flight),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInstanceOption } from "./types.js";
|
|
2
2
|
export { connect, checkpoint } from "./types.js";
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
|
-
export { Trait, urlMatches, textEquals, visible, frameVisible, frameTextEquals, frameContainsText } from "./trait.js";
|
|
4
|
+
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
|
|
6
6
|
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
|
|
7
7
|
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { connect, checkpoint } from "./types.js";
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
|
-
export { Trait, urlMatches, textEquals, visible, frameVisible, frameTextEquals, frameContainsText } from "./trait.js";
|
|
3
|
+
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
|
|
5
5
|
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PracticeKind = "wildcard-checkpoint" | "assert-no-type-arg" | "multi-input" | "combined-action";
|
|
1
|
+
export type PracticeKind = "wildcard-checkpoint" | "assert-no-type-arg" | "multi-input" | "combined-action" | "empty-verify";
|
|
2
2
|
export interface PracticeWarning {
|
|
3
3
|
kind: PracticeKind;
|
|
4
4
|
file: string;
|
package/dist/practices-check.js
CHANGED
|
@@ -9,6 +9,19 @@ import { relative } from "node:path";
|
|
|
9
9
|
const WILDCARD_CHECKPOINT = /\bdefine(?:Method|Effect|Page|Nav|MemNav|NavClick|Action)Block\s*<[^>]*\bCheckpoint\s*<\s*string\s*>/;
|
|
10
10
|
/** defineAssertBlock({ … }) with no explicit Out type — defaults to wildcard. */
|
|
11
11
|
const ASSERT_NO_TYPE_ARG = /\bdefineAssertBlock\s*\(\s*\{/;
|
|
12
|
+
/** defineAssertBlock, any form - used to scope the empty-verify check below. */
|
|
13
|
+
const IS_ASSERT_BLOCK = /\bdefineAssertBlock\s*[<(]/;
|
|
14
|
+
/**
|
|
15
|
+
* define(Method|Effect)Block<In, Out> with two BARE identifier type args
|
|
16
|
+
* (never a nested generic like Checkpoint<string> - that's WILDCARD_CHECKPOINT's
|
|
17
|
+
* own warning) - lets a real transition (In !== Out) be told apart from a
|
|
18
|
+
* self-loop (In === Out, e.g. fill-username) using only source text.
|
|
19
|
+
*/
|
|
20
|
+
const METHOD_EFFECT_GENERIC = /\bdefine(?:Method|Effect)Block\s*<\s*([A-Za-z_$][\w$]*)\s*,\s*([A-Za-z_$][\w$]*)\s*>/;
|
|
21
|
+
/** `verify: []` - a literal empty array, the codegen-stub shape this check exists for. */
|
|
22
|
+
const EMPTY_VERIFY_ARRAY = /\bverify\s*:\s*\[\s*\]/;
|
|
23
|
+
/** Any `verify:` key at all, empty or not. */
|
|
24
|
+
const HAS_VERIFY_KEY = /\bverify\s*:/;
|
|
12
25
|
const SCAN_GLOB = /\.(block|flow)\.ts$/;
|
|
13
26
|
const METHOD_OR_EFFECT_FILE = /\.(method|effect)\.block\.ts$/;
|
|
14
27
|
const METHOD_OR_EFFECT_DEFINE = /\bdefine(?:Method|Effect)Block\b/;
|
|
@@ -80,6 +93,27 @@ export function collectPracticeWarnings(projectDir, walkDir, opts) {
|
|
|
80
93
|
detail: "Method/Effect act() mixes .fill() and .click() — fill and submit must be separate Blocks (see examples/saucedemo login split)",
|
|
81
94
|
});
|
|
82
95
|
}
|
|
96
|
+
const genericMatch = METHOD_EFFECT_GENERIC.exec(src);
|
|
97
|
+
const isTransition = !!genericMatch && genericMatch[1] !== genericMatch[2];
|
|
98
|
+
if (isTransition &&
|
|
99
|
+
!isIgnored("empty-verify", ignore) &&
|
|
100
|
+
(!HAS_VERIFY_KEY.test(src) || EMPTY_VERIFY_ARRAY.test(src))) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
kind: "empty-verify",
|
|
103
|
+
file: rel,
|
|
104
|
+
detail: `transition Block (${genericMatch[1]} → ${genericMatch[2]}) has no verify checks — resolve() moving ` +
|
|
105
|
+
"to a new Checkpoint is never confirmed by anything; add a Trait (or defineAssertBlock right after it), " +
|
|
106
|
+
"or opt out explicitly with // waygraph-ignore: empty-verify if that's intentional",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!isIgnored("empty-verify", ignore) && IS_ASSERT_BLOCK.test(src) && EMPTY_VERIFY_ARRAY.test(src)) {
|
|
111
|
+
warnings.push({
|
|
112
|
+
kind: "empty-verify",
|
|
113
|
+
file: rel,
|
|
114
|
+
detail: "defineAssertBlock's verify array is empty — an assert Block whose only job is checking will pass " +
|
|
115
|
+
"trivially forever; fill in real Traits or remove the stub",
|
|
116
|
+
});
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
119
|
return warnings;
|
|
@@ -94,6 +128,8 @@ export function practiceKindLabel(kind) {
|
|
|
94
128
|
return "multiple inputs in one Block";
|
|
95
129
|
case "combined-action":
|
|
96
130
|
return "fill and click in one Block";
|
|
131
|
+
case "empty-verify":
|
|
132
|
+
return "empty verify — transition unconfirmed";
|
|
97
133
|
default: {
|
|
98
134
|
const _exhaustive = kind;
|
|
99
135
|
return _exhaustive;
|
package/dist/trait.d.ts
CHANGED
|
@@ -40,6 +40,20 @@ export declare function textEquals(selector: string, expected: string): Trait;
|
|
|
40
40
|
* "something matching is visible", not "the selector is unambiguous".
|
|
41
41
|
*/
|
|
42
42
|
export declare function visible(selector: string): Trait;
|
|
43
|
+
/**
|
|
44
|
+
* Passes when at least one element matching `selector` is currently disabled -
|
|
45
|
+
* a submit button greyed out until required fields are filled, a "Save" not
|
|
46
|
+
* yet re-enabled after a pending edit. A real, nameable feature *state*, not
|
|
47
|
+
* just static content or an action's result. Uses `.first()` for the same
|
|
48
|
+
* reason `visible` does. A point-in-time read via Playwright's own
|
|
49
|
+
* `isDisabled()` - no wait built in, so pair it with `Trait.visible` (or rely
|
|
50
|
+
* on `verify`'s own natural timing right after `resolve`) if the element
|
|
51
|
+
* itself might not be on the page yet.
|
|
52
|
+
* @example Trait.disabled(CheckoutSel.submitButton)
|
|
53
|
+
*/
|
|
54
|
+
export declare function disabled(selector: string): Trait;
|
|
55
|
+
/** The complement of {@link disabled} - passes once `selector` is enabled. */
|
|
56
|
+
export declare function enabled(selector: string): Trait;
|
|
43
57
|
/**
|
|
44
58
|
* `visible`, scoped to a frame (e.g. a mail catcher's message-preview iframe)
|
|
45
59
|
* instead of the top-level page. `page.locator(selector)` alone never reaches
|
|
@@ -73,6 +87,8 @@ export declare const Trait: {
|
|
|
73
87
|
url: typeof urlMatches;
|
|
74
88
|
text: typeof textEquals;
|
|
75
89
|
visible: typeof visible;
|
|
90
|
+
disabled: typeof disabled;
|
|
91
|
+
enabled: typeof enabled;
|
|
76
92
|
frameVisible: typeof frameVisible;
|
|
77
93
|
frameText: typeof frameTextEquals;
|
|
78
94
|
frameContains: typeof frameContainsText;
|
package/dist/trait.js
CHANGED
|
@@ -65,6 +65,34 @@ export function visible(selector) {
|
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Passes when at least one element matching `selector` is currently disabled -
|
|
70
|
+
* a submit button greyed out until required fields are filled, a "Save" not
|
|
71
|
+
* yet re-enabled after a pending edit. A real, nameable feature *state*, not
|
|
72
|
+
* just static content or an action's result. Uses `.first()` for the same
|
|
73
|
+
* reason `visible` does. A point-in-time read via Playwright's own
|
|
74
|
+
* `isDisabled()` - no wait built in, so pair it with `Trait.visible` (or rely
|
|
75
|
+
* on `verify`'s own natural timing right after `resolve`) if the element
|
|
76
|
+
* itself might not be on the page yet.
|
|
77
|
+
* @example Trait.disabled(CheckoutSel.submitButton)
|
|
78
|
+
*/
|
|
79
|
+
export function disabled(selector) {
|
|
80
|
+
return {
|
|
81
|
+
name: `disabled(${selector})`,
|
|
82
|
+
async check(page) {
|
|
83
|
+
return page.locator(selector).first().isDisabled();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** The complement of {@link disabled} - passes once `selector` is enabled. */
|
|
88
|
+
export function enabled(selector) {
|
|
89
|
+
return {
|
|
90
|
+
name: `enabled(${selector})`,
|
|
91
|
+
async check(page) {
|
|
92
|
+
return page.locator(selector).first().isEnabled();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
68
96
|
/**
|
|
69
97
|
* `visible`, scoped to a frame (e.g. a mail catcher's message-preview iframe)
|
|
70
98
|
* instead of the top-level page. `page.locator(selector)` alone never reaches
|
|
@@ -121,6 +149,8 @@ export const Trait = {
|
|
|
121
149
|
url: urlMatches,
|
|
122
150
|
text: textEquals,
|
|
123
151
|
visible,
|
|
152
|
+
disabled,
|
|
153
|
+
enabled,
|
|
124
154
|
frameVisible,
|
|
125
155
|
frameText: frameTextEquals,
|
|
126
156
|
frameContains: frameContainsText,
|
|
File without changes
|
|
File without changes
|
|
@@ -16,29 +16,424 @@
|
|
|
16
16
|
"typescript": "^5.6.0"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
"
|
|
20
|
-
"version": "0.
|
|
19
|
+
"node_modules/@esbuild/aix-ppc64": {
|
|
20
|
+
"version": "0.28.2",
|
|
21
|
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
|
22
|
+
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
|
|
23
|
+
"cpu": [
|
|
24
|
+
"ppc64"
|
|
25
|
+
],
|
|
21
26
|
"license": "MIT",
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
27
|
+
"optional": true,
|
|
28
|
+
"os": [
|
|
29
|
+
"aix"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"node_modules/@esbuild/android-arm": {
|
|
36
|
+
"version": "0.28.2",
|
|
37
|
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
|
|
38
|
+
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
|
|
39
|
+
"cpu": [
|
|
40
|
+
"arm"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"optional": true,
|
|
44
|
+
"os": [
|
|
45
|
+
"android"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"node_modules/@esbuild/android-arm64": {
|
|
52
|
+
"version": "0.28.2",
|
|
53
|
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
|
|
54
|
+
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
|
|
55
|
+
"cpu": [
|
|
56
|
+
"arm64"
|
|
57
|
+
],
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"optional": true,
|
|
60
|
+
"os": [
|
|
61
|
+
"android"
|
|
62
|
+
],
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"node_modules/@esbuild/android-x64": {
|
|
68
|
+
"version": "0.28.2",
|
|
69
|
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
|
|
70
|
+
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
|
|
71
|
+
"cpu": [
|
|
72
|
+
"x64"
|
|
73
|
+
],
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"optional": true,
|
|
76
|
+
"os": [
|
|
77
|
+
"android"
|
|
78
|
+
],
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=18"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"node_modules/@esbuild/darwin-arm64": {
|
|
84
|
+
"version": "0.28.2",
|
|
85
|
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
|
|
86
|
+
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
|
|
87
|
+
"cpu": [
|
|
88
|
+
"arm64"
|
|
89
|
+
],
|
|
90
|
+
"license": "MIT",
|
|
91
|
+
"optional": true,
|
|
92
|
+
"os": [
|
|
93
|
+
"darwin"
|
|
94
|
+
],
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": ">=18"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"node_modules/@esbuild/darwin-x64": {
|
|
100
|
+
"version": "0.28.2",
|
|
101
|
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
|
|
102
|
+
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
|
|
103
|
+
"cpu": [
|
|
104
|
+
"x64"
|
|
105
|
+
],
|
|
106
|
+
"license": "MIT",
|
|
107
|
+
"optional": true,
|
|
108
|
+
"os": [
|
|
109
|
+
"darwin"
|
|
110
|
+
],
|
|
111
|
+
"engines": {
|
|
112
|
+
"node": ">=18"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"node_modules/@esbuild/freebsd-arm64": {
|
|
116
|
+
"version": "0.28.2",
|
|
117
|
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
|
|
118
|
+
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
|
|
119
|
+
"cpu": [
|
|
120
|
+
"arm64"
|
|
121
|
+
],
|
|
122
|
+
"license": "MIT",
|
|
123
|
+
"optional": true,
|
|
124
|
+
"os": [
|
|
125
|
+
"freebsd"
|
|
126
|
+
],
|
|
127
|
+
"engines": {
|
|
128
|
+
"node": ">=18"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"node_modules/@esbuild/freebsd-x64": {
|
|
132
|
+
"version": "0.28.2",
|
|
133
|
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
|
|
134
|
+
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
|
|
135
|
+
"cpu": [
|
|
136
|
+
"x64"
|
|
137
|
+
],
|
|
138
|
+
"license": "MIT",
|
|
139
|
+
"optional": true,
|
|
140
|
+
"os": [
|
|
141
|
+
"freebsd"
|
|
142
|
+
],
|
|
143
|
+
"engines": {
|
|
144
|
+
"node": ">=18"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"node_modules/@esbuild/linux-arm": {
|
|
148
|
+
"version": "0.28.2",
|
|
149
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
|
|
150
|
+
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
|
|
151
|
+
"cpu": [
|
|
152
|
+
"arm"
|
|
153
|
+
],
|
|
154
|
+
"license": "MIT",
|
|
155
|
+
"optional": true,
|
|
156
|
+
"os": [
|
|
157
|
+
"linux"
|
|
158
|
+
],
|
|
159
|
+
"engines": {
|
|
160
|
+
"node": ">=18"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"node_modules/@esbuild/linux-arm64": {
|
|
164
|
+
"version": "0.28.2",
|
|
165
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
|
|
166
|
+
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
|
|
167
|
+
"cpu": [
|
|
168
|
+
"arm64"
|
|
169
|
+
],
|
|
170
|
+
"license": "MIT",
|
|
171
|
+
"optional": true,
|
|
172
|
+
"os": [
|
|
173
|
+
"linux"
|
|
174
|
+
],
|
|
175
|
+
"engines": {
|
|
176
|
+
"node": ">=18"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"node_modules/@esbuild/linux-ia32": {
|
|
180
|
+
"version": "0.28.2",
|
|
181
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
|
|
182
|
+
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
|
|
183
|
+
"cpu": [
|
|
184
|
+
"ia32"
|
|
185
|
+
],
|
|
186
|
+
"license": "MIT",
|
|
187
|
+
"optional": true,
|
|
188
|
+
"os": [
|
|
189
|
+
"linux"
|
|
190
|
+
],
|
|
191
|
+
"engines": {
|
|
192
|
+
"node": ">=18"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"node_modules/@esbuild/linux-loong64": {
|
|
196
|
+
"version": "0.28.2",
|
|
197
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
|
|
198
|
+
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
|
|
199
|
+
"cpu": [
|
|
200
|
+
"loong64"
|
|
201
|
+
],
|
|
202
|
+
"license": "MIT",
|
|
203
|
+
"optional": true,
|
|
204
|
+
"os": [
|
|
205
|
+
"linux"
|
|
206
|
+
],
|
|
207
|
+
"engines": {
|
|
208
|
+
"node": ">=18"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"node_modules/@esbuild/linux-mips64el": {
|
|
212
|
+
"version": "0.28.2",
|
|
213
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
|
|
214
|
+
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
|
|
215
|
+
"cpu": [
|
|
216
|
+
"mips64el"
|
|
217
|
+
],
|
|
218
|
+
"license": "MIT",
|
|
219
|
+
"optional": true,
|
|
220
|
+
"os": [
|
|
221
|
+
"linux"
|
|
222
|
+
],
|
|
223
|
+
"engines": {
|
|
224
|
+
"node": ">=18"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"node_modules/@esbuild/linux-ppc64": {
|
|
228
|
+
"version": "0.28.2",
|
|
229
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
|
|
230
|
+
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
|
|
231
|
+
"cpu": [
|
|
232
|
+
"ppc64"
|
|
233
|
+
],
|
|
234
|
+
"license": "MIT",
|
|
235
|
+
"optional": true,
|
|
236
|
+
"os": [
|
|
237
|
+
"linux"
|
|
238
|
+
],
|
|
239
|
+
"engines": {
|
|
240
|
+
"node": ">=18"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"node_modules/@esbuild/linux-riscv64": {
|
|
244
|
+
"version": "0.28.2",
|
|
245
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
|
|
246
|
+
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
|
|
247
|
+
"cpu": [
|
|
248
|
+
"riscv64"
|
|
249
|
+
],
|
|
250
|
+
"license": "MIT",
|
|
251
|
+
"optional": true,
|
|
252
|
+
"os": [
|
|
253
|
+
"linux"
|
|
254
|
+
],
|
|
255
|
+
"engines": {
|
|
256
|
+
"node": ">=18"
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"node_modules/@esbuild/linux-s390x": {
|
|
260
|
+
"version": "0.28.2",
|
|
261
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
|
|
262
|
+
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
|
|
263
|
+
"cpu": [
|
|
264
|
+
"s390x"
|
|
265
|
+
],
|
|
266
|
+
"license": "MIT",
|
|
267
|
+
"optional": true,
|
|
268
|
+
"os": [
|
|
269
|
+
"linux"
|
|
270
|
+
],
|
|
271
|
+
"engines": {
|
|
272
|
+
"node": ">=18"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"node_modules/@esbuild/linux-x64": {
|
|
276
|
+
"version": "0.28.2",
|
|
277
|
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
|
|
278
|
+
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
|
|
279
|
+
"cpu": [
|
|
280
|
+
"x64"
|
|
281
|
+
],
|
|
282
|
+
"license": "MIT",
|
|
283
|
+
"optional": true,
|
|
284
|
+
"os": [
|
|
285
|
+
"linux"
|
|
286
|
+
],
|
|
287
|
+
"engines": {
|
|
288
|
+
"node": ">=18"
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"node_modules/@esbuild/netbsd-arm64": {
|
|
292
|
+
"version": "0.28.2",
|
|
293
|
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
|
|
294
|
+
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
|
|
295
|
+
"cpu": [
|
|
296
|
+
"arm64"
|
|
297
|
+
],
|
|
298
|
+
"license": "MIT",
|
|
299
|
+
"optional": true,
|
|
300
|
+
"os": [
|
|
301
|
+
"netbsd"
|
|
302
|
+
],
|
|
303
|
+
"engines": {
|
|
304
|
+
"node": ">=18"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"node_modules/@esbuild/netbsd-x64": {
|
|
308
|
+
"version": "0.28.2",
|
|
309
|
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
|
|
310
|
+
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
|
|
311
|
+
"cpu": [
|
|
312
|
+
"x64"
|
|
313
|
+
],
|
|
314
|
+
"license": "MIT",
|
|
315
|
+
"optional": true,
|
|
316
|
+
"os": [
|
|
317
|
+
"netbsd"
|
|
318
|
+
],
|
|
319
|
+
"engines": {
|
|
320
|
+
"node": ">=18"
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
"node_modules/@esbuild/openbsd-arm64": {
|
|
324
|
+
"version": "0.28.2",
|
|
325
|
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
|
|
326
|
+
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
|
|
327
|
+
"cpu": [
|
|
328
|
+
"arm64"
|
|
329
|
+
],
|
|
330
|
+
"license": "MIT",
|
|
331
|
+
"optional": true,
|
|
332
|
+
"os": [
|
|
333
|
+
"openbsd"
|
|
334
|
+
],
|
|
335
|
+
"engines": {
|
|
336
|
+
"node": ">=18"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"node_modules/@esbuild/openbsd-x64": {
|
|
340
|
+
"version": "0.28.2",
|
|
341
|
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
|
|
342
|
+
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
|
|
343
|
+
"cpu": [
|
|
344
|
+
"x64"
|
|
345
|
+
],
|
|
346
|
+
"license": "MIT",
|
|
347
|
+
"optional": true,
|
|
348
|
+
"os": [
|
|
349
|
+
"openbsd"
|
|
350
|
+
],
|
|
351
|
+
"engines": {
|
|
352
|
+
"node": ">=18"
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"node_modules/@esbuild/openharmony-arm64": {
|
|
356
|
+
"version": "0.28.2",
|
|
357
|
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
|
|
358
|
+
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
|
|
359
|
+
"cpu": [
|
|
360
|
+
"arm64"
|
|
361
|
+
],
|
|
362
|
+
"license": "MIT",
|
|
363
|
+
"optional": true,
|
|
364
|
+
"os": [
|
|
365
|
+
"openharmony"
|
|
366
|
+
],
|
|
367
|
+
"engines": {
|
|
368
|
+
"node": ">=18"
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
"node_modules/@esbuild/sunos-x64": {
|
|
372
|
+
"version": "0.28.2",
|
|
373
|
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
|
|
374
|
+
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
|
|
375
|
+
"cpu": [
|
|
376
|
+
"x64"
|
|
377
|
+
],
|
|
378
|
+
"license": "MIT",
|
|
379
|
+
"optional": true,
|
|
380
|
+
"os": [
|
|
381
|
+
"sunos"
|
|
382
|
+
],
|
|
383
|
+
"engines": {
|
|
384
|
+
"node": ">=18"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"node_modules/@esbuild/win32-arm64": {
|
|
388
|
+
"version": "0.28.2",
|
|
389
|
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
|
|
390
|
+
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
|
|
391
|
+
"cpu": [
|
|
392
|
+
"arm64"
|
|
393
|
+
],
|
|
394
|
+
"license": "MIT",
|
|
395
|
+
"optional": true,
|
|
396
|
+
"os": [
|
|
397
|
+
"win32"
|
|
398
|
+
],
|
|
399
|
+
"engines": {
|
|
400
|
+
"node": ">=18"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
"node_modules/@esbuild/win32-ia32": {
|
|
404
|
+
"version": "0.28.2",
|
|
405
|
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
|
|
406
|
+
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
|
|
407
|
+
"cpu": [
|
|
408
|
+
"ia32"
|
|
409
|
+
],
|
|
410
|
+
"license": "MIT",
|
|
411
|
+
"optional": true,
|
|
412
|
+
"os": [
|
|
413
|
+
"win32"
|
|
414
|
+
],
|
|
415
|
+
"engines": {
|
|
416
|
+
"node": ">=18"
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
"node_modules/@esbuild/win32-x64": {
|
|
420
|
+
"version": "0.28.2",
|
|
421
|
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
|
|
422
|
+
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
|
|
423
|
+
"cpu": [
|
|
424
|
+
"x64"
|
|
425
|
+
],
|
|
426
|
+
"license": "MIT",
|
|
427
|
+
"optional": true,
|
|
428
|
+
"os": [
|
|
429
|
+
"win32"
|
|
430
|
+
],
|
|
431
|
+
"engines": {
|
|
432
|
+
"node": ">=18"
|
|
35
433
|
}
|
|
36
434
|
},
|
|
37
435
|
"node_modules/@playwright/test": {
|
|
38
436
|
"version": "1.63.0",
|
|
39
|
-
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
|
|
40
|
-
"integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
|
|
41
|
-
"dev": true,
|
|
42
437
|
"license": "Apache-2.0",
|
|
43
438
|
"dependencies": {
|
|
44
439
|
"playwright": "1.63.0"
|
|
@@ -52,19 +447,69 @@
|
|
|
52
447
|
},
|
|
53
448
|
"node_modules/@types/node": {
|
|
54
449
|
"version": "26.6.1",
|
|
55
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.1.tgz",
|
|
56
|
-
"integrity": "sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==",
|
|
57
450
|
"dev": true,
|
|
58
451
|
"license": "MIT",
|
|
59
452
|
"dependencies": {
|
|
60
453
|
"undici-types": "~8.9.0"
|
|
61
454
|
}
|
|
62
455
|
},
|
|
456
|
+
"node_modules/esbuild": {
|
|
457
|
+
"version": "0.28.2",
|
|
458
|
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
|
|
459
|
+
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
|
460
|
+
"hasInstallScript": true,
|
|
461
|
+
"license": "MIT",
|
|
462
|
+
"bin": {
|
|
463
|
+
"esbuild": "bin/esbuild"
|
|
464
|
+
},
|
|
465
|
+
"engines": {
|
|
466
|
+
"node": ">=18"
|
|
467
|
+
},
|
|
468
|
+
"optionalDependencies": {
|
|
469
|
+
"@esbuild/aix-ppc64": "0.28.2",
|
|
470
|
+
"@esbuild/android-arm": "0.28.2",
|
|
471
|
+
"@esbuild/android-arm64": "0.28.2",
|
|
472
|
+
"@esbuild/android-x64": "0.28.2",
|
|
473
|
+
"@esbuild/darwin-arm64": "0.28.2",
|
|
474
|
+
"@esbuild/darwin-x64": "0.28.2",
|
|
475
|
+
"@esbuild/freebsd-arm64": "0.28.2",
|
|
476
|
+
"@esbuild/freebsd-x64": "0.28.2",
|
|
477
|
+
"@esbuild/linux-arm": "0.28.2",
|
|
478
|
+
"@esbuild/linux-arm64": "0.28.2",
|
|
479
|
+
"@esbuild/linux-ia32": "0.28.2",
|
|
480
|
+
"@esbuild/linux-loong64": "0.28.2",
|
|
481
|
+
"@esbuild/linux-mips64el": "0.28.2",
|
|
482
|
+
"@esbuild/linux-ppc64": "0.28.2",
|
|
483
|
+
"@esbuild/linux-riscv64": "0.28.2",
|
|
484
|
+
"@esbuild/linux-s390x": "0.28.2",
|
|
485
|
+
"@esbuild/linux-x64": "0.28.2",
|
|
486
|
+
"@esbuild/netbsd-arm64": "0.28.2",
|
|
487
|
+
"@esbuild/netbsd-x64": "0.28.2",
|
|
488
|
+
"@esbuild/openbsd-arm64": "0.28.2",
|
|
489
|
+
"@esbuild/openbsd-x64": "0.28.2",
|
|
490
|
+
"@esbuild/openharmony-arm64": "0.28.2",
|
|
491
|
+
"@esbuild/sunos-x64": "0.28.2",
|
|
492
|
+
"@esbuild/win32-arm64": "0.28.2",
|
|
493
|
+
"@esbuild/win32-ia32": "0.28.2",
|
|
494
|
+
"@esbuild/win32-x64": "0.28.2"
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
"node_modules/fsevents": {
|
|
498
|
+
"version": "2.3.3",
|
|
499
|
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
500
|
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
501
|
+
"hasInstallScript": true,
|
|
502
|
+
"license": "MIT",
|
|
503
|
+
"optional": true,
|
|
504
|
+
"os": [
|
|
505
|
+
"darwin"
|
|
506
|
+
],
|
|
507
|
+
"engines": {
|
|
508
|
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
509
|
+
}
|
|
510
|
+
},
|
|
63
511
|
"node_modules/playwright": {
|
|
64
512
|
"version": "1.63.0",
|
|
65
|
-
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
|
|
66
|
-
"integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
|
|
67
|
-
"dev": true,
|
|
68
513
|
"license": "Apache-2.0",
|
|
69
514
|
"dependencies": {
|
|
70
515
|
"playwright-core": "1.63.0"
|
|
@@ -78,9 +523,6 @@
|
|
|
78
523
|
},
|
|
79
524
|
"node_modules/playwright-core": {
|
|
80
525
|
"version": "1.63.0",
|
|
81
|
-
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
|
|
82
|
-
"integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
|
|
83
|
-
"dev": true,
|
|
84
526
|
"license": "Apache-2.0",
|
|
85
527
|
"bin": {
|
|
86
528
|
"playwright-core": "cli.js"
|
|
@@ -89,10 +531,26 @@
|
|
|
89
531
|
"node": ">=20"
|
|
90
532
|
}
|
|
91
533
|
},
|
|
534
|
+
"node_modules/tsx": {
|
|
535
|
+
"version": "4.23.15",
|
|
536
|
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
|
|
537
|
+
"integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
|
|
538
|
+
"license": "MIT",
|
|
539
|
+
"dependencies": {
|
|
540
|
+
"esbuild": "~0.28.0"
|
|
541
|
+
},
|
|
542
|
+
"bin": {
|
|
543
|
+
"tsx": "dist/cli.mjs"
|
|
544
|
+
},
|
|
545
|
+
"engines": {
|
|
546
|
+
"node": ">=18.0.0"
|
|
547
|
+
},
|
|
548
|
+
"optionalDependencies": {
|
|
549
|
+
"fsevents": "~2.3.3"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
92
552
|
"node_modules/typescript": {
|
|
93
553
|
"version": "5.9.3",
|
|
94
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
95
|
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
96
554
|
"dev": true,
|
|
97
555
|
"license": "Apache-2.0",
|
|
98
556
|
"bin": {
|
|
@@ -105,14 +563,22 @@
|
|
|
105
563
|
},
|
|
106
564
|
"node_modules/undici-types": {
|
|
107
565
|
"version": "8.9.0",
|
|
108
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
|
109
|
-
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
|
110
566
|
"dev": true,
|
|
111
567
|
"license": "MIT"
|
|
112
568
|
},
|
|
113
569
|
"node_modules/waygraph": {
|
|
114
|
-
"
|
|
115
|
-
"
|
|
570
|
+
"version": "0.15.17",
|
|
571
|
+
"resolved": "file:../..",
|
|
572
|
+
"license": "MIT",
|
|
573
|
+
"dependencies": {
|
|
574
|
+
"tsx": "^4.0.0"
|
|
575
|
+
},
|
|
576
|
+
"bin": {
|
|
577
|
+
"waygraph": "bin/waygraph"
|
|
578
|
+
},
|
|
579
|
+
"peerDependencies": {
|
|
580
|
+
"@playwright/test": "^1.40.0"
|
|
581
|
+
}
|
|
116
582
|
}
|
|
117
583
|
}
|
|
118
584
|
}
|
|
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
|
|
6
6
|
const projectDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
7
|
|
|
8
|
-
test("waygraph auto discovers the decomposed saucedemo graph
|
|
8
|
+
test("waygraph auto discovers the decomposed saucedemo graph, skipping only the FFCompose auth step", async () => {
|
|
9
9
|
const graph = await discoverGraph(projectDir);
|
|
10
10
|
|
|
11
11
|
expect(graph.nodes.map((n) => n.checkpoint).sort()).toEqual(
|
|
@@ -20,8 +20,12 @@ test("waygraph auto discovers the decomposed saucedemo graph with zero skipped B
|
|
|
20
20
|
"OrderComplete",
|
|
21
21
|
].sort(),
|
|
22
22
|
);
|
|
23
|
-
//
|
|
24
|
-
|
|
23
|
+
// 24 vs the earlier 15: checkout-info split into fill-first-name/
|
|
24
|
+
// fill-last-name/fill-postal-code (+2 net) plus the atomic login Blocks
|
|
25
|
+
// (fill-username/fill-password/submit-login's 2 branches) that static
|
|
26
|
+
// discovery still sees even though checkoutFlow itself now drives them
|
|
27
|
+
// through the ff-owner-auth composition below.
|
|
28
|
+
expect(graph.edges.length).toBe(24);
|
|
25
29
|
const removeEdges = graph.edges.filter((e) => e.block === "remove-from-cart");
|
|
26
30
|
expect(removeEdges).toEqual(
|
|
27
31
|
expect.arrayContaining([
|
|
@@ -32,7 +36,15 @@ test("waygraph auto discovers the decomposed saucedemo graph with zero skipped B
|
|
|
32
36
|
|
|
33
37
|
const loginFail = graph.edges.find((e) => e.block === "submit-login" && e.to === "LoginPage");
|
|
34
38
|
expect(loginFail).toMatchObject({ from: "LoginPage", to: "LoginPage", kind: "action" });
|
|
35
|
-
|
|
39
|
+
|
|
40
|
+
// ff-owner-auth (fastForwardComposeBlock) collapses nav-login + fill-username
|
|
41
|
+
// + fill-password + submit-login into one opaque demo/run step - discoverGraph
|
|
42
|
+
// only understands plain defineMethodBlock/defineEffectBlock<In, Out> generic
|
|
43
|
+
// calls, so this one Block is expected to be skipped by static discovery even
|
|
44
|
+
// though it's a real, used step (see src/blocks/saucedemo-web/ff-owner-auth.block.ts).
|
|
45
|
+
expect(graph.skipped).toEqual([
|
|
46
|
+
expect.objectContaining({ block: "ff-owner-auth" }),
|
|
47
|
+
]);
|
|
36
48
|
|
|
37
49
|
const logout = graph.edges.find((e) => e.block === "submit-logout");
|
|
38
50
|
expect(logout).toMatchObject({ from: "LoggedIn", to: "LoginPage", kind: "action" });
|
|
@@ -29,10 +29,15 @@ test("each episode's title survives chaining, and blocks() reports the flattened
|
|
|
29
29
|
|
|
30
30
|
expect(blocks.map((b) => b.name)).toEqual([
|
|
31
31
|
"nav-login",
|
|
32
|
+
"fill-username",
|
|
33
|
+
"fill-password",
|
|
32
34
|
"submit-login",
|
|
33
35
|
"add-to-cart",
|
|
34
36
|
"nav-cart",
|
|
35
37
|
"nav-checkout-info",
|
|
38
|
+
"fill-first-name",
|
|
39
|
+
"fill-last-name",
|
|
40
|
+
"fill-postal-code",
|
|
36
41
|
"submit-checkout-info",
|
|
37
42
|
"finish-order",
|
|
38
43
|
]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test, expect } from "@playwright/test";
|
|
2
2
|
import { MemPage, checkpoint, Trait } from "waygraph";
|
|
3
3
|
import { checkoutFlow } from "../src/flows/checkout.flow.js";
|
|
4
|
-
import {
|
|
4
|
+
import { FfOwnerAuthBlock } from "../src/blocks/saucedemo-web/ff-owner-auth.block.js";
|
|
5
5
|
import { LoginCreds, SelectedItem } from "../src/states/checkout.mem-keys.js";
|
|
6
6
|
|
|
7
7
|
// checkoutFlow.withBlockVerify/modBlockVerify - a spec that only imports the
|
|
@@ -31,9 +31,12 @@ test("withBlockVerify: replacing login's verify with an extra real check still p
|
|
|
31
31
|
mem.set(LoginCreds({ username: "standard_user", password: "secret_sauce" }));
|
|
32
32
|
mem.set(SelectedItem({ id: "sauce-labs-backpack", name: "Sauce Labs Backpack" }));
|
|
33
33
|
|
|
34
|
-
// Addressed by the actual
|
|
35
|
-
// form: no typo risk, refactor-safe, jump-to-definition works.
|
|
36
|
-
|
|
34
|
+
// Addressed by the actual FfOwnerAuthBlock reference - the preferred
|
|
35
|
+
// form: no typo risk, refactor-safe, jump-to-definition works. checkoutFlow
|
|
36
|
+
// drives login through the ff-owner-auth FFCompose step (collapsed
|
|
37
|
+
// nav-login + fill-username + fill-password + submit-login), not a bare
|
|
38
|
+
// SubmitLoginBlock reference, so that's the Block whose verify this patches.
|
|
39
|
+
const patched = checkoutFlow.withBlockVerify(FfOwnerAuthBlock, [
|
|
37
40
|
Trait.url({ pathname: "/inventory.html" }),
|
|
38
41
|
Trait.visible(".inventory_list"),
|
|
39
42
|
]);
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// waygraph-ignore: empty-verify (confirmed by AssertEmailContentBlock right after it in mail-verify.flow.ts)
|
|
1
2
|
import { defineMethodBlock, checkpoint } from "waygraph";
|
|
2
3
|
import type { MailpitInbox, MailpitMessageOpen } from "../../../../states/demo.states.js";
|
|
3
4
|
import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
|
|
@@ -22,6 +22,11 @@ against a live site, use `--skill-pilot-blind`.
|
|
|
22
22
|
inline selector literals in `Trait.visible` / `Trait.text`.
|
|
23
23
|
4. **Assert-only = `defineAssertBlock`.** Self-loop, no hand-written `act`/`resolve`. Pass
|
|
24
24
|
an explicit type argument when it sits between two specifically-typed Blocks in a Flow.
|
|
25
|
+
Not just static content or an action's result - it also names a *feature state* worth a
|
|
26
|
+
Checkpoint of its own (a submit button greyed out until required fields are filled, a
|
|
27
|
+
field error under one input, a stale "Save" re-enabling). `Trait.disabled(selector)` /
|
|
28
|
+
`Trait.enabled(selector)` cover that directly; write a custom Trait only for a state
|
|
29
|
+
condition neither one expresses.
|
|
25
30
|
5. **`requires` = externally supplied.** A key an earlier Block in the same chain produces
|
|
26
31
|
is never listed under `requires` - preflight checks the whole chain up front.
|
|
27
32
|
6. **Keep Checkpoints typed — never `Checkpoint<string>`.** Block helpers take concrete
|
|
@@ -32,6 +37,16 @@ against a live site, use `--skill-pilot-blind`.
|
|
|
32
37
|
7. **One input/action per Method Block.** No block that `.fill()`s two fields or `.fill()` + `.click()` in one
|
|
33
38
|
`act()` — split like saucedemo login (`fill-username`, `fill-password`, `submit-login`). Opt out per file:
|
|
34
39
|
`// waygraph-ignore: multi-input` or `// waygraph-ignore-practices`.
|
|
40
|
+
8. **A transition never ships with an empty `verify`.** A framework can enforce that a check
|
|
41
|
+
*exists*; it can't author the semantic content of one - only whoever knows what "success"
|
|
42
|
+
means for this app can write it. That's the right line to draw, but it means an unfilled
|
|
43
|
+
`verify: []` placeholder (codegen's honest way of not guessing) is indistinguishable from
|
|
44
|
+
a Block that was never given real verification at all, and it passes `check`/`typecheck`/
|
|
45
|
+
the orphan scan/`tsc` silently unless something is actually looking for it. Something is:
|
|
46
|
+
`defineMethodBlock`/`defineEffectBlock` where `In`/`Out` genuinely differ (a transition,
|
|
47
|
+
not a self-loop) needs a non-empty `verify`, same for `defineAssertBlock`. Opt out only
|
|
48
|
+
when a *different* Block right after it in the `.flow.ts` is what actually confirms the
|
|
49
|
+
transition (e.g. an `AssertXBlock`) - `// waygraph-ignore: empty-verify`.
|
|
35
50
|
|
|
36
51
|
## Block kinds (file convention)
|
|
37
52
|
|
|
@@ -105,6 +120,22 @@ npm run typecheck # when the consumer has it
|
|
|
105
120
|
are still runnable in Pilot's live menu - orphan status only blocks
|
|
106
121
|
`auto --blocks <From> <To>` pathfinding.
|
|
107
122
|
|
|
123
|
+
**Static tools above only find gaps in Blocks that already exist.** A real link/button on
|
|
124
|
+
the live page with *no Block written for it at all* has nothing in source for `check`/
|
|
125
|
+
`graph` to scan - the only way to find that is to actually run the app. Every live
|
|
126
|
+
`auto`/`browser`/`pilot` session watches the real page and warns (once per element) on
|
|
127
|
+
anything with no matching Block:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
[waygraph] unmapped nav link on this page: /settings - no NavBlock covers this URL; add defineNavBlock
|
|
131
|
+
[waygraph] unmapped button on this page: #save-draft - no Method/NavClick Block selector matches; consider defineMethodBlock or defineNavClickBlock
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Read them back with `auto console <sessionId>` (same channel as real console/network
|
|
135
|
+
errors) - see `--skill-pilot`'s "Coverage gaps on the live page" section for the full
|
|
136
|
+
detection rules (same-origin `<a href>` only, visible clickables only, one warning per
|
|
137
|
+
element per session).
|
|
138
|
+
|
|
108
139
|
## Related skills (print via CLI)
|
|
109
140
|
|
|
110
141
|
```bash
|