waygraph 0.13.6 → 0.14.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 +820 -232
- package/assets/stubs/stub.mp4 +0 -0
- package/assets/stubs/stub.pdf +32 -0
- package/assets/stubs/stub.png +0 -0
- package/dist/agent-dive.d.ts +9 -0
- package/dist/agent-dive.js +32 -0
- package/dist/auto-explore-run.d.ts +20 -2
- package/dist/auto-explore-run.js +9 -8
- package/dist/auto-explore.js +22 -2
- package/dist/auto-session-ipc.d.ts +123 -0
- package/dist/auto-session-ipc.js +381 -0
- package/dist/auto-session.d.ts +342 -0
- package/dist/auto-session.js +760 -0
- package/dist/cli.js +417 -51
- package/dist/engine.d.ts +40 -1
- package/dist/engine.js +82 -0
- package/dist/graph.d.ts +18 -0
- package/dist/graph.js +65 -2
- package/dist/highlights.d.ts +20 -1
- package/dist/highlights.js +60 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +5 -3
- package/dist/pilot-overlay.d.ts +88 -0
- package/dist/pilot-overlay.js +363 -0
- package/dist/pilot.d.ts +49 -0
- package/dist/pilot.js +53 -0
- package/dist/trait.d.ts +22 -0
- package/dist/trait.js +60 -3
- package/examples/routed-demo/package.json +26 -0
- package/examples/routed-demo/playwright.config.ts +20 -0
- package/examples/routed-demo/scripts/fixture-server.mjs +37 -0
- package/examples/routed-demo/scripts/with-fixture.mjs +70 -0
- package/examples/routed-demo/src/flows/routed-demo.flow.ts +12 -0
- package/examples/routed-demo/src/pages/dashboard.html +10 -0
- package/examples/routed-demo/src/pages/docs.html +8 -0
- package/examples/routed-demo/src/routes/(base_app)/dashboard/dashboard.sel.ts +10 -0
- package/examples/routed-demo/src/routes/(base_app)/dashboard/methods/click-widget.block.ts +23 -0
- package/examples/routed-demo/src/routes/(base_app)/dashboard/nav.block.ts +18 -0
- package/examples/routed-demo/src/routes/(base_app)/dashboard/page.block.ts +22 -0
- package/examples/routed-demo/src/routes/(external)/docs/docs.sel.ts +7 -0
- package/examples/routed-demo/src/routes/(external)/docs/nav.block.ts +21 -0
- package/examples/routed-demo/src/routes/(external)/docs/page.block.ts +15 -0
- package/examples/routed-demo/src/states/routed-demo.states.ts +10 -0
- package/examples/routed-demo/tests/routed-demo.spec.ts +75 -0
- package/examples/routed-demo/tsconfig.json +18 -0
- package/examples/routed-demo-consumer/package.json +18 -0
- package/examples/saucedemo/.waygraph-auto/010dd92b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/011ad671.log +0 -0
- package/examples/saucedemo/.waygraph-auto/01b45645.log +0 -0
- package/examples/saucedemo/.waygraph-auto/03764153.log +0 -0
- package/examples/saucedemo/.waygraph-auto/0593587e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/06d12823.log +0 -0
- package/examples/saucedemo/.waygraph-auto/0b1f5b26.json +8 -0
- package/examples/saucedemo/.waygraph-auto/0c00f25f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/0e480591.log +0 -0
- package/examples/saucedemo/.waygraph-auto/110958b8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/138107c1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/159d541b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/16aa2c01.log +0 -0
- package/examples/saucedemo/.waygraph-auto/16c35dd5.log +0 -0
- package/examples/saucedemo/.waygraph-auto/1706547d.log +0 -0
- package/examples/saucedemo/.waygraph-auto/17537e42.log +0 -0
- package/examples/saucedemo/.waygraph-auto/17ede493.log +0 -0
- package/examples/saucedemo/.waygraph-auto/1fe2414f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/1fe2e3b8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/25f68ada.log +0 -0
- package/examples/saucedemo/.waygraph-auto/26ca9cd8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/2bb6c4e8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/2d425004.log +0 -0
- package/examples/saucedemo/.waygraph-auto/2f5b3ea6.log +0 -0
- package/examples/saucedemo/.waygraph-auto/31bb303d.log +0 -0
- package/examples/saucedemo/.waygraph-auto/31f2042f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/36551ec1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/36a1c274.log +0 -0
- package/examples/saucedemo/.waygraph-auto/381aa338.log +0 -0
- package/examples/saucedemo/.waygraph-auto/3882bce1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/38acd1dc.log +0 -0
- package/examples/saucedemo/.waygraph-auto/3b8b77b9.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4028d2a2.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4168b208.log +0 -0
- package/examples/saucedemo/.waygraph-auto/43ab66ce.log +0 -0
- package/examples/saucedemo/.waygraph-auto/43f66838.log +0 -0
- package/examples/saucedemo/.waygraph-auto/442171d4.log +0 -0
- package/examples/saucedemo/.waygraph-auto/453b1ce1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/457322bd.log +0 -0
- package/examples/saucedemo/.waygraph-auto/46197027.log +0 -0
- package/examples/saucedemo/.waygraph-auto/46889468.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4890da1c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4893a63b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4af71440.log +0 -0
- package/examples/saucedemo/.waygraph-auto/4f7496d1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/50e8baaa.log +0 -0
- package/examples/saucedemo/.waygraph-auto/52b3a455.log +0 -0
- package/examples/saucedemo/.waygraph-auto/532dd9b8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/5391ce40.log +0 -0
- package/examples/saucedemo/.waygraph-auto/57dbddb3.log +0 -0
- package/examples/saucedemo/.waygraph-auto/5897f501.log +0 -0
- package/examples/saucedemo/.waygraph-auto/59c0b57d.log +0 -0
- package/examples/saucedemo/.waygraph-auto/5b511794.log +0 -0
- package/examples/saucedemo/.waygraph-auto/5c47910f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/5eda8273.log +0 -0
- package/examples/saucedemo/.waygraph-auto/621518ad.log +0 -0
- package/examples/saucedemo/.waygraph-auto/632b7118.log +0 -0
- package/examples/saucedemo/.waygraph-auto/64314c2b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/66516213.log +0 -0
- package/examples/saucedemo/.waygraph-auto/66c17079.log +0 -0
- package/examples/saucedemo/.waygraph-auto/69bf0b80.log +0 -0
- package/examples/saucedemo/.waygraph-auto/69c2347a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/6d22bdb0.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7103bebb.log +0 -0
- package/examples/saucedemo/.waygraph-auto/740c9faa.log +0 -0
- package/examples/saucedemo/.waygraph-auto/74872fc5.log +0 -0
- package/examples/saucedemo/.waygraph-auto/75846ca9.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7626c85b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/76308eec.log +0 -0
- package/examples/saucedemo/.waygraph-auto/76c1650e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7861f38f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7af224ed.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7c3be585.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7ed55a2c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/80086ba2.log +0 -0
- package/examples/saucedemo/.waygraph-auto/80206369.log +0 -0
- package/examples/saucedemo/.waygraph-auto/847f1092.log +0 -0
- package/examples/saucedemo/.waygraph-auto/8581abfa.log +0 -0
- package/examples/saucedemo/.waygraph-auto/87b7e0fd.log +0 -0
- package/examples/saucedemo/.waygraph-auto/87c79256.log +0 -0
- package/examples/saucedemo/.waygraph-auto/88af2495.log +0 -0
- package/examples/saucedemo/.waygraph-auto/8c04a403.log +0 -0
- package/examples/saucedemo/.waygraph-auto/8c9ab09e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/8cc5db51.log +0 -0
- package/examples/saucedemo/.waygraph-auto/8ea3a69c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/9150f913.log +0 -0
- package/examples/saucedemo/.waygraph-auto/929a4313.log +0 -0
- package/examples/saucedemo/.waygraph-auto/95a599e4.log +0 -0
- package/examples/saucedemo/.waygraph-auto/96c45350.log +0 -0
- package/examples/saucedemo/.waygraph-auto/986030e6.log +0 -0
- package/examples/saucedemo/.waygraph-auto/99787abb.log +0 -0
- package/examples/saucedemo/.waygraph-auto/99ddb62c.log +0 -0
- package/examples/saucedemo/.waygraph-auto/a63a2d20.log +0 -0
- package/examples/saucedemo/.waygraph-auto/a78befb7.log +0 -0
- package/examples/saucedemo/.waygraph-auto/a84d9b79.log +0 -0
- package/examples/saucedemo/.waygraph-auto/a9270890.log +0 -0
- package/examples/saucedemo/.waygraph-auto/aa440153.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ab7561e0.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ac19ae60.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ae21ad8e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/af09042f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b1684126.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b21e9d12.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b5a838be.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b603549d.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b83e6259.log +0 -0
- package/examples/saucedemo/.waygraph-auto/b9cb2796.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bb0a0018.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bc0bdd0e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bc7d4a83.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bd8e082a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bea0c93e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bf35201a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/bffa3a7a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/c4afb8bc.log +0 -0
- package/examples/saucedemo/.waygraph-auto/c916a654.log +0 -0
- package/examples/saucedemo/.waygraph-auto/c9b8d69e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/d02e5e7e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/d09aff71.log +0 -0
- package/examples/saucedemo/.waygraph-auto/d0e41396.log +0 -0
- package/examples/saucedemo/.waygraph-auto/d1370d76.log +0 -0
- package/examples/saucedemo/.waygraph-auto/d3714ae4.log +0 -0
- package/examples/saucedemo/.waygraph-auto/da0685dc.log +0 -0
- package/examples/saucedemo/.waygraph-auto/da894ad8.log +0 -0
- package/examples/saucedemo/.waygraph-auto/daa2f922.log +0 -0
- package/examples/saucedemo/.waygraph-auto/daefb614.log +0 -0
- package/examples/saucedemo/.waygraph-auto/db97e833.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e101d19a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e207c989.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e633e545.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e69aec68.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e70e2d06.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ebad3410.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ecbb0966.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ecdee753.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ed888783.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ed931dac.log +0 -0
- package/examples/saucedemo/.waygraph-auto/ef655a3f.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f3c29b21.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f53f7cfc.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f5ad5830.log +0 -0
- package/examples/saucedemo/.waygraph-auto/faed36dd.log +0 -0
- package/examples/saucedemo/docs/CONVENTION.md +25 -5
- package/examples/saucedemo/docs/HELPERS.md +15 -4
- package/examples/saucedemo/scripts/capture-wrong-login.mjs +8 -2
- package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +8 -2
- package/examples/saucedemo/src/blocks/SITE-MAP.md +14 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +15 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.sel.ts +9 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +2 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +6 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/checkout-info.sel.ts +11 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-first-name.method.block.ts +22 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-last-name.method.block.ts +21 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.ts +21 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts +6 -7
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +2 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/checkout-overview.sel.ts +9 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +7 -6
- package/examples/saucedemo/src/blocks/saucedemo-web/ff-owner-auth.block.ts +7 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/NAV.md +1 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/item-detail.sel.ts +9 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +2 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +2 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +2 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +51 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +69 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/login.sel.ts +13 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +4 -4
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +13 -51
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts +4 -3
- package/examples/saucedemo/src/blocks/saucedemo-web/nav-login.block.ts +14 -6
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +15 -0
- package/examples/saucedemo/src/flows/checkout.flow.ts +20 -2
- package/examples/saucedemo/src/flows/device-showcase.flow.ts +4 -0
- package/examples/saucedemo/src/flows/login.flow.ts +3 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +6 -0
- package/examples/saucedemo/src/flows/smoke.flow.ts +13 -7
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +4 -2
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +6 -2
- package/examples/saucedemo/tests/flow-block-verify.spec.ts +3 -3
- package/examples/saucedemo/tests/submit-login-branch.spec.ts +5 -3
- package/package.json +2 -1
- package/templates/agents/waygraph-author.agent.md +43 -5
- package/templates/agents/waygraph-planner.agent.md +33 -2
- package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +15 -1
- package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -0
- package/templates/quickstart/src/blocks/saucedemo-web/cart/cart.sel.ts +9 -0
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +19 -15
- package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +2 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +6 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/checkout-info.sel.ts +11 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-first-name.method.block.ts +22 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-last-name.method.block.ts +21 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.ts +21 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts +6 -7
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +2 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/checkout-overview.sel.ts +9 -0
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +7 -6
- package/templates/quickstart/src/blocks/saucedemo-web/ff-owner-auth.block.ts +7 -2
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +24 -16
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +3 -3
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/NAV.md +1 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/item-detail.sel.ts +9 -0
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +2 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +2 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +2 -1
- package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +51 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +69 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/login.sel.ts +13 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +4 -4
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +35 -63
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts +4 -3
- package/templates/quickstart/src/blocks/saucedemo-web/nav-login.block.ts +14 -6
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +15 -0
- package/templates/quickstart/src/flows/checkout.flow.ts +29 -2
- package/templates/quickstart/src/flows/device-showcase.flow.ts +4 -0
- package/templates/quickstart/src/flows/login.flow.ts +3 -1
- package/templates/quickstart/src/flows/shop.flow.ts +6 -0
- package/templates/quickstart/src/flows/smoke.flow.ts +13 -7
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +6 -2
- package/templates/quickstart/tests/chain-flow.spec.ts +8 -1
- package/templates/scaffold/STRUCTURE.md +23 -3
- package/templates/scaffold/src/blocks/demo-external/mailpit/mailpit-inbox.page.block.ts +30 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/mailpit-sel.ts +5 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/methods/assert-email-content.method.block.ts +48 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/methods/assert-email-received.method.block.ts +49 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/methods/extract-email-link.method.block.ts +41 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/methods/open-message.method.block.ts +39 -0
- package/templates/scaffold/src/blocks/demo-external/mailpit/nav-mailpit-inbox.block.ts +20 -0
- package/templates/scaffold/src/blocks/demo-web/demo-sel.ts +1 -0
- package/templates/scaffold/src/blocks/demo-web/docs.html +8 -0
- package/templates/scaffold/src/blocks/demo-web/home.html +4 -0
- package/templates/scaffold/src/blocks/demo-web/methods/assert-hello.method.block.ts +34 -38
- package/templates/scaffold/src/blocks/demo-web/methods/assert-item-added.method.block.ts +38 -0
- package/templates/scaffold/src/blocks/demo-web/nav-verification-link.block.ts +34 -0
- package/templates/scaffold/src/flows/mail-verify.flow.ts +85 -0
- package/templates/scaffold/src/flows/routes-demo.flow.ts +14 -0
- package/templates/scaffold/src/flows/shop.flow.ts +19 -2
- package/templates/scaffold/src/routes/(external)/docs/docs.sel.ts +7 -0
- package/templates/scaffold/src/routes/(external)/docs/nav.block.ts +21 -0
- package/templates/scaffold/src/routes/(external)/docs/page.block.ts +15 -0
- package/templates/scaffold/src/states/demo.mem-keys.ts +25 -1
- package/templates/scaffold/src/states/demo.states.ts +13 -0
- package/templates/scaffold/test-results/.last-run.json +4 -0
- package/templates/scaffold/tests/mail-verify.spec.ts +191 -0
- package/templates/skills/waygraph-blind-pilot.skill.md +61 -0
- package/templates/skills/waygraph-pilot.skill.md +108 -0
- package/templates/quickstart/README-saucedemo-commands.md +0 -72
package/README.md
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
A typed graph of reusable Blocks for driving a browser through E2E flows, in place of a
|
|
4
4
|
flat pile of ad hoc helper functions.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Not yet `1.0.0` - breaking changes between minor versions are expected. See
|
|
7
|
+
[ROADMAP.md](./ROADMAP.md) for what's shipped, spec'd, and planned, and
|
|
8
|
+
`openspec/specs/` for the current merged spec behind each capability
|
|
9
|
+
(`openspec/changes/archive/` holds the historical planning artifacts once a change ships).
|
|
7
10
|
|
|
8
11
|
This repo is the `waygraph` npm package itself. The full live **Sauce Demo** example
|
|
9
12
|
(Page inventory hub, Effect Add/Remove, MemNav Open details, `waygraph auto`) ships
|
|
@@ -11,48 +14,325 @@ in-tree at [`examples/saucedemo`](./examples/saucedemo). `waygraph try demo` cop
|
|
|
11
14
|
[`templates/quickstart`](./templates/quickstart) (same Sauce Demo Blocks) into an OS
|
|
12
15
|
temp dir.
|
|
13
16
|
|
|
14
|
-
See [ROADMAP.md](./ROADMAP.md) for what's shipped, spec'd, and planned by version, and
|
|
15
|
-
`openspec/changes/` for the actual planning artifacts behind each roadmap item.
|
|
16
|
-
|
|
17
17
|
## Docs (GitHub Pages)
|
|
18
18
|
|
|
19
|
-
Browser docs (quick start, **demo / run**, **auto explore**, engine handout, consumer
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
Browser docs (quick start, **demo / run**, **auto explore**, engine handout, consumer
|
|
20
|
+
layout, deploy):
|
|
21
|
+
|
|
22
|
+
- **Published:** https://deviate-dv8.github.io/waygraph/ (workflow auto-enables Pages via
|
|
23
|
+
`enablement: true`; manual Settings only if org policy blocks it)
|
|
24
|
+
- **Source:** [`docs/`](./docs/) - static HTML, no build step
|
|
25
|
+
- **Local preview:** `npm run docs:preview` -> http://127.0.0.1:4173/
|
|
26
|
+
- **Deploy:** push to `main` touching `docs/**` runs
|
|
27
|
+
[`.github/workflows/pages.yml`](./.github/workflows/pages.yml) (`workflow_dispatch` also
|
|
28
|
+
works)
|
|
29
|
+
- **Demo / run contract:** [`docs/demo.html`](./docs/demo.html) - `list` / `.flow.ts` path
|
|
30
|
+
/ export / flags
|
|
31
|
+
- **Auto explore:** [`docs/auto.html`](./docs/auto.html) - prefer `auto --cli` /
|
|
32
|
+
`npm run auto:cli` (same menus as headed)
|
|
33
|
+
- **In-repo example:** [`examples/saucedemo`](./examples/saucedemo) - full Sauce Demo
|
|
34
|
+
project
|
|
35
|
+
- **Convention showcase (Pages):** [`docs/saucedemo/`](./docs/saucedemo/) -
|
|
29
36
|
https://deviate-dv8.github.io/waygraph/saucedemo/
|
|
30
37
|
- **Helpers (consumer copy):** [`examples/saucedemo/docs/HELPERS.md`](./examples/saucedemo/docs/HELPERS.md)
|
|
31
38
|
|
|
32
|
-
This README stays the in-repo API narrative; Pages is the same material for eyeballing and
|
|
39
|
+
This README stays the in-repo API narrative; Pages is the same material for eyeballing and
|
|
40
|
+
cross-mesh handoff.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install waygraph @playwright/test
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`@playwright/test` is a peer dependency, not bundled - you bring your own version
|
|
49
|
+
(`^1.40.0` or later) since it's also what drives the rest of your test suite. Requires
|
|
50
|
+
Node 22+ (uses `URL`/`URLPattern` from `@types/node` for `Trait.url`'s type checking).
|
|
51
|
+
|
|
52
|
+
## Core concepts
|
|
53
|
+
|
|
54
|
+
- **`Checkpoint<Tag>`** - a state identified solely by its string tag. Carries no data.
|
|
55
|
+
- **`MemPage`** / **`MemKey<T>`** - typed shared memory across a run, keyed by object
|
|
56
|
+
identity (`key<T>("debug-name")`), not by name string.
|
|
57
|
+
- **`Block<In, Out>`** - one step: `act(page, input, mem)` does the work,
|
|
58
|
+
`resolve()` decides the resulting Checkpoint (never touches the page or mem),
|
|
59
|
+
`verify` (optional) confirms the page actually matches what `resolve()` claimed.
|
|
60
|
+
- **`Trait`** - a named, independently-reportable check (`{ name, check(page, mem) }`).
|
|
61
|
+
Built-in factories: `Trait.url` / `Trait.text` / `Trait.visible` (also importable as
|
|
62
|
+
free functions `urlMatches` / `textEquals` / `visible`).
|
|
63
|
+
- **`Flow`** - several Blocks chained with `Engine().defineFlow([start, ...blocks, end])`,
|
|
64
|
+
run with `flow.run(mem)` (Engine launches its own browser) or `flow.run(context, mem)`
|
|
65
|
+
(reuse an existing `@playwright/test` `context` fixture).
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Engine, start, end, MemPage, key, checkpoint, Trait } from "waygraph";
|
|
71
|
+
import type { Block, Checkpoint, Start } from "waygraph";
|
|
72
|
+
|
|
73
|
+
type LoggedIn = Checkpoint<"LoggedIn">;
|
|
74
|
+
|
|
75
|
+
const Username = key<string>("username");
|
|
76
|
+
|
|
77
|
+
const Login: Block<Start, LoggedIn> = {
|
|
78
|
+
name: "login",
|
|
79
|
+
instruction: {
|
|
80
|
+
async act(page, _input, mem) {
|
|
81
|
+
await page.goto("https://example.com/login");
|
|
82
|
+
await page.getByLabel("Username").fill(mem.get(Username));
|
|
83
|
+
await page.getByRole("button", { name: "Sign in" }).click();
|
|
84
|
+
},
|
|
85
|
+
resolve: () => checkpoint("LoggedIn"),
|
|
86
|
+
verify: [Trait.url({ pathname: "/dashboard" })],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const mem = new MemPage();
|
|
91
|
+
mem.set(Username, "alice");
|
|
92
|
+
|
|
93
|
+
const flow = new Engine().defineFlow([start, Login, end]);
|
|
94
|
+
const result = await flow.run(mem); // Engine launches its own browser
|
|
95
|
+
// result === { __state: "LoggedIn" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Inside an existing `@playwright/test` file, reuse its `context` fixture instead of having
|
|
99
|
+
the Engine launch its own browser: `await flow.run(context, mem)`.
|
|
100
|
+
|
|
101
|
+
See `tests/define-flow.spec.ts` here, and
|
|
102
|
+
`examples/saucedemo/tests/checkout-flow.spec.ts` (a real multi-Block flow against live
|
|
103
|
+
saucedemo.com), for this running for real. The lower-level `connect()`/`runGraph()` still
|
|
104
|
+
exist and are what `defineFlow` builds on - reach for them directly only if you need a
|
|
105
|
+
shape `defineFlow`'s array can't express yet.
|
|
106
|
+
|
|
107
|
+
## Engine features
|
|
108
|
+
|
|
109
|
+
A Block's output type is the only contract the next Block can rely on. `resolve` is
|
|
110
|
+
structurally incapable of touching the browser or shared memory. `verify` runs only after
|
|
111
|
+
`resolve` has already decided (confirms or fails loud, never redirects).
|
|
112
|
+
|
|
113
|
+
### Routing and composition
|
|
114
|
+
|
|
115
|
+
- **`branch()`** attaches routing to a Block - including back to itself (self-loops) -
|
|
116
|
+
without ever touching that Block's own phases. A `maxSteps` safety cap keeps a routing
|
|
117
|
+
bug from hanging a run forever.
|
|
118
|
+
- **`withVerify(block, verify)`** decorates any existing Block with different
|
|
119
|
+
confirmation, same `act`/`resolve`, same routing - without that Block needing to have
|
|
120
|
+
been authored as a parameterized factory (`withVerify(LoginBlock, [])` is the "I only
|
|
121
|
+
care that I navigated this far, no DOM check" case).
|
|
122
|
+
- **`flow.withBlockVerify` / `flow.modBlockVerify`** patch one Block's verify from
|
|
123
|
+
outside a finished `Flow` - addressed by the Block reference (preferred), its name, or
|
|
124
|
+
its numeric position - without editing the flow's own file.
|
|
125
|
+
- **`composeBlock(name, steps)`** chains several Blocks into one named unit, the same way
|
|
126
|
+
`connect()` already does - useful for a multi-step form that's naturally "one feature"
|
|
127
|
+
but still wants each step's own real `verify` independently overridable, not one giant
|
|
128
|
+
Block with no per-step confirmation. `composedBlock.withStepVerify` / `modStepVerify`
|
|
129
|
+
patch one step from outside, addressed the same three ways as `withBlockVerify`. A
|
|
130
|
+
`composeBlock` result is a plain Block, so it drops straight into
|
|
131
|
+
`defineFlow([start, ..., composed, ..., end])` like any other.
|
|
132
|
+
- **`fastForwardComposeBlock(name, steps)`** is the same shape marked for wall-clock
|
|
133
|
+
**fast-forward**: one opaque demo step that blitzes (no per-inner gate, no smooth cursor
|
|
134
|
+
theater, Playwright `slowMo` off for that flow unless `WAYGRAPH_SLOWMO` is set) unless
|
|
135
|
+
expanded. Put several FF units in one Flow to race past auth, then a heavy dashboard
|
|
136
|
+
settle, then watch the interesting middle. `waygraph demo --ff-disabled` (alias
|
|
137
|
+
`--no-ff`) expands every FFCompose into its inner Blocks so a broken step is visible as
|
|
138
|
+
its own panel row; former FF inners keep blitz pacing so wall-clock stays comparable.
|
|
139
|
+
`--ff-expand` is the older expand-only flag (same flatten).
|
|
140
|
+
|
|
141
|
+
### Traits beyond the page
|
|
142
|
+
|
|
143
|
+
A hand-written `Trait`'s `check(page, mem)` receives `mem` too, for the same-Checkpoint
|
|
144
|
+
case: an action that doesn't navigate at all (clicking "+" to bump a cart item's quantity,
|
|
145
|
+
say) has `In === Out`, which `defineBlock`/`defineFlow` already accept with no
|
|
146
|
+
special-casing - `act()` updates `mem` alongside the real click, and a bespoke `verify`
|
|
147
|
+
Trait confirms the page's own displayed value actually matches what `mem` now expects, not
|
|
148
|
+
just that *some* number is showing. `resolve()` itself stays exactly as pure as ever - it
|
|
149
|
+
never sees `mem`, only `verify` does.
|
|
150
|
+
|
|
151
|
+
`Trait.url` takes a structured `URLPatternInit` (`{ pathname, hostname, search, hash, ... }`,
|
|
152
|
+
the same shape the standard `URLPattern` Web API takes), not a hand-written regex -
|
|
153
|
+
whichever components you leave out default to "match anything," so
|
|
154
|
+
`Trait.url({ pathname: "/inventory.html" })` already ignores whatever query params a
|
|
155
|
+
hybrid SPA tacks on, with no separate flag needed for that.
|
|
156
|
+
|
|
157
|
+
### `precondition` - confirming the page is *still* there
|
|
158
|
+
|
|
159
|
+
`verify` only ever confirms what a Block itself just did - nothing previously checked
|
|
160
|
+
whether the page was still there by the time the next Block's `act()` starts. A
|
|
161
|
+
`precondition` (same `Trait[]` / `(input) => Trait[]` shape as `verify`, checked
|
|
162
|
+
automatically by `connect()`/`runGraph` right before `act()` runs, never called directly)
|
|
163
|
+
closes that gap: a session timeout, a redirect, an interstitial popup - anything that
|
|
164
|
+
changes the page in the moment between one Block finishing and the next one starting -
|
|
165
|
+
fails loud with a clear "trait X failed before Y," instead of a confusing error deep
|
|
166
|
+
inside `act()` trying to click something that's no longer there.
|
|
167
|
+
|
|
168
|
+
### `requires` and `preflight()`
|
|
169
|
+
|
|
170
|
+
A Block's `requires: MemKey[]` gets checked by `preflight()` (built into `runGraph`)
|
|
171
|
+
before a tab even opens - a missing credential fails in milliseconds, not after several
|
|
172
|
+
real browser actions already ran.
|
|
173
|
+
|
|
174
|
+
### Engine config and pluggable browsers
|
|
175
|
+
|
|
176
|
+
`new Engine({ headless, browserName, slowMo })` configures a browser the Engine launches
|
|
177
|
+
and owns itself: `flow.run(mem)` (no context) uses it, and
|
|
178
|
+
`flow.run(mem, { ...overrides })` overrides it per call - so one module-level `Flow`,
|
|
179
|
+
imported by many specs, doesn't force every caller into the same settings.
|
|
180
|
+
`flow.run(context, mem)` still works exactly as before and ignores all of this, since that
|
|
181
|
+
context is already launched.
|
|
182
|
+
|
|
183
|
+
`new Engine({ browsers: { chromium, firefox, webkit } })` overrides which `BrowserType`
|
|
184
|
+
actually launches for a `mem`-only run, per browser name - waygraph is deliberately "just
|
|
185
|
+
an opinionated Playwright," so a stealth-patched or otherwise customized launcher (e.g.
|
|
186
|
+
`playwright-extra` plus a stealth plugin) drops in unmodified, since those already expose
|
|
187
|
+
the same `.launch()` shape as Playwright's own `chromium`/`firefox`/`webkit`.
|
|
188
|
+
|
|
189
|
+
### Driving an existing page / tabs
|
|
190
|
+
|
|
191
|
+
`spawnTab(entry, page, mem)` drives a genuinely separate second tab through its own
|
|
192
|
+
Block/Flow, in the same browser context an existing `page` already belongs to - the
|
|
193
|
+
pattern `observe()` could already reach for (it's the only phase allowed to touch
|
|
194
|
+
`page.context()`), now a named, discoverable, tested primitive.
|
|
195
|
+
|
|
196
|
+
`flow.run(context, mem, options)` also accepts a trailing optional options object -
|
|
197
|
+
additively, so `flow.run(context, mem)` still behaves byte-for-byte as before:
|
|
198
|
+
`options.page` drives that already-open page instead of opening a fresh tab (the run
|
|
199
|
+
won't close a page you handed it unless you say so), and `options.closeOnFinish: false`
|
|
200
|
+
leaves the run's page open and returns `{ result, page }` so the caller can keep driving
|
|
201
|
+
it. See [Recipes](#recipes) for the surviving-tab and popup-capture patterns.
|
|
202
|
+
|
|
203
|
+
### NavBlock / ActionPage / waygraph check
|
|
204
|
+
|
|
205
|
+
`defineNavBlock({ name, checkpoint, url })` or
|
|
206
|
+
`defineNavClickBlock({ name, checkpoint, click })` builds a Block whose only possible
|
|
207
|
+
action is navigating. Prefer `defineNavClickBlock` for click-nav in app code;
|
|
208
|
+
`defineNavBlock` for `url` / `goto` deep links. Both accept a plain string or
|
|
209
|
+
`(mem) => string` / `(mem) => selector`. Generated `act()` is always exactly
|
|
210
|
+
`page.goto(url)` or `page.locator(click).click()`. For screen hubs that also register
|
|
211
|
+
methods, prefer `definePageBlock` (see [Block helpers](#block-helpers-page-method-sel)
|
|
212
|
+
below).
|
|
213
|
+
|
|
214
|
+
A regular `defineBlock` / `defineMethodBlock`'s `act()` receives `page` typed as
|
|
215
|
+
`ActionPage` - `goto`/`reload`/`goBack`/`goForward` are `@deprecated` there (editor
|
|
216
|
+
strike-through pointing at Nav/Page helpers). This is a soft, TypeScript-only signal:
|
|
217
|
+
nothing is blocked, nothing fails to build. `waygraph check [project]` is the
|
|
218
|
+
complementary whole-project sweep for contexts with no editor watching (CI, generated
|
|
219
|
+
code, an autonomous agent authoring Blocks) - it warns, never fails the process.
|
|
220
|
+
|
|
221
|
+
### Demo narration
|
|
33
222
|
|
|
34
|
-
|
|
223
|
+
Automation rings are gray; authored stubs/slides can use
|
|
224
|
+
`tone: "info" | "warning" | "danger" | "success"` (iconified captions), plus
|
|
225
|
+
`size: "sm" | "md" | "lg"` and `weight: "normal" | "bold"`. Flow episode defaults:
|
|
226
|
+
`withHighlightStyle(flow, { size, weight, tone? })` (slot/fixture wins). Episode pacing:
|
|
227
|
+
`withDemoPace(flow, "fast" | "slow" | "normal" | "blitz" | number)` or
|
|
228
|
+
`withBlockPace(block, ...)` (`number <= 20` = scale vs normal, e.g. `0.5` / `2`; `> 20` =
|
|
229
|
+
absolute ms, e.g. `4500`). FFCompose stays blitz. Living objectives:
|
|
230
|
+
`docs/proposals/OBJECTIVES.md`.
|
|
231
|
+
|
|
232
|
+
### Graph traversal
|
|
233
|
+
|
|
234
|
+
`waygraph traverse` walks the Block graph. `--parallel N` (default `--session clone`)
|
|
235
|
+
bootstraps one context, forks N workers with `storageState` + mem snapshot, partitions
|
|
236
|
+
edges by hash, and claims via in-process edge leases (audit trail under
|
|
237
|
+
`.waygraph-traverse/`). `--session inherit` is refused when `parallel > 1`. It writes
|
|
238
|
+
`.waygraph-traverse/coverage.json` and prints `[Coverage edges=H/T ratio=R%...]`;
|
|
239
|
+
`--min-edge-coverage 80%` fails the suite with exit 2 when the ratio is below the gate
|
|
240
|
+
(even if every worker leaf-PASSed). `--coverage-out PATH` / `--no-coverage-report` adjust
|
|
241
|
+
the JSON. See `docs/proposals/traverse-ffcompose-rfc.md`.
|
|
242
|
+
|
|
243
|
+
### Not yet implemented
|
|
244
|
+
|
|
245
|
+
Tracked on the project board / roadmap, not in this package today:
|
|
246
|
+
|
|
247
|
+
- Interstitials/Watchers - background overlay handling (cookie banners, popups).
|
|
248
|
+
- Split `requires` (externally-supplied vs producedBy) recovery hints.
|
|
249
|
+
- Federated multi-package pool of waygraphs.
|
|
250
|
+
|
|
251
|
+
## Block helpers (Page, Method, Sel)
|
|
35
252
|
|
|
36
253
|
Runtime is still one type: `Block`. Helpers are TypeScript salt.
|
|
37
254
|
|
|
38
255
|
| Helper | Role | Typical file |
|
|
39
256
|
|--------|------|----------------|
|
|
40
257
|
| `definePageBlock` | Screen hub (checkpoint + `verify` + registered methods) | `*.page.block.ts` |
|
|
41
|
-
| `defineMethodBlock` | One-shot non-nav step (submit, upload, logout) | `methods/*.method.block.ts` |
|
|
258
|
+
| `defineMethodBlock` | One-shot non-nav step, exactly one action (submit, upload, logout) | `methods/*.method.block.ts` |
|
|
259
|
+
| `defineAssertBlock` | Self-loop-only assertion (no state change) - `verify` only | `methods/*.method.block.ts` |
|
|
42
260
|
| `defineEffectBlock` | Instance mutate + `instanceOptions` auto menu | `methods/*.effect.block.ts` |
|
|
43
261
|
| `defineNavBlock` / `defineNavClickBlock` | `goto` / click-nav | `nav-*.block.ts` |
|
|
44
262
|
| `defineMemNavBlock` | Nav + per-row `instanceOptions` | `nav-*.block.ts` |
|
|
45
|
-
| `defineActionBlock` |
|
|
263
|
+
| `defineActionBlock` | Deprecated alias of `defineMethodBlock` | - |
|
|
46
264
|
|
|
47
265
|
**Page hub:** one Checkpoint = this screen. Methods hang off the page for readability /
|
|
48
266
|
auto grouping. Arrival-only hubs omit `url`/`click` (previous Block already landed here).
|
|
49
267
|
Deep-link hubs pass `url` or `click` like a Nav.
|
|
50
268
|
|
|
51
|
-
|
|
269
|
+
**One distinct action per Block - hard rule.** A Method that fills form fields *and*
|
|
270
|
+
submits, or that drives several steps inside one `act()`, is wrong even if it "works" -
|
|
271
|
+
split it into atomic Blocks (a login form is `fill-username` + `fill-password` +
|
|
272
|
+
`submit-login`, never one Block doing all three; see `examples/saucedemo`'s own
|
|
273
|
+
`saucedemo-web/methods/` for the real split). A Block that only asserts something on the
|
|
274
|
+
current page, with no state change, is `defineAssertBlock({ name, checkpoint, verify,
|
|
275
|
+
waitForHeading?, requires? })` - self-loop and `resolve` are generated for you, so there's no
|
|
276
|
+
hand-written `act`/`resolve` to accidentally make do two things. It also accepts
|
|
277
|
+
`stubBefore`/`stubAfter`/`stubOnError`/`slides`, the same narration fields every other
|
|
278
|
+
Block helper takes, so converting a hand-written assertion Method loses no demo/highlight
|
|
279
|
+
fixture data. `requires` (declare it whenever a mem-aware Trait reads a mem key the caller
|
|
280
|
+
must supply externally - see the mail example below) lets preflight catch a missing input
|
|
281
|
+
before the flow ever runs, the same as every other Block helper:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
export const AssertInventoryHeaderBlock = defineAssertBlock({
|
|
285
|
+
name: "assert-inventory-header",
|
|
286
|
+
checkpoint: "LoggedIn",
|
|
287
|
+
waitForHeading: "Products",
|
|
288
|
+
verify: [Trait.visible(InventorySel.list)],
|
|
289
|
+
});
|
|
290
|
+
```
|
|
52
291
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
292
|
+
`waygraph check` warns when a `verify` array inlines a literal selector string
|
|
293
|
+
(`Trait.visible("#some-id")`) instead of referencing a `*Sel` object - the exact pattern
|
|
294
|
+
`defineAssertBlock` exists to make easy to avoid.
|
|
295
|
+
|
|
296
|
+
**Give `defineAssertBlock` an explicit type argument when it sits mid-chain.** Without one,
|
|
297
|
+
`Out` defaults to wildcard `Checkpoint<string>` on both sides - harmless when the assert is
|
|
298
|
+
the last real Block before `end` (nothing downstream needs it narrower), but a `defineFlow`
|
|
299
|
+
array's tuple typing breaks once a wildcard Block is sandwiched between two
|
|
300
|
+
specifically-typed ones. Write `defineAssertBlock<LoggedIn>({ ..., checkpoint: "LoggedIn",
|
|
301
|
+
... })` any time the assert has a real Block after it, not just before.
|
|
302
|
+
|
|
303
|
+
**An assert Block can check the *result* of an action, not just static page state.** Since
|
|
304
|
+
`Trait.check(page, mem)` receives mem as well as the page, an assert placed right after a
|
|
305
|
+
Method/Effect can confirm what that specific action actually did - not a duplicate of that
|
|
306
|
+
Block's own inline `verify`, a genuinely separate, nameable QA checkpoint:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const removeButtonVisibleForSelectedItem: Trait = {
|
|
310
|
+
name: "remove-button-visible-for-selected-item",
|
|
311
|
+
async check(page, mem) {
|
|
312
|
+
const { id } = mem.get(SelectedItem.key); // what add-item just acted on
|
|
313
|
+
return page.locator(RemoveBtnSel(id)).isVisible();
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export const AssertItemAddedBlock = defineAssertBlock<ItemInCart>({
|
|
318
|
+
name: "assert-item-added",
|
|
319
|
+
checkpoint: "ItemInCart",
|
|
320
|
+
verify: [removeButtonVisibleForSelectedItem, Trait.text(CartCountSel, "1")],
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// wired right after the action it checks:
|
|
324
|
+
engine.defineFlow([start, NavHomeBlock, AddItemBlock, AssertItemAddedBlock, ClearCartBlock, end])
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Live reference: `templates/scaffold/src/blocks/demo-web/methods/assert-item-added.method.block.ts`,
|
|
328
|
+
wired into `shop.flow.ts`.
|
|
329
|
+
|
|
330
|
+
**`methods/` folder:** on-page work lives next to the route, not free-floating.
|
|
331
|
+
|
|
332
|
+
**`*Sel`:** DOM selectors only (static strings + `(id) => ...` for item-no-1 style). Mem
|
|
333
|
+
keys store values (which item / upload queue), not selectors. `instanceOptions` scrapes the
|
|
334
|
+
live DOM into menu rows and `mem.set`s on pick; predefined methods seed mem from the flow
|
|
335
|
+
instead.
|
|
56
336
|
|
|
57
337
|
```typescript
|
|
58
338
|
import { definePageBlock, Trait } from "waygraph";
|
|
@@ -80,240 +360,539 @@ export const InventoryPage = definePageBlock({
|
|
|
80
360
|
|
|
81
361
|
Live reference: `examples/saucedemo/src/blocks/saucedemo-web/inventory/`
|
|
82
362
|
(`inventory.page.block.ts` + `methods/` + `InventorySel`). Bulk `add-all-to-cart` /
|
|
83
|
-
`remove-all-from-cart` are one menu row each (`from: "*"`
|
|
363
|
+
`remove-all-from-cart` are one menu row each (`from: "*"` - works after leave/return).
|
|
84
364
|
|
|
365
|
+
## Mail adapters (cross-origin, browser-driven)
|
|
85
366
|
|
|
86
|
-
|
|
367
|
+
For flows driven by an email (signup confirmation, password reset, a magic link), the
|
|
368
|
+
convention is **not** a REST/HTTP client to the mail catcher - it's more Blocks, pointed at
|
|
369
|
+
the catcher's own web UI (MailHog, MailDev, Mailpit all ship one) as a real, separate,
|
|
370
|
+
cross-origin page. This needs zero new engine surface: a `NavBlock` navigates there like any
|
|
371
|
+
other page, `MethodBlock`s read the DOM with ordinary `page.locator`/`page.frameLocator`
|
|
372
|
+
calls, and a final `NavBlock` carries the extracted link back into the app under test.
|
|
373
|
+
Convergent, real-world evidence for this exact shape: independently, more than one real
|
|
374
|
+
consumer project settled on it rather than a REST client.
|
|
87
375
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| Watch Sauce Demo step-through (temp dir only) | `npx waygraph try demo` |
|
|
91
|
-
| Interactive explore (Add/Remove/Open details from live page) | `cd examples/saucedemo && npm i && npx waygraph auto` |
|
|
92
|
-
| Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx create-waygraph my-app` **or** `npx waygraph init my-app` |
|
|
93
|
-
| Install coding-agent defs for diving an app into Blocks | `npx waygraph agent-dive --loop claude` (also `opencode` / `cursor` / `vscode`) |
|
|
94
|
-
| Add waygraph to an existing repo | `npm install waygraph @playwright/test` |
|
|
376
|
+
**Keep it in its own `*-external/<tool>/` folder, never mixed into your app's own Blocks** -
|
|
377
|
+
it's a different origin, driving a different site:
|
|
95
378
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
379
|
+
```typescript
|
|
380
|
+
// demo-external/mailpit/nav-mailpit-inbox.block.ts
|
|
381
|
+
export const NavMailpitInboxBlock = defineNavBlock<MailpitInbox>({
|
|
382
|
+
name: "nav-mailpit-inbox",
|
|
383
|
+
checkpoint: "MailpitInbox",
|
|
384
|
+
url: () => process.env.WAYGRAPH_MAIL_URL ?? "http://127.0.0.1:8025",
|
|
385
|
+
verify: [Trait.url({ pathname: "/" })],
|
|
386
|
+
});
|
|
101
387
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
388
|
+
// demo-external/mailpit/methods/open-message.method.block.ts
|
|
389
|
+
export const OpenMessageBlock = defineMethodBlock<MailpitInbox, MailpitMessageOpen>({
|
|
390
|
+
name: "open-message",
|
|
391
|
+
requires: [ExpectedRecipient.key], // externally supplied - avoids picking the wrong inbox row
|
|
392
|
+
instruction: {
|
|
393
|
+
async act(page, _in, mem) {
|
|
394
|
+
const { email } = mem.get(ExpectedRecipient.key);
|
|
395
|
+
await page.locator(MailpitSel.messageRow, { hasText: email }).first().click();
|
|
396
|
+
await page.waitForURL(/\/view\//);
|
|
397
|
+
},
|
|
398
|
+
resolve: () => checkpoint("MailpitMessageOpen"),
|
|
399
|
+
},
|
|
400
|
+
});
|
|
105
401
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
402
|
+
// demo-external/mailpit/methods/extract-email-link.method.block.ts
|
|
403
|
+
// Named for what it does, not for one scenario - see "reusable across
|
|
404
|
+
// scenarios" below for why.
|
|
405
|
+
export const ExtractEmailLinkBlock = defineMethodBlock<MailpitMessageOpen, MailpitMessageOpen>({
|
|
406
|
+
name: "extract-email-link",
|
|
407
|
+
requires: [ExpectedLinkPattern], // which link in the body to follow - externally supplied
|
|
408
|
+
instruction: {
|
|
409
|
+
async act(page, _in, mem) {
|
|
410
|
+
const pattern = mem.get(ExpectedLinkPattern);
|
|
411
|
+
const link = page.frameLocator(MailpitSel.previewIframe).locator(`a[href*="${pattern}"]`).first();
|
|
412
|
+
const href = await link.getAttribute("href");
|
|
413
|
+
if (!href) throw new Error(`no link matching "${pattern}" found`);
|
|
414
|
+
mem.set(EmailLink({ url: href }));
|
|
415
|
+
},
|
|
416
|
+
resolve: () => checkpoint("MailpitMessageOpen"),
|
|
417
|
+
},
|
|
418
|
+
});
|
|
110
419
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
420
|
+
// demo-web/nav-verification-link.block.ts - back into the app, no `requires` (see below)
|
|
421
|
+
export const NavVerificationLinkBlock = defineNavBlock<HomeVerified>({
|
|
422
|
+
name: "nav-verification-link",
|
|
423
|
+
checkpoint: "HomeVerified",
|
|
424
|
+
url: (mem) => mem.get(EmailLink.key).url,
|
|
425
|
+
verify: [Trait.visible(DemoSel.verifiedBanner)],
|
|
426
|
+
});
|
|
118
427
|
```
|
|
119
428
|
|
|
429
|
+
Four atomic Blocks, each doing exactly one thing - `open-message` and `extract-email-link`
|
|
430
|
+
stay separate Methods for the same reason `fill-username`/`submit-login` do in the Sauce
|
|
431
|
+
Demo example: reading the link and navigating to it are two distinct actions, so they are
|
|
432
|
+
two distinct Blocks (`nav-verification-link` does the actual navigation).
|
|
120
433
|
|
|
121
|
-
|
|
434
|
+
Note `nav-verification-link` deliberately does **not** declare `requires: [EmailLink.key]` -
|
|
435
|
+
`requires` means "the caller must seed this externally before the flow starts" (preflight
|
|
436
|
+
checks a whole chain's requires up front), and this key is instead produced by
|
|
437
|
+
`extract-email-link` earlier in the very same chain. Declaring it as a requirement would make
|
|
438
|
+
preflight reject the run before it ever gets a chance to produce it - `requires` is only for
|
|
439
|
+
keys a caller truly must supply from outside the chain (like `ExpectedRecipient` above).
|
|
122
440
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
441
|
+
Live reference, proven end to end against a real, throwaway Mailpit container:
|
|
442
|
+
`templates/scaffold/src/blocks/demo-external/mailpit/` + `src/flows/mail-verify.flow.ts` +
|
|
443
|
+
`tests/mail-verify.spec.ts`.
|
|
126
444
|
|
|
127
|
-
|
|
128
|
-
or later) since it's also what drives the rest of your test suite. Requires Node 22+ (uses
|
|
129
|
-
`URL`/`URLPattern` from `@types/node` for `Trait.url`'s type checking).
|
|
445
|
+
### Reusable across scenarios: mem-driven, not one Block per email
|
|
130
446
|
|
|
131
|
-
|
|
447
|
+
The natural instinct once you have a signup-verification flow is to want a matching set of
|
|
448
|
+
Blocks for password reset, then another for a magic link, and so on - N scenarios, N Block
|
|
449
|
+
sets. That's the wrong axis to scale on. `open-message`, `extract-email-link`, and the two
|
|
450
|
+
assert Blocks below are already scenario-agnostic: **only mem varies per run, never the
|
|
451
|
+
Blocks**:
|
|
132
452
|
|
|
133
|
-
|
|
134
|
-
|
|
453
|
+
| Mem key | Answers |
|
|
454
|
+
|---|---|
|
|
455
|
+
| `ExpectedRecipient` | Which inbox row to open |
|
|
456
|
+
| `ExpectedLinkPattern` | Which link in the body to follow (a real email often has more than one - "report abuse" and the real link both present) |
|
|
457
|
+
| `ExpectedEmailContent` | What the body is supposed to say |
|
|
458
|
+
|
|
459
|
+
The same four Blocks (`assert-email-received`, `open-message`, `assert-email-content`,
|
|
460
|
+
`extract-email-link`) run a signup-verification check and a password-reset check identically
|
|
461
|
+
- just with different mem, in `templates/scaffold/tests/mail-verify.spec.ts`:
|
|
135
462
|
|
|
136
463
|
```typescript
|
|
137
|
-
|
|
138
|
-
|
|
464
|
+
mem.set(ExpectedRecipient({ email: "demo-user@example.com" }));
|
|
465
|
+
mem.set(ExpectedLinkPattern, "verified=1");
|
|
466
|
+
mem.set(ExpectedEmailContent, "Please confirm your email address...");
|
|
467
|
+
await mailVerifyFlow.run(context, mem); // signup verification
|
|
468
|
+
|
|
469
|
+
mem.set(ExpectedRecipient({ email: "password-reset-user@example.com" }));
|
|
470
|
+
mem.set(ExpectedLinkPattern, "verified=1"); // picks the real link, not the "report abuse" decoy also in the body
|
|
471
|
+
mem.set(ExpectedEmailContent, "We received a request to reset your password.");
|
|
472
|
+
await mailVerifyFlow.run(context, mem); // same flow, same Blocks, different email entirely
|
|
473
|
+
```
|
|
139
474
|
|
|
140
|
-
|
|
475
|
+
Naming matters here: a Block named `extract-verification-link` would wrongly imply it only
|
|
476
|
+
ever handles verification emails - once a Block's behavior is mem-driven, its name should
|
|
477
|
+
describe the mechanism (`extract-email-link`), not one scenario that happens to use it.
|
|
141
478
|
|
|
142
|
-
|
|
479
|
+
A **mail-provider page hub** ties it together, the same `definePageBlock` pattern used
|
|
480
|
+
everywhere else - one page, several reusable methods, each pulling its specifics from mem:
|
|
143
481
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
482
|
+
```typescript
|
|
483
|
+
export const MailpitInboxPageBlock = definePageBlock<MailpitInbox>({
|
|
484
|
+
name: "page-mailpit-inbox",
|
|
485
|
+
checkpoint: "MailpitInbox",
|
|
486
|
+
verify: [Trait.url({ pathname: "/" })],
|
|
487
|
+
methods: {
|
|
488
|
+
assertEmailReceived: () => AssertEmailReceivedBlock,
|
|
489
|
+
openMessage: () => OpenMessageBlock,
|
|
490
|
+
assertEmailContent: () => AssertEmailContentBlock,
|
|
491
|
+
extractEmailLink: () => ExtractEmailLinkBlock,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### QA-rich: does the email exist, and does it say the right thing
|
|
497
|
+
|
|
498
|
+
Two more assert Blocks slot into the same chain, both mem-driven, both proven to fail loud
|
|
499
|
+
(not silently pass) on a real negative case - no email arrived, and an email arrived with
|
|
500
|
+
the wrong copy:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// "Did the email even arrive" - doesn't open/consume it, safe to check before deciding to.
|
|
504
|
+
// A mem-aware Trait: the selector depends on the expected recipient, so it
|
|
505
|
+
// can't be a static Trait.visible(...) string - check(page, mem) already gets mem.
|
|
506
|
+
const emailReceived: Trait = {
|
|
507
|
+
name: "email-received",
|
|
508
|
+
async check(page, mem) {
|
|
509
|
+
const { email } = mem.get(ExpectedRecipient.key);
|
|
510
|
+
try {
|
|
511
|
+
await page.locator(MailpitSel.messageRow, { hasText: email }).first().waitFor({
|
|
512
|
+
state: "visible",
|
|
513
|
+
timeout: 10_000,
|
|
514
|
+
});
|
|
515
|
+
return true;
|
|
516
|
+
} catch {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
154
519
|
},
|
|
155
520
|
};
|
|
521
|
+
export const AssertEmailReceivedBlock = defineAssertBlock<MailpitInbox>({
|
|
522
|
+
name: "assert-email-received",
|
|
523
|
+
checkpoint: "MailpitInbox",
|
|
524
|
+
requires: [ExpectedRecipient.key],
|
|
525
|
+
verify: [emailReceived],
|
|
526
|
+
});
|
|
156
527
|
|
|
157
|
-
|
|
158
|
-
mem
|
|
528
|
+
// "Does the email say what it's supposed to" - reads the body content, not
|
|
529
|
+
// just whether a link exists. The expected text comes from mem too (same
|
|
530
|
+
// reuse reasoning as above) via another bespoke mem-aware Trait, since
|
|
531
|
+
// Trait.frameContains's own factory bakes its expected string in at
|
|
532
|
+
// Block-definition time, not runtime.
|
|
533
|
+
const bodyContainsExpectedText: Trait = {
|
|
534
|
+
name: "body-contains-expected-text",
|
|
535
|
+
async check(page, mem) {
|
|
536
|
+
const expected = mem.get(ExpectedEmailContent);
|
|
537
|
+
const text = await page.frameLocator(MailpitSel.previewIframe).locator("body").textContent();
|
|
538
|
+
return (text ?? "").includes(expected);
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
export const AssertEmailContentBlock = defineAssertBlock<MailpitMessageOpen>({
|
|
542
|
+
name: "assert-email-content",
|
|
543
|
+
checkpoint: "MailpitMessageOpen",
|
|
544
|
+
requires: [ExpectedEmailContent],
|
|
545
|
+
verify: [bodyContainsExpectedText],
|
|
546
|
+
});
|
|
547
|
+
```
|
|
159
548
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
549
|
+
`Trait.frameVisible(frameSelector, innerSelector)` / `Trait.frameText(frameSelector,
|
|
550
|
+
innerSelector, expected)` (exact match) / `Trait.frameContains(frameSelector, innerSelector,
|
|
551
|
+
expected)` (substring match, for a *fixed* known expected string) mirror
|
|
552
|
+
`Trait.visible`/`Trait.text` exactly, scoped to a frame - reusable for any iframe content,
|
|
553
|
+
not just mail; reach for a bespoke mem-aware Trait instead when the expected value itself
|
|
554
|
+
needs to vary per run, as both examples above do. Wired into the chain right where they
|
|
555
|
+
belong:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
engine.defineFlow([
|
|
559
|
+
start, NavMailpitInboxBlock, AssertEmailReceivedBlock, OpenMessageBlock,
|
|
560
|
+
AssertEmailContentBlock, ExtractEmailLinkBlock, NavVerificationLinkBlock, end,
|
|
561
|
+
])
|
|
163
562
|
```
|
|
164
563
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
just findable by typing `Trait.` without needing to already know their names). `Trait.url`
|
|
188
|
-
takes a structured `URLPatternInit` (`{ pathname, hostname, search, hash, ... }`, the same
|
|
189
|
-
shape the standard `URLPattern` Web API takes), not a hand-written regex - whichever
|
|
190
|
-
components you leave out default to "match anything," so `Trait.url({ pathname:
|
|
191
|
-
"/inventory.html" })` already ignores whatever query params a hybrid SPA tacks on, with no
|
|
192
|
-
separate flag needed for that. A hand-written `Trait` isn't limited to the page, either - its
|
|
193
|
-
`check(page, mem)` receives `mem` too, for the same-Checkpoint case: an action that doesn't
|
|
194
|
-
navigate anywhere at all (clicking "+" to bump a cart item's quantity, say) has `In === Out`,
|
|
195
|
-
which `defineBlock`/`defineFlow` already accept with no special-casing - `act()` updates `mem`
|
|
196
|
-
alongside the real click (`mem.set(Quantity, mem.get(Quantity) + 1)`), and a bespoke `verify`
|
|
197
|
-
Trait confirms the page's own displayed value actually matches what `mem` now expects
|
|
198
|
-
(`shown === String(mem.get(Quantity))`), not just that *some* number is showing. `resolve()`
|
|
199
|
-
itself stays exactly as pure as ever - it never sees `mem`, only `verify` does. `verify` only
|
|
200
|
-
ever confirms what a Block itself just did, though - nothing previously checked whether the
|
|
201
|
-
page was STILL there by the time the NEXT Block's `act()` starts. A `precondition` (same
|
|
202
|
-
`Trait[]`/`(input) => Trait[]` shape as `verify`, checked automatically by `connect()`/
|
|
203
|
-
`runGraph` right before `act()` runs, never called directly) closes that gap: a session
|
|
204
|
-
timeout, a redirect, an interstitial popup - anything that changes the page in the moment
|
|
205
|
-
between one Block finishing and the next one starting - fails loud with a clear "trait X
|
|
206
|
-
failed before Y," instead of a confusing error deep inside `act()` trying to click something
|
|
207
|
-
that's no longer there. Once Blocks are composed into a `Flow`, `flow.withBlockVerify`/
|
|
208
|
-
`flow.modBlockVerify` patch one Block's verify from outside - a spec that only imports the
|
|
209
|
-
finished flow, addressed by the Block reference (preferred), its name, or its numeric
|
|
210
|
-
position in the flow - without editing the flow's own file. `composeBlock(name, steps)`
|
|
211
|
-
chains several Blocks into one named unit the same way `connect()` already does - the
|
|
212
|
-
multi-step-form case (a government form with several stepper pages, say), which is
|
|
213
|
-
naturally "one feature" but still wants each step's own real `verify` independently
|
|
214
|
-
overridable, not one giant Block with no per-step confirmation. `composedBlock.withStepVerify`/
|
|
215
|
-
`modStepVerify` patch one step from outside, addressed the same three ways
|
|
216
|
-
`withBlockVerify`/`modBlockVerify` already are - a `composeBlock` result is a plain Block,
|
|
217
|
-
so it drops straight into `defineFlow([start, ..., composed, ..., end])` like any other.
|
|
218
|
-
`fastForwardComposeBlock(name, steps)` is the same shape marked for **wall-clock
|
|
219
|
-
fast-forward**: one opaque demo step that **blitzes** (no per-inner gate, no smooth
|
|
220
|
-
cursor theater, Playwright slowMo off for that flow unless `WAYGRAPH_SLOWMO` is set)
|
|
221
|
-
unless expanded. Put **several** FF units in one Flow when you need to race past auth,
|
|
222
|
-
then a heavy dashboard settle, then watch the interesting middle.
|
|
223
|
-
|
|
224
|
-
**Dispute / disable FF:** `waygraph demo --ff-disabled` (alias `--no-ff`) expands every
|
|
225
|
-
FFCompose into its inner Blocks so a broken login step is visible as its own panel row.
|
|
226
|
-
Former FF inners keep blitz pacing so wall-clock stays comparable to opaque FF (same
|
|
227
|
-
real acts). `--ff-expand` is the older expand-only flag (same flatten). Saucedemo
|
|
228
|
-
`checkoutFlow` uses `ff-owner-auth` for the login prefix; `loginFlow` stays expanded for
|
|
229
|
-
Sign In narration.
|
|
230
|
-
|
|
231
|
-
`waygraph traverse` walks the Block graph (Phase B-E). `--parallel N` (default
|
|
232
|
-
`--session clone`) bootstraps one context, forks N workers with `storageState` + mem
|
|
233
|
-
snapshot, partitions edges by hash, and claims via in-process edge leases (audit under
|
|
234
|
-
`.waygraph-traverse/`). `--session inherit` is refused when `parallel > 1`. Phase E
|
|
235
|
-
writes `.waygraph-traverse/coverage.json` and prints `[Coverage edges=H/T ratio=R%...]`;
|
|
236
|
-
`--min-edge-coverage 80%` fails the suite with exit 2 when the ratio is below the gate
|
|
237
|
-
(even if every worker leaf-PASSed). `--coverage-out PATH` / `--no-coverage-report` adjust
|
|
238
|
-
the JSON. See `docs/proposals/traverse-ffcompose-rfc.md`.
|
|
564
|
+
### Where this shows up in `waygraph auto`
|
|
565
|
+
|
|
566
|
+
A `NavBlock` always has wildcard `In` (`from: "*"` in the static graph), so it's reasonable to
|
|
567
|
+
wonder whether `nav-mailpit-inbox` clutters the live picker on every single screen. It
|
|
568
|
+
doesn't - `waygraph auto`'s menu builder already special-cases exactly this
|
|
569
|
+
(`src/auto-explore.ts`, `buildExploreMenu`): a URL-based Nav is excluded from the interactive
|
|
570
|
+
menu once you're on a known screen, offered only as a "Start here" bootstrap option. The
|
|
571
|
+
static graph still keeps the `from: "*"` edge for `waygraph graph`/`findBlockPath`/`traverse`,
|
|
572
|
+
so `waygraph auto --blocks "<AppCheckpoint> MailpitInbox"` can still route there deliberately -
|
|
573
|
+
it's just never suggested unprompted. The tested, recommended way to actually use this is an
|
|
574
|
+
authored flow/chain (`someAppFlow then mailVerifyFlow`), exactly like `mailVerifyFlow` itself.
|
|
575
|
+
|
|
576
|
+
## Waygraph Pilot
|
|
577
|
+
|
|
578
|
+
Bootstraps a real, persistent, inspectable browser session for an **agent** to discover and
|
|
579
|
+
drive toward a natural-language goal it plans itself - not a tool that resolves one ask to one
|
|
580
|
+
Block internally. A real request is a multi-step task ("log in and buy the backpack for me",
|
|
581
|
+
"make a signature draft request") that needs an agent reasoning over a sequence of Blocks, not
|
|
582
|
+
a single best-matching edge. Comparable in shape to a browser-use-style agent, but the action
|
|
583
|
+
space is constrained to this project's own pre-verified Blocks (each with a real resolve/verify
|
|
584
|
+
already checked) instead of freeform DOM/pixel guessing - narrower, but far more accurate and
|
|
585
|
+
deterministic.
|
|
239
586
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
`withDemoPace(flow, "fast" | "slow" | "normal" | "blitz" | number)` or `withBlockPace(block, ...)`
|
|
245
|
-
(`number` `<=20` = scale vs normal, e.g. `0.5` / `2`; `>20` = absolute ms, e.g. `4500`).
|
|
246
|
-
FFCompose stays blitz. Living objectives: `docs/proposals/OBJECTIVES.md`.
|
|
247
|
-
`spawnTab(entry, page, mem)` drives a genuinely separate second tab through its own
|
|
248
|
-
Block/Flow, in the same browser context an existing `page` already belongs to - the
|
|
249
|
-
pattern `observe()` could already reach for (it's the only phase allowed to touch
|
|
250
|
-
`page.context()`), now a named, discoverable, tested primitive instead of something
|
|
251
|
-
you'd have to already know to hand-roll. `flow.run(context, mem, options)` also accepts a
|
|
252
|
-
trailing optional options object - additively, so `flow.run(context, mem)` still behaves
|
|
253
|
-
byte-for-byte as before: `options.page` drives that already-open page instead of opening
|
|
254
|
-
a fresh tab (the run won't close a page you handed it unless you say so), and
|
|
255
|
-
`options.closeOnFinish: false` leaves the run's page open and returns `{ result, page }`
|
|
256
|
-
so the caller can keep driving it - hands a run tab back instead of losing it. See
|
|
257
|
-
"Recipes" for capture-on-popup, which stays a documented pattern until a second real use
|
|
258
|
-
case promotes it to an engine API.
|
|
259
|
-
`defineNavBlock({ name, checkpoint, url })` or `defineNavClickBlock({ name, checkpoint, click })`
|
|
260
|
-
builds a Block whose only possible action is navigating. Prefer `defineNavClickBlock` for
|
|
261
|
-
click-nav in app code; `defineNavBlock` for `url` / `goto` deep links. Both accept a plain
|
|
262
|
-
string or `(mem) => string` / `(mem) => selector`. Generated `act()` is always exactly
|
|
263
|
-
`page.goto(url)` or `page.locator(click).click()`. For screen hubs that also register
|
|
264
|
-
methods, prefer `definePageBlock` (see **0.9.0 — Page, Method, Sel** above). A regular
|
|
265
|
-
`defineBlock` / `defineMethodBlock`'s `act()` receives `page` typed as `ActionPage` —
|
|
266
|
-
`goto`/`reload`/`goBack`/`goForward` are `@deprecated` there (editor strike-through pointing
|
|
267
|
-
at Nav/Page helpers). `waygraph check [project]` is the complementary whole-project sweep
|
|
268
|
-
for contexts with no editor watching (CI, generated code).
|
|
269
|
-
`new Engine({ browsers: { chromium, firefox, webkit } })` overrides which `BrowserType`
|
|
270
|
-
actually launches for a `mem`-only run, per browser name - waygraph is deliberately "just
|
|
271
|
-
an opinionated Playwright," so a stealth-patched or otherwise customized launcher (e.g.
|
|
272
|
-
`playwright-extra` plus a stealth plugin) drops in unmodified, since those already expose
|
|
273
|
-
the same `.launch()` shape as Playwright's own `chromium`/`firefox`/`webkit`.
|
|
587
|
+
```bash
|
|
588
|
+
waygraph pilot start --non-headless
|
|
589
|
+
# {"sessionId":"bb65d74c","socketPath":"...","headless":false,"graph":{...},"snapshot":{...}}
|
|
590
|
+
```
|
|
274
591
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
`
|
|
278
|
-
|
|
592
|
+
One call gives an agent everything it needs to start: a real, already-running Playwright
|
|
593
|
+
session (the exact mechanism `auto --cli --detach` already provides), and the whole project's
|
|
594
|
+
Block graph (`graph` - every Checkpoint/edge/description, not just what's reachable from the
|
|
595
|
+
current position, so the agent can plan several steps ahead). From there, the agent drives the
|
|
596
|
+
session itself, one pick at a time, using the session-control commands that already existed
|
|
597
|
+
before Pilot did:
|
|
279
598
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
599
|
+
```bash
|
|
600
|
+
waygraph auto status <sessionId> # read the current menu (no side effects)
|
|
601
|
+
waygraph auto send <sessionId> "<pick>" # run one pick, get the resulting menu back
|
|
602
|
+
waygraph auto dom <sessionId> # inspect the live DOM/ARIA tree
|
|
603
|
+
waygraph auto trace <sessionId> # read the Checkpoint/Block history so far
|
|
604
|
+
waygraph auto reach <sessionId> <Checkpoint> # run a whole multi-step route in one call
|
|
605
|
+
waygraph auto resync <sessionId> # force here to re-detect from the real page
|
|
606
|
+
```
|
|
284
607
|
|
|
285
|
-
|
|
286
|
-
-
|
|
287
|
-
|
|
288
|
-
|
|
608
|
+
Worked example (run for real against `examples/saucedemo`, no new execution code involved): an
|
|
609
|
+
agent gets a plain request - "log me in and buy the backpack" - reads the graph and the starting
|
|
610
|
+
`status`, reasons out login -> add-to-cart -> cart -> checkout info -> submit -> finish as the
|
|
611
|
+
route to `OrderComplete`, then drives it by repeatedly reading each `status`/`send` response and
|
|
612
|
+
picking the next index itself. The browser stays open the whole time; the user can watch it, or
|
|
613
|
+
check the final tab afterward.
|
|
614
|
+
|
|
615
|
+
**`auto reach <sessionId> <Checkpoint>`** runs a whole route in one call instead of one
|
|
616
|
+
`send` per step - real, reported pain for any non-trivial task on a rich graph ("the entire
|
|
617
|
+
prompts of the day" for one manual walkthrough). It path-finds via the same BFS `auto --blocks
|
|
618
|
+
<From> <To>` already uses, then runs each step directly against the session's own live
|
|
619
|
+
page/mem, verifying every step actually lands where its own graph edge promised (not just that
|
|
620
|
+
it ran without throwing - a Block can legitimately resolve elsewhere, e.g. a login submission
|
|
621
|
+
staying put after bad auth). **Real, stated limitation, not silently papered over:** for a
|
|
622
|
+
target only reachable by crossing a wildcard (`from: "*"`) edge whose real DOM precondition
|
|
623
|
+
depends on same-Checkpoint setup (e.g. filling form fields before a submit), neither `reach`
|
|
624
|
+
nor the already-shipped `auto --blocks` can safely route there automatically - both fail loud
|
|
625
|
+
within a bounded time (a real Playwright click/wait timeout) rather than silently guessing
|
|
626
|
+
wrong or hanging. Full design history - three rejected approaches, a real correctness bug this
|
|
627
|
+
change's own tests caught, and this limitation confirmed by direct reproduction - is in
|
|
628
|
+
`openspec/changes/waygraph-pilot/tasks.md`'s own M5 section.
|
|
629
|
+
|
|
630
|
+
**`auto resync <sessionId>`** - `here` is only ever updated by an action the session itself
|
|
631
|
+
ran (`send`/`reach` set it from a Block's own resolved Checkpoint; the raw `click`/`type`/
|
|
632
|
+
`goto` primitives null it because *they* just changed the page). Anything that changes the
|
|
633
|
+
page *outside* the session - a human clicking around in a visible `--non-headless` session
|
|
634
|
+
someone is co-driving, for instance - leaves `here` silently stale, since `send`/`reach` only
|
|
635
|
+
ever re-detect when the position is already unknown, never to double-check a known one.
|
|
636
|
+
`resync` forces that re-detection unconditionally, right now, against whatever the real page
|
|
637
|
+
actually shows. Doesn't crash or corrupt anything if you never need it - it's a real, found
|
|
638
|
+
gap (confirmed by reading the exact code path, `src/auto-session.ts`'s `currentMenu()`), fixed
|
|
639
|
+
outright rather than left as a caveat.
|
|
640
|
+
|
|
641
|
+
**On-page indicator.** A headful (`--non-headless`) session looks like an ordinary browser
|
|
642
|
+
tab otherwise - no sign anything is driving it, which matters once a human might be watching
|
|
643
|
+
or co-driving the same visible session. Every session now injects a small badge (bottom-right
|
|
644
|
+
corner: `Waygraph Pilot - session <id> - <Checkpoint>`) that click-expands into a panel
|
|
645
|
+
listing the current live menu's real edges (block name, kind, target Checkpoint,
|
|
646
|
+
description) - the same data `auto status` returns, rendered in the page itself. Both refresh
|
|
647
|
+
automatically after every `send`/`reach`/`reload`/`resync`/raw action - there's nothing
|
|
648
|
+
separate to call to keep it in sync. Particularly useful for Blind Pilot: watching the panel
|
|
649
|
+
update as newly-authored Blocks get picked up via `reload` shows the map actually growing,
|
|
650
|
+
live, not just a JSON diff in a terminal. Self-contained (`src/pilot-overlay.ts`), not built
|
|
651
|
+
on `waygraph demo`'s own panel/banner system, for the same `cli.ts`-side-effect reason
|
|
652
|
+
`pilot-v1`'s old narrate-mode ring wasn't either.
|
|
653
|
+
|
|
654
|
+
**Pilot itself does no planning, resolving, narrating, or acting** - `pilotStart`'s only job is
|
|
655
|
+
starting the session and handing back its graph in one round trip; see `src/pilot.ts`. An
|
|
656
|
+
earlier shape (`pilot ask "<text>"`, a deterministic ask-to-one-edge text matcher) was built,
|
|
657
|
+
shipped, and then explicitly rejected as not matching this goal - preserved for reference at git
|
|
658
|
+
tag `waygraph-pilot-v1-logs-prettified`, not on any active branch.
|
|
659
|
+
|
|
660
|
+
**Honest scope, not silently redefined:** this capability's own proof runs against an in-repo
|
|
661
|
+
example (`examples/saucedemo`), not a real external consumer application - it demonstrates the
|
|
662
|
+
mechanism works, not the "demoable on one real consumer" bar `ROADMAP.md` sets for `1.0.0`,
|
|
663
|
+
which remains separate, later, unmet work. Live reference: `tests/pilot/pilot.spec.ts` (the
|
|
664
|
+
library function) and `tests/cli/pilot.spec.ts` (the real CLI), both proven against live
|
|
665
|
+
saucedemo.com.
|
|
666
|
+
|
|
667
|
+
## Blind Pilot (cold start, zero Blocks)
|
|
668
|
+
|
|
669
|
+
Waygraph Pilot above assumes a Block library already exists. Blind Pilot is the opposite
|
|
670
|
+
case: a session on a site nobody has described to this package yet. `AutoSession`/`auto --cli
|
|
671
|
+
--detach` already tolerate a project directory with **zero** `.block.ts` files (no crash,
|
|
672
|
+
`here` just stays `null`), so an agent can open a real session and start exploring cold with
|
|
673
|
+
no new code at all - `auto dom <sessionId>` already gives real DOM/ARIA visibility
|
|
674
|
+
independent of any Block library.
|
|
675
|
+
|
|
676
|
+
What's actually new here is two primitives an agent needs to go from "exploring" to "building
|
|
677
|
+
a Block library incrementally":
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
waygraph auto click <sessionId> "<selector>" # act before any Block covers this
|
|
681
|
+
waygraph auto type <sessionId> "<selector>" "<text>" # same, for a real input
|
|
682
|
+
waygraph auto goto <sessionId> "<url>" # navigate the real live page
|
|
683
|
+
waygraph auto reload <sessionId> # pick up a Block just written to disk,
|
|
684
|
+
# without restarting the session
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
`click`/`type`/`goto` each re-detect the session's Checkpoint afterward (the same detection
|
|
688
|
+
`send`/`status` already use) and return a fresh snapshot - a selector matching nothing is a
|
|
689
|
+
reported failure naming it, not a silent no-op. `reload` re-discovers the project's Block
|
|
690
|
+
library/graph from disk in place, leaving the live page, mem, and current Checkpoint
|
|
691
|
+
untouched - so a Block an agent just wrote to disk becomes immediately runnable via the
|
|
692
|
+
session's existing `send`, without relaunching the browser.
|
|
693
|
+
|
|
694
|
+
**This package writes no Block content.** The driving agent recognizes a pattern ("this is a
|
|
695
|
+
login form") and writes the `.block.ts`/`*Sel`/mem-keys files itself, using the conventions
|
|
696
|
+
documented above - the same way it would write any other source file. Worked example (proven
|
|
697
|
+
end to end in `tests/cli/auto-session-blind-pilot.spec.ts` against an in-repo synthetic
|
|
698
|
+
fixture, `tests/fixtures/blind-pilot-site/`): start a session with zero Blocks, explore via
|
|
699
|
+
`dom`/`click`/`goto`, write one real `defineNavBlock` to disk, `reload`, then `send` it -
|
|
700
|
+
reaching the real Checkpoint it declares, proving it's genuinely driveable, not just visible.
|
|
701
|
+
|
|
702
|
+
**Two real gotchas found while proving this, worth knowing before debugging a silent
|
|
703
|
+
failure:** a `.block.ts` file that throws on import (e.g. a malformed Trait call) vanishes
|
|
704
|
+
from the graph with no error printed anywhere - `discoverGraph`'s per-file import errors are
|
|
705
|
+
mostly swallowed silently, so "reload did nothing" can mean a real mistake in the file just
|
|
706
|
+
written, not a package bug. And a Block not wired into any `.flow.ts` (`waygraph graph`'s own
|
|
707
|
+
"orphan" warning) is still fully visible and runnable in a live session's menu - orphan status
|
|
708
|
+
only affects `auto --blocks <From> <To>` path-finding, not `auto`'s own menu.
|
|
709
|
+
|
|
710
|
+
**Honest scope:** Blind Pilot does not currently author into the Waygraph Map convention
|
|
711
|
+
below by default - it still writes plain files wherever it's pointed. Proof stays against an
|
|
712
|
+
in-repo synthetic fixture, not a real external site.
|
|
713
|
+
|
|
714
|
+
## Waygraph Map
|
|
715
|
+
|
|
716
|
+
An opinionated, Next.js/Nuxt-style folder convention where a project's own directory
|
|
717
|
+
structure mechanically *is* its map - not a separate schema file to generate, maintain, or
|
|
718
|
+
let drift out of sync. Needs zero new engine code: `discoverGraph`/`loadBlockLibrary` already
|
|
719
|
+
recurse through any folder structure, matching only the `.block.ts` filename pattern - folder
|
|
720
|
+
naming and depth are already 100% cosmetic to every existing command.
|
|
721
|
+
|
|
722
|
+
```text
|
|
723
|
+
src/routes/
|
|
724
|
+
(base_app)/ # purely organizational - never part of any Checkpoint tag
|
|
725
|
+
dashboard/ # one folder per Checkpoint - "Dashboard"
|
|
726
|
+
page.block.ts # arrival hub (definePageBlock) - fixed name, every folder
|
|
727
|
+
nav.block.ts # navigation (defineNavBlock) - fixed name, every folder
|
|
728
|
+
dashboard.sel.ts
|
|
729
|
+
methods/
|
|
730
|
+
click-widget.block.ts
|
|
731
|
+
(external)/
|
|
732
|
+
docs/ # a different group, same shape - "Docs"
|
|
733
|
+
page.block.ts
|
|
734
|
+
nav.block.ts
|
|
735
|
+
docs.sel.ts
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Same Block helpers as today's freeform "manual mode" (`definePageBlock`/`defineNavBlock`/
|
|
739
|
+
`defineMethodBlock`/etc.) - this only changes *where files live and what they're named*,
|
|
740
|
+
never what kind of Block authors them or how they execute. A project adopts it incrementally,
|
|
741
|
+
folder by folder; the two styles compose in the same flow with no special-casing (proven in
|
|
742
|
+
`templates/scaffold`'s own `routes-demo.flow.ts`: `nav-home` from `src/blocks/`, manual mode,
|
|
743
|
+
into `nav-docs` from `src/routes/`, Map convention, one real navigation).
|
|
744
|
+
|
|
745
|
+
**"waypack" - loading another project's Blocks needs no separate manifest either.** A second
|
|
746
|
+
project depending on a Map-convention project as a plain `file:` dependency (the same
|
|
747
|
+
mechanism `examples/saucedemo`'s own `"waygraph": "file:../.."` already uses) is immediately
|
|
748
|
+
understandable: `waygraph graph node_modules/<loaded-package>` correctly discovers its full
|
|
749
|
+
Checkpoint/edge structure, with nothing generated or maintained separately. Proven end to end
|
|
750
|
+
in `examples/routed-demo-consumer/` against `examples/routed-demo/`.
|
|
751
|
+
|
|
752
|
+
**A real bug this proof surfaced, fixed, not left as a caveat:** a *live* `--detach` session
|
|
753
|
+
against a package loaded through a consumer project's own deeply nested `node_modules/<pkg>`
|
|
754
|
+
path used to fail with `listen EINVAL` - the session's Unix socket lived under the project
|
|
755
|
+
directory, and that absolute path could exceed the OS's AF_UNIX limit (~108 bytes on Linux).
|
|
756
|
+
Fixed: session sockets now live in a short, fixed location (`os.tmpdir()/waygraph-auto/`),
|
|
757
|
+
independent of how deeply the project itself is nested - confirmed against the exact
|
|
758
|
+
previously-failing path (`auto --cli --detach`, `auto reach`, and quit all working normally).
|
|
759
|
+
Session metadata (`.waygraph-auto/<id>.json`, discoverable per-project) is unaffected - only
|
|
760
|
+
the socket moved.
|
|
761
|
+
|
|
762
|
+
Full design history (why "Router" isn't a separate thing to build, two prior wrong readings
|
|
763
|
+
of this before landing here, and everything above stated as requirements) in
|
|
764
|
+
`openspec/changes/waygraph-map/`.
|
|
765
|
+
|
|
766
|
+
## Getting started (pick one)
|
|
767
|
+
|
|
768
|
+
| Goal | Command |
|
|
769
|
+
|------|---------|
|
|
770
|
+
| Watch Sauce Demo step-through (temp dir only) | `npx waygraph try demo` |
|
|
771
|
+
| Interactive explore (Add/Remove/Open details from live page) | `cd examples/saucedemo && npm i && npx waygraph auto` |
|
|
772
|
+
| Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx create-waygraph my-app` **or** `npx waygraph init my-app` |
|
|
773
|
+
| Install coding-agent defs for diving an app into Blocks | `npx waygraph agent-dive --loop claude` (also `opencode` / `cursor` / `vscode`) |
|
|
774
|
+
| Add waygraph to an existing repo | `npm install waygraph @playwright/test` |
|
|
289
775
|
|
|
290
|
-
**
|
|
291
|
-
`
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
776
|
+
**Scaffold is not hidden inside waygraph alone** - the offline starter also ships as
|
|
777
|
+
[`create-waygraph`](https://github.com/deviate-dv8/create-waygraph) on npm (same template
|
|
778
|
+
as `waygraph init` since 0.7.5). Use whichever entry you already have:
|
|
779
|
+
`npx create-waygraph my-app` when reading the GitHub Pages docs, or `npx waygraph init my-app`
|
|
780
|
+
when the CLI is already installed. After either:
|
|
295
781
|
|
|
296
|
-
|
|
782
|
+
```bash
|
|
783
|
+
cd my-app && npm install && npx playwright install chromium && npm test
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
`try demo` is different: live saucedemo.com, step-through **Sign In -> Shop & Checkout ->
|
|
787
|
+
blocked Viewer login**, then headless tests. Permanent full example:
|
|
788
|
+
[`examples/saucedemo`](./examples/saucedemo). Offline empty scaffold: `init` /
|
|
789
|
+
`create-waygraph`.
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
# From this checkout, after npm run build:
|
|
793
|
+
cd examples/saucedemo && npm install && npx playwright install chromium
|
|
794
|
+
npm test # live Playwright suite
|
|
795
|
+
npm run auto # headed interactive explore
|
|
796
|
+
npm run demo:step # stepper (manual Next)
|
|
797
|
+
npm run demo:autoplay # stepper with Auto-advance
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
Both scaffold commands emit the same offline tree (route-shaped `demo-web/` + `methods/`,
|
|
801
|
+
stubs, fixtures, one YAP slide). Layout: [`docs/scaffold.html`](./docs/scaffold.html) ·
|
|
802
|
+
`templates/scaffold/STRUCTURE.md`.
|
|
803
|
+
|
|
804
|
+
**Consumer layout (Next.js App Router):** block folders mirror `app/` page routes (`/` at
|
|
805
|
+
the namespace root; no invented `landing/`/`root/`; sidebar = chrome + edges). See
|
|
806
|
+
[`docs/consumer.html`](./docs/consumer.html). A separate mesh handout
|
|
807
|
+
(`WAYGRAPH-CONSUMER-CONVENTION.md`) covers the same convention outside this repo - not
|
|
808
|
+
part of this package's own tree.
|
|
809
|
+
|
|
810
|
+
## CLI reference
|
|
811
|
+
|
|
812
|
+
`waygraph auto [project]` is the **interactive explore** loop: `locate()` reads where you
|
|
813
|
+
are, lists runnable Blocks from the graph, you pick one (headful panel or `--cli` terminal
|
|
814
|
+
menu), repeat. Static graph export (old JSON / Mermaid) is `waygraph graph [project]`
|
|
815
|
+
(`--mermaid`). Unattended JSON execution is `run` + `WAYGRAPH_JSON=1`.
|
|
816
|
+
|
|
817
|
+
**`--cli` session control:** `waygraph auto --cli` on its own is a blocking terminal loop -
|
|
818
|
+
fine for a person, awkward for an agent that needs to inspect state between picks or send
|
|
819
|
+
one command at a time without guessing a whole input sequence upfront. `--detach` runs that
|
|
820
|
+
same session as a background socket server instead (session identity under
|
|
821
|
+
`.waygraph-auto/` in the target project, mirroring `.waygraph-traverse/`'s convention);
|
|
822
|
+
`auto send <sessionId> "<pick>"` and `auto status <sessionId>` are non-interactive
|
|
823
|
+
request/response calls against it (no TTY needed), and `auto attach <sessionId>` reopens an
|
|
824
|
+
interactive terminal against an already-running session. `send q`/`send quit` ends the
|
|
825
|
+
session and cleans up its socket/metadata. `--detach` defaults to headless;
|
|
826
|
+
`--non-headless` (the same flag `run`/`demo` already use) launches a real visible browser
|
|
827
|
+
window while the session stays driven entirely through `send`/`status`/`attach`/`dom` - not
|
|
828
|
+
a new picker UI, and the existing page-embedded headful panel (bare `waygraph auto`, no
|
|
829
|
+
`--cli`) is untouched either way.
|
|
830
|
+
|
|
831
|
+
**Session history:** `auto trace <sessionId>` returns a Checkpoint/Block-level record of
|
|
832
|
+
what the session actually did - Block name, Checkpoint before/after, and (when the Block
|
|
833
|
+
authors them) its resolved `stubBefore`/`stubAfter`/`stubOnError` demo-narration fixtures
|
|
834
|
+
(the same highlight/todo/device data `waygraph demo`'s own lifecycle logging computes via
|
|
835
|
+
`runStubPhase`, reused here since it's pure data with no browser side effects). Deliberately
|
|
836
|
+
not a raw action recorder - no clicks/fills, only the same Block-level resolution the rest of
|
|
837
|
+
the engine already reasons about, capped at the last 500 steps.
|
|
838
|
+
|
|
839
|
+
**Reading the live page:** `auto dom <sessionId>` gives an agent driving a session read-only
|
|
840
|
+
visibility into the actual page, without reading the target project's frontend source.
|
|
841
|
+
Default `--mode aria` returns Playwright's AI-oriented accessibility snapshot
|
|
842
|
+
(`ariaSnapshotJSON({ mode: "ai" })`) - small and usually enough to find "the Username
|
|
843
|
+
textbox" or "the Login button." `--mode full` returns a bounded raw DOM subtree
|
|
844
|
+
(tag/attributes/text/children - useful when you need actual CSS classes or data-attributes
|
|
845
|
+
`aria` can't see), hard-capped on depth/node count/text length and marked `truncated: true`
|
|
846
|
+
whenever a cap is hit, since this is read by an LLM, not a human. `--selector <sel>` scopes
|
|
847
|
+
either mode to one element's subtree instead of the whole page - a modifier on the mode, not
|
|
848
|
+
a third mode.
|
|
849
|
+
|
|
850
|
+
**Agent bootstrap:** `waygraph pilot start` starts a `--detach` session and reads back the
|
|
851
|
+
whole project graph in one call, so an agent can begin planning a multi-step request
|
|
852
|
+
immediately - see "Waygraph Pilot" above for the full picture.
|
|
853
|
+
|
|
854
|
+
**Cold start (zero Blocks):** `auto click/type/goto <sessionId>` act on the live page even
|
|
855
|
+
when no Block covers that action yet, and `auto reload <sessionId>` picks up a Block just
|
|
856
|
+
written to disk without restarting the session - see "Blind Pilot" above.
|
|
857
|
+
|
|
858
|
+
Flows are files (0.10.5+):
|
|
297
859
|
|
|
298
860
|
| Want | Command |
|
|
299
861
|
|------|---------|
|
|
300
|
-
| List flows | `waygraph list`
|
|
301
|
-
| Run by file | `waygraph run src/flows/shop.flow.ts --data '{
|
|
302
|
-
| Same via `auto` | `waygraph auto src/flows/shop.flow.ts --data '{
|
|
862
|
+
| List flows | `waygraph list` -> `src/flows/shop.flow.ts shopFlow` |
|
|
863
|
+
| Run by file | `waygraph run src/flows/shop.flow.ts --data '{...}'` |
|
|
864
|
+
| Same via `auto` | `waygraph auto src/flows/shop.flow.ts --data '{...}'` |
|
|
303
865
|
| Run by export | `waygraph run --blocks shopFlow` |
|
|
304
866
|
| Manual watch | `waygraph demo src/flows/shop.flow.ts` |
|
|
305
867
|
| Auto-advance | `waygraph demo --blocks shopFlow --auto-next` (nav auto-hides strip) |
|
|
306
|
-
| Fast / full strip | `waygraph demo
|
|
868
|
+
| Fast / full strip | `waygraph demo ... --fast` (shorter gates; keeps cursor) · `--full` (classic chips; default = carousel) |
|
|
307
869
|
| QA watch + record | `waygraph demo --blocks shopFlow --auto-play-video` |
|
|
308
|
-
| Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{
|
|
870
|
+
| Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{...}'` |
|
|
309
871
|
| Headed execute | `waygraph run --blocks shopFlow --non-headless --video` |
|
|
310
872
|
| Explore | `waygraph auto` / `waygraph auto --cli` |
|
|
311
873
|
| Path-find | `waygraph auto --blocks LoginPage OrderComplete` |
|
|
874
|
+
| Run `--cli` as a background session | `waygraph auto --cli --detach` -> `{sessionId, socketPath}` |
|
|
875
|
+
| Drive a detached session (no TTY) | `waygraph auto send <sessionId> "<pick>"` -> JSON state |
|
|
876
|
+
| Read a detached session's state | `waygraph auto status <sessionId>` (no side effects) |
|
|
877
|
+
| Reattach a terminal to a detached session | `waygraph auto attach <sessionId>` |
|
|
878
|
+
| Read the live page (aria, small/default) | `waygraph auto dom <sessionId>` |
|
|
879
|
+
| Read the live page (raw DOM, bounded) | `waygraph auto dom <sessionId> --mode full` |
|
|
880
|
+
| Scope either to one element | `waygraph auto dom <sessionId> --selector ".inventory_list"` |
|
|
881
|
+
| Detached session with a real visible browser | `waygraph auto --cli --detach --non-headless` |
|
|
882
|
+
| Read the session's Checkpoint/Block history | `waygraph auto trace <sessionId>` |
|
|
883
|
+
| Whole-project nav-escape + inline-selector + orphan sweep | `waygraph check [project]` |
|
|
884
|
+
| Graph crawl | `waygraph traverse [project] --parallel N --min-edge-coverage 80%` |
|
|
885
|
+
| Coding-agent defs | `waygraph agent-dive --loop claude` |
|
|
886
|
+
| One-shot temp-dir demo | `waygraph try demo` / `waygraph try auto` / `waygraph try auto:cli` |
|
|
312
887
|
|
|
313
888
|
`--blocks` / positional accepts a Flow export, a `.flow.ts` path, or `"a then b"`.
|
|
314
889
|
`auto <file.flow.ts>` **runs** that flow (same as `run`); bare `auto` still explores.
|
|
315
|
-
`--auto-next` (alias `--autoplay`) = panel Auto-advance. `--auto-play-video` is **demo
|
|
316
|
-
`chain` remains a compat alias for `run --blocks` / `auto --blocks`.
|
|
890
|
+
`--auto-next` (alias `--autoplay`) = panel Auto-advance. `--auto-play-video` is **demo
|
|
891
|
+
only**. `chain` remains a compat alias for `run --blocks` / `auto --blocks`.
|
|
892
|
+
|
|
893
|
+
Run `waygraph --help` (or any subcommand with no args) for the full flag reference kept
|
|
894
|
+
in `src/cli.ts`'s own `usage()` - that's the source of truth for flags, this table is the
|
|
895
|
+
scannable summary.
|
|
317
896
|
|
|
318
897
|
## Example
|
|
319
898
|
|
|
@@ -372,18 +951,13 @@ const result = await flow.run(context, mem);
|
|
|
372
951
|
// result === { __state: "Submitted" }
|
|
373
952
|
```
|
|
374
953
|
|
|
375
|
-
See `tests/define-flow.spec.ts` here, and `examples/saucedemo/tests/checkout-flow.spec.ts`
|
|
376
|
-
(a real multi-Block flow against live saucedemo.com), for this running for real. The
|
|
377
|
-
lower-level `connect()`/`runGraph()` still exist and are what `defineFlow` builds on -
|
|
378
|
-
reach for them directly only if you need a shape `defineFlow`'s array can't express yet.
|
|
379
|
-
|
|
380
954
|
## Recipes
|
|
381
955
|
|
|
382
956
|
### Detect / drive the surviving tab after a run
|
|
383
957
|
|
|
384
958
|
The default run closes the page it opened, and a page you hand in via `options.page`
|
|
385
|
-
survives. For a flow that must BE the surviving tab after it returns (an interactive
|
|
386
|
-
a hand-back to a caller), ask for the page back explicitly:
|
|
959
|
+
survives. For a flow that must BE the surviving tab after it returns (an interactive
|
|
960
|
+
demo, a hand-back to a caller), ask for the page back explicitly:
|
|
387
961
|
|
|
388
962
|
```typescript
|
|
389
963
|
const { result, page } = await registerFlow.run(context, mem, { closeOnFinish: false });
|
|
@@ -402,7 +976,7 @@ event armed before the click that is expected to open exactly one popup:
|
|
|
402
976
|
let popup: Page | undefined;
|
|
403
977
|
context.once("page", (p) => { popup = p; });
|
|
404
978
|
await linkHandle.click(); // the block that causes the popup (e.g. the
|
|
405
|
-
|
|
979
|
+
// MailHog email's real verify link)
|
|
406
980
|
if (popup) {
|
|
407
981
|
await popup.waitForLoadState(); // fully loaded before calling into the engine
|
|
408
982
|
await verifyLinkFlow.run(context, mem, { page: popup, closeOnFinish: false });
|
|
@@ -431,9 +1005,9 @@ npm run build # tsc -p tsconfig.build.json, emits dist/
|
|
|
431
1005
|
|
|
432
1006
|
### Blind-agent overlay gate (anti-blank)
|
|
433
1007
|
|
|
434
|
-
Every live modal (`#wg-panel`, `#wg-banner`, `#wg-auto-panel`) stamps
|
|
435
|
-
`data-wg-ui` / `data-wg-modal` / `data-wg-ready="1"`. Do **not** claim the
|
|
436
|
-
|
|
1008
|
+
Every live modal (`#wg-panel`, `#wg-banner`, `#wg-auto-panel`, `#wg-todo-dock`) stamps
|
|
1009
|
+
`data-wg-ui` / `data-wg-modal` / `data-wg-ready="1"`. Do **not** claim the stepper works
|
|
1010
|
+
unless one of these passes:
|
|
437
1011
|
|
|
438
1012
|
```bash
|
|
439
1013
|
# Playwright helper (exported from waygraph)
|
|
@@ -446,32 +1020,46 @@ WAYGRAPH_PROVE_EXIT=1 WAYGRAPH_PROVE_SHOT=/tmp/wg.png \
|
|
|
446
1020
|
# stderr: WAYGRAPH_PROVE {"ok":true,"beacons":[...],"readyAttr":"1"}
|
|
447
1021
|
```
|
|
448
1022
|
|
|
449
|
-
Tests: `tests/cli/overlay-beacon.spec.ts`. Helpers: `src/overlay-beacon.ts`.
|
|
1023
|
+
Tests: `tests/cli/overlay-beacon.spec.ts`. Helpers: `src/overlay-beacon.ts`. Rules for
|
|
1024
|
+
touching the stepper/overlay chrome itself: `src/CLI-STOMP-GUARD.md`.
|
|
450
1025
|
|
|
451
1026
|
## Layout
|
|
452
1027
|
|
|
453
1028
|
```
|
|
454
|
-
package.json the "waygraph" npm package itself
|
|
1029
|
+
package.json the "waygraph" npm package itself
|
|
455
1030
|
src/
|
|
456
1031
|
types.ts Checkpoint, Instruction, Block, connect()
|
|
457
1032
|
mem-page.ts MemKey, key(), MemPage
|
|
458
1033
|
trait.ts Trait (type + discoverable Trait.url/.text/.visible), runVerify()
|
|
459
|
-
engine.ts runGraph(), definePageBlock, defineMethodBlock, Engine,
|
|
1034
|
+
engine.ts runGraph(), definePageBlock, defineMethodBlock, Engine, ...
|
|
460
1035
|
graph.ts discoverGraph, orphans, paths
|
|
461
|
-
auto-explore.ts
|
|
462
|
-
|
|
1036
|
+
auto-explore.ts waygraph auto menu building
|
|
1037
|
+
auto-explore-run.ts waygraph auto interactive loop (headful + --cli)
|
|
1038
|
+
highlights.ts stub/fixture/slide/todo-dock/device narration types + helpers
|
|
1039
|
+
step-overlay.ts demo step overlay chrome (panel, ring, cursor)
|
|
1040
|
+
overlay-beacon.ts blind-agent overlay readiness beacons
|
|
1041
|
+
traverse-run.ts waygraph traverse crawl (serial/parallel)
|
|
1042
|
+
traverse-coverage.ts traverse coverage report + CI gate
|
|
1043
|
+
traverse-lease.ts traverse edge lease coordinator
|
|
1044
|
+
blocks-select.ts shared --blocks glob/regex/bare grammar
|
|
1045
|
+
agent-dive.ts waygraph agent-dive coding-agent scaffolding
|
|
1046
|
+
cli.ts waygraph CLI entrypoint (all subcommands)
|
|
1047
|
+
index.ts public barrel
|
|
463
1048
|
examples/saucedemo/ live Sauce Demo (Page + methods/ + Sel)
|
|
464
1049
|
templates/quickstart/ try demo / init template (mirrors sauce)
|
|
465
|
-
|
|
466
|
-
|
|
1050
|
+
templates/scaffold/ offline init/create-waygraph scaffold template
|
|
1051
|
+
templates/agents/ waygraph agent-dive coding-agent definitions
|
|
1052
|
+
docs/ GitHub Pages static HTML + docs/proposals/ RFCs
|
|
1053
|
+
openspec/ spec-driven planning: specs/ (current), changes/ (in-flight),
|
|
1054
|
+
changes/archive/ (shipped)
|
|
467
1055
|
tests/ runtime unit + integration tests (@playwright/test)
|
|
468
1056
|
tsconfig.json base config
|
|
469
1057
|
tsconfig.build.json emits to dist/
|
|
470
1058
|
```
|
|
471
1059
|
|
|
472
1060
|
In-package Sauce Demo: [`examples/saucedemo`](./examples/saucedemo). Other consumer
|
|
473
|
-
experiments
|
|
474
|
-
|
|
1061
|
+
experiments may still live in a sibling workspace folder and depend on this package via
|
|
1062
|
+
`"waygraph": "file:../waygraph"` during local dev (see that
|
|
475
1063
|
project's own `.npmrc` - `install-links=true` is required there, or npm will symlink
|
|
476
1064
|
instead of copy and pull this package's own `node_modules` in through the symlink, causing
|
|
477
1065
|
a duplicate-Playwright-installation error).
|