waygraph 0.13.7 → 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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Engine, start, end } from "waygraph";
|
|
2
2
|
import type { LoginPage, LoggedIn, ItemInCart } from "../states/checkout.states.js";
|
|
3
3
|
import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
|
|
4
|
+
import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
|
|
5
|
+
import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
|
|
4
6
|
import { SubmitLoginForFlow } from "../blocks/saucedemo-web/methods/submit-login-for-flow.js";
|
|
5
7
|
import { AddToCartBlock } from "../blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.js";
|
|
6
8
|
|
|
@@ -10,17 +12,21 @@ const engine = new Engine();
|
|
|
10
12
|
// it doesn't need the same DOM confirmation the real checkout.flow.ts wants.
|
|
11
13
|
// Same Blocks, same act/resolve, zero duplication - only verify differs.
|
|
12
14
|
// .withVerify([]) reads from the Block itself, discoverable by typing
|
|
13
|
-
// "NavLoginBlock."/"
|
|
15
|
+
// "NavLoginBlock."/"SubmitLoginForFlow." in an editor - not a free function
|
|
14
16
|
// you have to already know exists.
|
|
15
17
|
//
|
|
16
|
-
// Explicit <LoginPage, LoggedIn, ItemInCart> here -
|
|
17
|
-
// the wildcard Checkpoint<string> (it's reused as
|
|
18
|
-
// too), and inferring
|
|
19
|
-
// one array trips TS's overload
|
|
20
|
-
//
|
|
21
|
-
|
|
18
|
+
// Explicit <LoginPage, LoginPage, LoginPage, LoggedIn, ItemInCart> here -
|
|
19
|
+
// AddToCartBlock's In is now the wildcard Checkpoint<string> (it's reused as
|
|
20
|
+
// shopFlow's own entry block too), and inferring the chain purely from
|
|
21
|
+
// several chained .withVerify([]) calls in one array trips TS's overload
|
|
22
|
+
// resolution into unifying the wrong Out type partway through; pinning the
|
|
23
|
+
// type args sidesteps it rather than fighting it. The two middle `LoginPage`
|
|
24
|
+
// entries are fill-username / fill-password (self-loop Methods).
|
|
25
|
+
export const smokeFlow = engine.defineFlow<LoginPage, LoginPage, LoginPage, LoggedIn, ItemInCart>([
|
|
22
26
|
start,
|
|
23
27
|
NavLoginBlock.withVerify([]),
|
|
28
|
+
FillUsernameBlock,
|
|
29
|
+
FillPasswordBlock,
|
|
24
30
|
SubmitLoginForFlow.withVerify([]),
|
|
25
31
|
AddToCartBlock.withVerify([]),
|
|
26
32
|
end,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Engine, start, end, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
|
|
2
2
|
import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
|
|
3
|
-
import {
|
|
3
|
+
import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
|
|
4
|
+
import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
|
|
5
|
+
import { SubmitLoginBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
|
|
4
6
|
|
|
5
7
|
const engine = new Engine();
|
|
6
8
|
|
|
@@ -22,7 +24,9 @@ const engine = new Engine();
|
|
|
22
24
|
// "expected outcome" panel (branching no longer throws).
|
|
23
25
|
export const viewerBlockedFlow = withExpectedFailure(
|
|
24
26
|
withTitle(
|
|
25
|
-
withSessionReset(
|
|
27
|
+
withSessionReset(
|
|
28
|
+
engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginBlock, end]),
|
|
29
|
+
),
|
|
26
30
|
"Viewer: Blocked Login Attempt",
|
|
27
31
|
),
|
|
28
32
|
"locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
|
|
@@ -38,16 +38,23 @@ test("chainFlow blocks() lists the flattened episode chain in order", () => {
|
|
|
38
38
|
const blocks = chainFlow(loginFlow, shopFlow, viewerBlockedFlow).blocks();
|
|
39
39
|
expect(blocks.map((b) => b.name)).toEqual([
|
|
40
40
|
"nav-login",
|
|
41
|
+
"fill-username",
|
|
42
|
+
"fill-password",
|
|
41
43
|
"submit-login",
|
|
42
44
|
"add-to-cart",
|
|
43
45
|
"nav-cart",
|
|
44
46
|
"nav-checkout-info",
|
|
47
|
+
"fill-first-name",
|
|
48
|
+
"fill-last-name",
|
|
49
|
+
"fill-postal-code",
|
|
45
50
|
"submit-checkout-info",
|
|
46
51
|
"finish-order",
|
|
47
52
|
"nav-login",
|
|
53
|
+
"fill-username",
|
|
54
|
+
"fill-password",
|
|
48
55
|
"submit-login",
|
|
49
56
|
]);
|
|
50
|
-
expect(blocks.every((b, i) => (i ===
|
|
57
|
+
expect(blocks.every((b, i) => (i === 12 ? b.resetSessionBefore === true : !b.resetSessionBefore))).toBe(
|
|
51
58
|
true,
|
|
52
59
|
);
|
|
53
60
|
});
|
|
@@ -25,9 +25,16 @@ Docs: [docs/scaffold.html](../../docs/scaffold.html)
|
|
|
25
25
|
│ ├── states/
|
|
26
26
|
│ │ ├── demo.states.ts
|
|
27
27
|
│ │ └── demo.mem-keys.ts # SelectedItem for Effect / auto
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
│
|
|
28
|
+
│ ├── flows/
|
|
29
|
+
│ │ ├── example.flow.ts # nav + assert (smoke)
|
|
30
|
+
│ │ ├── shop.flow.ts # nav + add-item + clear (Effect path)
|
|
31
|
+
│ │ └── routes-demo.flow.ts # Home (manual mode) -> Docs (Map convention)
|
|
32
|
+
│ └── routes/ # Waygraph Map convention - see README.md
|
|
33
|
+
│ └── (external)/
|
|
34
|
+
│ └── docs/
|
|
35
|
+
│ ├── docs.sel.ts
|
|
36
|
+
│ ├── nav.block.ts # fixed name - every page-slug folder's Nav
|
|
37
|
+
│ └── page.block.ts # fixed name - every page-slug folder's arrival hub
|
|
31
38
|
├── scripts/
|
|
32
39
|
│ ├── fixture-server.mjs # serves home.html on :4177
|
|
33
40
|
│ └── with-fixture.mjs # start server + run waygraph auto/demo/run
|
|
@@ -59,3 +66,16 @@ npm run auto:cli # same menus in the terminal
|
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
After `nav-home`, Effect blocks expose one menu row per visible Add/Remove button.
|
|
69
|
+
|
|
70
|
+
## Two authoring modes
|
|
71
|
+
|
|
72
|
+
`src/blocks/` (`demo-web/`, `demo-external/`) is **manual mode** - freeform, developer-chosen
|
|
73
|
+
file layout, the default this whole scaffold otherwise uses.
|
|
74
|
+
|
|
75
|
+
`src/routes/` is the **Waygraph Map** convention - `(group)/<page-slug>/page.block.ts` +
|
|
76
|
+
`nav.block.ts` + `methods/*.block.ts`, one Checkpoint per folder, `(group)` purely
|
|
77
|
+
organizational (parens, Next.js route-group style - never part of the Checkpoint tag). Same
|
|
78
|
+
Block helpers, same execution - only where files live and what they're named changes. A
|
|
79
|
+
project can adopt it folder by folder; `routes-demo.flow.ts` proves both modes compose in one
|
|
80
|
+
flow (`nav-home` from `blocks/`, `nav-docs` from `routes/`). See the main package's own
|
|
81
|
+
`README.md` "Waygraph Map" section and `openspec/changes/waygraph-map/`.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { definePageBlock, Trait } from "waygraph";
|
|
2
|
+
import type { MailpitInbox } from "../../../states/demo.states.js";
|
|
3
|
+
import { AssertEmailReceivedBlock } from "./methods/assert-email-received.method.block.js";
|
|
4
|
+
import { OpenMessageBlock } from "./methods/open-message.method.block.js";
|
|
5
|
+
import { AssertEmailContentBlock } from "./methods/assert-email-content.method.block.js";
|
|
6
|
+
import { ExtractEmailLinkBlock } from "./methods/extract-email-link.method.block.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Kind: Page
|
|
10
|
+
* Helper: definePageBlock
|
|
11
|
+
* Route: demo-external/mailpit/ (= http://127.0.0.1:8025/ by default)
|
|
12
|
+
*
|
|
13
|
+
* Hub for the Mailpit dev-inbox screen. Arrival-only (no url/click) -
|
|
14
|
+
* nav-mailpit-inbox owns the goto. Methods: confirm the email arrived, open
|
|
15
|
+
* it, confirm its content, then read its link - all mem-driven (recipient,
|
|
16
|
+
* link pattern, expected content), so this one page's methods serve any
|
|
17
|
+
* number of different email scenarios, not just one.
|
|
18
|
+
*/
|
|
19
|
+
export const MailpitInboxPageBlock = definePageBlock<MailpitInbox>({
|
|
20
|
+
name: "page-mailpit-inbox",
|
|
21
|
+
description: "Mailpit dev inbox hub - confirm arrival/content, open the expected message, read its link.",
|
|
22
|
+
checkpoint: "MailpitInbox",
|
|
23
|
+
verify: [Trait.url({ pathname: "/" })],
|
|
24
|
+
methods: {
|
|
25
|
+
assertEmailReceived: () => AssertEmailReceivedBlock,
|
|
26
|
+
openMessage: () => OpenMessageBlock,
|
|
27
|
+
assertEmailContent: () => AssertEmailContentBlock,
|
|
28
|
+
extractEmailLink: () => ExtractEmailLinkBlock,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineAssertBlock, type Trait } from "waygraph";
|
|
2
|
+
import type { MailpitMessageOpen } from "../../../../states/demo.states.js";
|
|
3
|
+
import { ExpectedEmailContent } from "../../../../states/demo.mem-keys.js";
|
|
4
|
+
import { MailpitSel } from "../mailpit-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mem-aware Trait: the expected copy varies per email scenario, so it can't
|
|
8
|
+
* be a static `Trait.frameContains(...)` call (which bakes its expected
|
|
9
|
+
* string in at Block-definition time) - a Trait's own `check(page, mem)`
|
|
10
|
+
* already receives mem, so a bespoke Trait reads it at check time instead.
|
|
11
|
+
* This is what lets one Block serve any number of different email templates.
|
|
12
|
+
*/
|
|
13
|
+
const bodyContainsExpectedText: Trait = {
|
|
14
|
+
name: "body-contains-expected-text",
|
|
15
|
+
async check(page, mem) {
|
|
16
|
+
const expected = mem.get(ExpectedEmailContent);
|
|
17
|
+
const text = await page.frameLocator(MailpitSel.previewIframe).locator("body").textContent();
|
|
18
|
+
return (text ?? "").includes(expected);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Kind: Assert
|
|
24
|
+
* Helper: defineAssertBlock
|
|
25
|
+
* Route: demo-external/mailpit/methods/
|
|
26
|
+
*
|
|
27
|
+
* "Does the email actually say what it's supposed to" - not just "does a
|
|
28
|
+
* link exist somewhere in it." Reads the message body straight out of the
|
|
29
|
+
* preview iframe (the top-level page's own `Trait.text`/`Trait.visible`
|
|
30
|
+
* can't see inside an iframe at all - see README's "Mail adapters" section).
|
|
31
|
+
* Self-loop on MailpitMessageOpen - no state change, verify only.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately named for what it does, not for one scenario: the expected
|
|
34
|
+
* copy comes from mem (`ExpectedEmailContent`), not a hardcoded string in
|
|
35
|
+
* this file, so this one Block serves any number of different email
|
|
36
|
+
* scenarios - a Block named "assert-verification-email-content" would
|
|
37
|
+
* wrongly imply it only ever checks verification-email copy.
|
|
38
|
+
*
|
|
39
|
+
* Explicit `<MailpitMessageOpen>` type argument - see the same note on
|
|
40
|
+
* `assert-email-received.method.block.ts` for why this can't be inferred.
|
|
41
|
+
*/
|
|
42
|
+
export const AssertEmailContentBlock = defineAssertBlock<MailpitMessageOpen>({
|
|
43
|
+
name: "assert-email-content",
|
|
44
|
+
description: "Confirms the open message's body contains the mem-supplied expected copy.",
|
|
45
|
+
checkpoint: "MailpitMessageOpen",
|
|
46
|
+
requires: [ExpectedEmailContent],
|
|
47
|
+
verify: [bodyContainsExpectedText],
|
|
48
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineAssertBlock, type Trait } from "waygraph";
|
|
2
|
+
import type { MailpitInbox } from "../../../../states/demo.states.js";
|
|
3
|
+
import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
|
|
4
|
+
import { MailpitSel } from "../mailpit-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mem-aware Trait: the selector depends on which recipient this run expects,
|
|
8
|
+
* so it can't be a static `Trait.visible(...)` string - a Trait's own
|
|
9
|
+
* `check(page, mem)` already receives mem, so a bespoke Trait object reads it
|
|
10
|
+
* directly instead of needing a new engine primitive.
|
|
11
|
+
*/
|
|
12
|
+
const emailReceived: Trait = {
|
|
13
|
+
name: "email-received",
|
|
14
|
+
async check(page, mem) {
|
|
15
|
+
const { email } = mem.get(ExpectedRecipient.key);
|
|
16
|
+
try {
|
|
17
|
+
await page.locator(MailpitSel.messageRow, { hasText: email }).first().waitFor({
|
|
18
|
+
state: "visible",
|
|
19
|
+
timeout: 10_000,
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Kind: Assert
|
|
30
|
+
* Helper: defineAssertBlock
|
|
31
|
+
* Route: demo-external/mailpit/methods/
|
|
32
|
+
*
|
|
33
|
+
* "Did the email even arrive" - a lightweight existence check that does not
|
|
34
|
+
* open or consume the message, so it's safe to run before deciding to open
|
|
35
|
+
* anything. Self-loop on MailpitInbox - no state change, verify only.
|
|
36
|
+
*
|
|
37
|
+
* Explicit `<MailpitInbox>` type argument: `defineAssertBlock` defaults to
|
|
38
|
+
* wildcard `Checkpoint<string>` on both sides when uninferred, which only
|
|
39
|
+
* "works" when the assert sits last in a chain (nothing downstream narrows
|
|
40
|
+
* it) - this one sits between two specifically-typed Blocks, so it must be
|
|
41
|
+
* parameterized explicitly or `defineFlow`'s tuple typing breaks.
|
|
42
|
+
*/
|
|
43
|
+
export const AssertEmailReceivedBlock = defineAssertBlock<MailpitInbox>({
|
|
44
|
+
name: "assert-email-received",
|
|
45
|
+
description: "Confirms a message addressed to the expected recipient has arrived.",
|
|
46
|
+
checkpoint: "MailpitInbox",
|
|
47
|
+
requires: [ExpectedRecipient.key],
|
|
48
|
+
verify: [emailReceived],
|
|
49
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineMethodBlock, checkpoint } from "waygraph";
|
|
2
|
+
import type { MailpitMessageOpen } from "../../../../states/demo.states.js";
|
|
3
|
+
import { EmailLink, ExpectedLinkPattern } from "../../../../states/demo.mem-keys.js";
|
|
4
|
+
import { MailpitSel } from "../mailpit-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kind: Method
|
|
8
|
+
* Helper: defineMethodBlock
|
|
9
|
+
* Route: demo-external/mailpit/methods/
|
|
10
|
+
*
|
|
11
|
+
* Reads a link straight out of the open message's HTML preview iframe via a
|
|
12
|
+
* real DOM read (getAttribute) - no mail-catcher REST API - and stores it in
|
|
13
|
+
* mem. A separate NavBlock does the actual navigation: folding "read the
|
|
14
|
+
* link" and "go there" into one Block would repeat the exact compound-action
|
|
15
|
+
* anti-pattern `waygraph check` flags elsewhere.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately named for what it does, not for one scenario: the
|
|
18
|
+
* link-matching substring comes from mem (`ExpectedLinkPattern`), not a
|
|
19
|
+
* hardcoded string, so this one Block serves any number of different email
|
|
20
|
+
* scenarios in a project (a signup confirmation, a password reset, a magic
|
|
21
|
+
* link, ...) by varying mem per run, not by writing a new Block per
|
|
22
|
+
* scenario - a Block named "extract-verification-link" would wrongly imply
|
|
23
|
+
* it only ever handles verification emails.
|
|
24
|
+
*/
|
|
25
|
+
export const ExtractEmailLinkBlock = defineMethodBlock<MailpitMessageOpen, MailpitMessageOpen>({
|
|
26
|
+
name: "extract-email-link",
|
|
27
|
+
description: "Reads the link matching the mem-supplied pattern out of the open message's preview.",
|
|
28
|
+
requires: [ExpectedLinkPattern],
|
|
29
|
+
instruction: {
|
|
30
|
+
async act(page, _in, mem) {
|
|
31
|
+
const pattern = mem.get(ExpectedLinkPattern);
|
|
32
|
+
const frame = page.frameLocator(MailpitSel.previewIframe);
|
|
33
|
+
const link = frame.locator(`a[href*="${pattern}"]`).first();
|
|
34
|
+
await link.waitFor({ state: "visible", timeout: 15_000 });
|
|
35
|
+
const href = await link.getAttribute("href");
|
|
36
|
+
if (!href) throw new Error(`extract-email-link: no link matching "${pattern}" in message preview`);
|
|
37
|
+
mem.set(EmailLink({ url: href }));
|
|
38
|
+
},
|
|
39
|
+
resolve: () => checkpoint("MailpitMessageOpen"),
|
|
40
|
+
},
|
|
41
|
+
});
|
package/templates/scaffold/src/blocks/demo-external/mailpit/methods/open-message.method.block.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineMethodBlock, checkpoint } from "waygraph";
|
|
2
|
+
import type { MailpitInbox, MailpitMessageOpen } from "../../../../states/demo.states.js";
|
|
3
|
+
import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
|
|
4
|
+
import { MailpitSel } from "../mailpit-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kind: Method
|
|
8
|
+
* Helper: defineMethodBlock
|
|
9
|
+
* Route: demo-external/mailpit/methods/
|
|
10
|
+
*
|
|
11
|
+
* Opens the newest inbox row addressed to the mem-set recipient (not bare
|
|
12
|
+
* inbox position - a prior run's leftover mail for a different recipient
|
|
13
|
+
* would otherwise pick the wrong row). UI click only, no mail-catcher REST
|
|
14
|
+
* API - the row itself is a real link (`page.waitForURL` sees the client-side
|
|
15
|
+
* route change the same as any other in-page navigation).
|
|
16
|
+
*/
|
|
17
|
+
export const OpenMessageBlock = defineMethodBlock<MailpitInbox, MailpitMessageOpen>({
|
|
18
|
+
name: "open-message",
|
|
19
|
+
description: "Opens the newest message addressed to the expected recipient.",
|
|
20
|
+
requires: [ExpectedRecipient.key],
|
|
21
|
+
instruction: {
|
|
22
|
+
async act(page, _in, mem) {
|
|
23
|
+
const { email } = mem.get(ExpectedRecipient.key);
|
|
24
|
+
const row = page.locator(MailpitSel.messageRow, { hasText: email }).first();
|
|
25
|
+
try {
|
|
26
|
+
await row.waitFor({ state: "visible", timeout: 30_000 });
|
|
27
|
+
} catch {
|
|
28
|
+
throw new Error(`open-message: no email arrived for "${email}" within 30s`);
|
|
29
|
+
}
|
|
30
|
+
await row.click();
|
|
31
|
+
await page.waitForURL(/\/view\//, { timeout: 15_000 });
|
|
32
|
+
await page.frameLocator(MailpitSel.previewIframe).locator("body").waitFor({
|
|
33
|
+
state: "attached",
|
|
34
|
+
timeout: 15_000,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
resolve: () => checkpoint("MailpitMessageOpen"),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineNavBlock, Trait } from "waygraph";
|
|
2
|
+
import type { MailpitInbox } from "../../../states/demo.states.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kind: Nav
|
|
6
|
+
* Helper: defineNavBlock
|
|
7
|
+
* Route: demo-external/mailpit/ (cross-origin - a real Mailpit web UI, not this app)
|
|
8
|
+
*
|
|
9
|
+
* Cross-origin tooling lives under demo-external/, never demo-web/ - this is
|
|
10
|
+
* a dev-inbox screen, not part of the app under test. `WAYGRAPH_MAIL_URL`
|
|
11
|
+
* defaults to Mailpit's own usual local port.
|
|
12
|
+
*/
|
|
13
|
+
const mailpitUrl = (): string => process.env.WAYGRAPH_MAIL_URL ?? "http://127.0.0.1:8025";
|
|
14
|
+
|
|
15
|
+
export const NavMailpitInboxBlock = defineNavBlock<MailpitInbox>({
|
|
16
|
+
name: "nav-mailpit-inbox",
|
|
17
|
+
checkpoint: "MailpitInbox",
|
|
18
|
+
url: mailpitUrl,
|
|
19
|
+
verify: [Trait.url({ pathname: "/" })],
|
|
20
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<body>
|
|
15
15
|
<h1>Hello Waygraph</h1>
|
|
16
16
|
<p id="sub">Scaffold demo - Effect / auto / Page hub</p>
|
|
17
|
+
<p id="verified-banner" hidden>Email verified!</p>
|
|
17
18
|
<div id="cart">Cart: <span data-test="cart-count">0</span></div>
|
|
18
19
|
<ul id="catalog">
|
|
19
20
|
<li class="item" data-id="alpha">
|
|
@@ -34,6 +35,9 @@
|
|
|
34
35
|
</ul>
|
|
35
36
|
<button type="button" data-test="clear-cart" id="clear">Clear cart</button>
|
|
36
37
|
<script>
|
|
38
|
+
if (new URLSearchParams(location.search).get("verified") === "1") {
|
|
39
|
+
document.getElementById("verified-banner").hidden = false;
|
|
40
|
+
}
|
|
37
41
|
const countEl = document.querySelector("[data-test=cart-count]");
|
|
38
42
|
const sync = () => {
|
|
39
43
|
const n = document.querySelectorAll(".remove:not([hidden])").length;
|
|
@@ -1,50 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineAssertBlock, Trait } from "waygraph";
|
|
2
2
|
import { DemoSel } from "../demo-sel.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Kind:
|
|
6
|
-
* Helper:
|
|
5
|
+
* Kind: Assert
|
|
6
|
+
* Helper: defineAssertBlock
|
|
7
7
|
* Route: demo-web/methods/
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Self-loop-only "verify what's already on the page" check - no hand-written
|
|
10
|
+
* act/resolve to accidentally do a second thing. Same-URL assert after nav,
|
|
11
|
+
* with stubs + fixtures + a YAP slide.
|
|
10
12
|
*/
|
|
11
|
-
export const AssertHelloBlock =
|
|
13
|
+
export const AssertHelloBlock = defineAssertBlock({
|
|
12
14
|
name: "assert-hello",
|
|
13
15
|
description: "Confirms the hello heading is still visible after arrival.",
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
title: {
|
|
27
|
-
selector: DemoSel.title,
|
|
28
|
-
label: "Hello confirmed",
|
|
29
|
-
detail: "Offline scaffold proof point.",
|
|
30
|
-
duration: true,
|
|
31
|
-
},
|
|
32
|
-
sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
|
|
16
|
+
checkpoint: "HomeVerified",
|
|
17
|
+
waitForHeading: "Hello Waygraph",
|
|
18
|
+
verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.sub)],
|
|
19
|
+
stubBefore: {
|
|
20
|
+
title: { selector: DemoSel.title, label: "Heading about to check" },
|
|
21
|
+
},
|
|
22
|
+
stubAfter: {
|
|
23
|
+
title: {
|
|
24
|
+
selector: DemoSel.title,
|
|
25
|
+
label: "Hello confirmed",
|
|
26
|
+
detail: "Offline scaffold proof point.",
|
|
27
|
+
duration: true,
|
|
33
28
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
|
|
30
|
+
},
|
|
31
|
+
stubOnError: {
|
|
32
|
+
title: {
|
|
33
|
+
selector: DemoSel.title,
|
|
34
|
+
label: "Heading at failure",
|
|
35
|
+
tag: "FAIL",
|
|
40
36
|
},
|
|
41
|
-
slides: [
|
|
42
|
-
{
|
|
43
|
-
caption: "Scaffold covers auto + narration",
|
|
44
|
-
detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
|
|
45
|
-
tag: "YAP",
|
|
46
|
-
duration: true,
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
37
|
},
|
|
38
|
+
slides: [
|
|
39
|
+
{
|
|
40
|
+
caption: "Scaffold covers auto + narration",
|
|
41
|
+
detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
|
|
42
|
+
tag: "YAP",
|
|
43
|
+
duration: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
50
46
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineAssertBlock, Trait, type Trait as TraitType } from "waygraph";
|
|
2
|
+
import type { ItemInCart } from "../../../states/demo.states.js";
|
|
3
|
+
import { SelectedItem } from "../../../states/demo.mem-keys.js";
|
|
4
|
+
import { DemoSel } from "../demo-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mem-aware Trait: which item's Remove button should be visible depends on
|
|
8
|
+
* which row add-item just acted on (mem, not a static selector) - a Trait's
|
|
9
|
+
* own `check(page, mem)` already receives mem, so this reads it directly.
|
|
10
|
+
*/
|
|
11
|
+
const removeButtonVisibleForSelectedItem: TraitType = {
|
|
12
|
+
name: "remove-button-visible-for-selected-item",
|
|
13
|
+
async check(page, mem) {
|
|
14
|
+
const { id } = mem.get(SelectedItem.key);
|
|
15
|
+
return page.locator(DemoSel.removeBtn(id)).isVisible();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Kind: Assert
|
|
21
|
+
* Helper: defineAssertBlock
|
|
22
|
+
* Route: demo-web/methods/
|
|
23
|
+
*
|
|
24
|
+
* "Did add-item actually work" - provable only after the fact, from the
|
|
25
|
+
* method's real effect on the page: the specific row's own Remove button
|
|
26
|
+
* appeared (mem-matched, not just "some" row), AND the aggregate cart count
|
|
27
|
+
* reflects it. Self-loop on ItemInCart - no state change, verify only.
|
|
28
|
+
*
|
|
29
|
+
* Explicit `<ItemInCart>` type argument - `defineAssertBlock` defaults to
|
|
30
|
+
* wildcard `Checkpoint<string>` when uninferred, which only "works" when the
|
|
31
|
+
* assert sits last in a chain; this one sits mid-chain (see shop.flow.ts).
|
|
32
|
+
*/
|
|
33
|
+
export const AssertItemAddedBlock = defineAssertBlock<ItemInCart>({
|
|
34
|
+
name: "assert-item-added",
|
|
35
|
+
description: "Confirms the mem-selected item's own Remove button appeared and the cart count reflects it.",
|
|
36
|
+
checkpoint: "ItemInCart",
|
|
37
|
+
verify: [removeButtonVisibleForSelectedItem, Trait.text(DemoSel.cartCount, "1")],
|
|
38
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineNavBlock, Trait } from "waygraph";
|
|
2
|
+
import type { HomeVerified } from "../../states/demo.states.js";
|
|
3
|
+
import { EmailLink } from "../../states/demo.mem-keys.js";
|
|
4
|
+
import { DemoSel } from "./demo-sel.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kind: Nav
|
|
8
|
+
* Helper: defineNavBlock
|
|
9
|
+
* Route: demo-web/
|
|
10
|
+
*
|
|
11
|
+
* Navigates back into the app to the link extract-email-link read out of
|
|
12
|
+
* the real Mailpit message, then confirms the page actually reflects it
|
|
13
|
+
* (the verified banner only appears when the exact `verified=1` link was
|
|
14
|
+
* followed, not just any URL). This particular check is app-specific (this
|
|
15
|
+
* scaffold's own verification landing route) even though the mail-reading
|
|
16
|
+
* Blocks feeding it are scenario-agnostic - a real project would have one
|
|
17
|
+
* such Nav per distinct landing route, same as any other app screen.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately no `requires: [EmailLink.key]` here: `requires` means "the
|
|
20
|
+
* caller must seed this externally before the flow starts" (preflight
|
|
21
|
+
* checks the whole chain's requires up front) - `EmailLink` is instead
|
|
22
|
+
* produced by an earlier Block in this very chain, so declaring it as a
|
|
23
|
+
* requirement would make preflight fail before the flow ever gets the
|
|
24
|
+
* chance to produce it. Engine support for declaring a mem key as "produced
|
|
25
|
+
* by an earlier Block in this chain" (vs. externally supplied) is a
|
|
26
|
+
* separate, not-yet-built roadmap item - see ROADMAP.md's "Split requires"
|
|
27
|
+
* row.
|
|
28
|
+
*/
|
|
29
|
+
export const NavVerificationLinkBlock = defineNavBlock<HomeVerified>({
|
|
30
|
+
name: "nav-verification-link",
|
|
31
|
+
checkpoint: "HomeVerified",
|
|
32
|
+
url: (mem) => mem.get(EmailLink.key).url,
|
|
33
|
+
verify: [Trait.visible(DemoSel.verifiedBanner)],
|
|
34
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
|
|
2
|
+
import { NavMailpitInboxBlock } from "../blocks/demo-external/mailpit/nav-mailpit-inbox.block.js";
|
|
3
|
+
import { AssertEmailReceivedBlock } from "../blocks/demo-external/mailpit/methods/assert-email-received.method.block.js";
|
|
4
|
+
import { OpenMessageBlock } from "../blocks/demo-external/mailpit/methods/open-message.method.block.js";
|
|
5
|
+
import { AssertEmailContentBlock } from "../blocks/demo-external/mailpit/methods/assert-email-content.method.block.js";
|
|
6
|
+
import { ExtractEmailLinkBlock } from "../blocks/demo-external/mailpit/methods/extract-email-link.method.block.js";
|
|
7
|
+
import { NavVerificationLinkBlock } from "../blocks/demo-web/nav-verification-link.block.js";
|
|
8
|
+
import "../blocks/demo-external/mailpit/mailpit-inbox.page.block.js";
|
|
9
|
+
import { MailpitSel } from "../blocks/demo-external/mailpit/mailpit-sel.js";
|
|
10
|
+
|
|
11
|
+
const engine = new Engine();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cross-origin mail demo, QA-rich: navigate to a real Mailpit inbox (a real,
|
|
15
|
+
* separate web UI - not this app), confirm the email actually arrived,
|
|
16
|
+
* open it, confirm its body says what it's supposed to, read a link
|
|
17
|
+
* straight out of the DOM, then navigate back into the app with it - six
|
|
18
|
+
* atomic Blocks, no mail-catcher REST API anywhere.
|
|
19
|
+
*
|
|
20
|
+
* The four mail-reading Blocks (`assert-email-received`, `open-message`,
|
|
21
|
+
* `assert-email-content`, `extract-email-link`) are reusable across any
|
|
22
|
+
* number of different email scenarios in a project (signup confirmation,
|
|
23
|
+
* password reset, magic link, ...) with zero new Blocks per scenario - only
|
|
24
|
+
* mem varies per run: `ExpectedRecipient` (who), `ExpectedLinkPattern`
|
|
25
|
+
* (which link in the body to follow), `ExpectedEmailContent` (what the body
|
|
26
|
+
* should say). This particular flow wires them to this scaffold's own
|
|
27
|
+
* "verified" landing route (`nav-verification-link`, app-specific by
|
|
28
|
+
* nature - a different scenario would use a different landing Nav) - see
|
|
29
|
+
* tests/mail-verify.spec.ts for two different scenarios driven through the
|
|
30
|
+
* same four mail-reading Blocks.
|
|
31
|
+
*
|
|
32
|
+
* Needs a real Mailpit instance reachable at WAYGRAPH_MAIL_URL (default
|
|
33
|
+
* http://127.0.0.1:8025) with a message already waiting for
|
|
34
|
+
* ExpectedRecipient's email - see tests/mail-verify.spec.ts for how the test
|
|
35
|
+
* drives this end to end (spins up a real Mailpit container, sends a real
|
|
36
|
+
* email, then runs this exact flow) - including the negative cases: no email
|
|
37
|
+
* arriving, or content that doesn't match, each fail loud, naming
|
|
38
|
+
* themselves, not a silent pass.
|
|
39
|
+
*/
|
|
40
|
+
export const mailVerifyFlow = withHighlightFixtures(
|
|
41
|
+
withTitle(
|
|
42
|
+
engine.defineFlow([
|
|
43
|
+
start,
|
|
44
|
+
NavMailpitInboxBlock,
|
|
45
|
+
AssertEmailReceivedBlock,
|
|
46
|
+
OpenMessageBlock,
|
|
47
|
+
AssertEmailContentBlock,
|
|
48
|
+
ExtractEmailLinkBlock,
|
|
49
|
+
NavVerificationLinkBlock,
|
|
50
|
+
end,
|
|
51
|
+
]),
|
|
52
|
+
"Scaffold: Cross-origin mail verification (QA-rich)",
|
|
53
|
+
),
|
|
54
|
+
{
|
|
55
|
+
"assert-email-received": {
|
|
56
|
+
stubAfter: {
|
|
57
|
+
note: {
|
|
58
|
+
selector: MailpitSel.messageRow,
|
|
59
|
+
label: "AC · email arrived",
|
|
60
|
+
detail: "Confirmed before opening anything.",
|
|
61
|
+
tag: "AC",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"assert-email-content": {
|
|
66
|
+
stubAfter: {
|
|
67
|
+
note: {
|
|
68
|
+
selector: MailpitSel.previewIframe,
|
|
69
|
+
label: "AC · email body matches expected copy",
|
|
70
|
+
tag: "AC",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"extract-email-link": {
|
|
75
|
+
stubAfter: {
|
|
76
|
+
note: {
|
|
77
|
+
selector: MailpitSel.previewIframe,
|
|
78
|
+
label: "AC · link found",
|
|
79
|
+
detail: "Read from the message preview iframe.",
|
|
80
|
+
tag: "AC",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
);
|