waygraph 0.15.2 → 0.15.3

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.
Files changed (25) hide show
  1. package/dist/cli.js +6 -1
  2. package/dist/engine.d.ts +124 -12
  3. package/dist/engine.js +170 -13
  4. package/dist/highlights.d.ts +8 -0
  5. package/dist/highlights.js +2 -0
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +1 -1
  8. package/dist/types.d.ts +17 -0
  9. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -7
  10. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +8 -10
  11. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +11 -10
  12. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +5 -4
  13. package/examples/saucedemo/src/flows/checkout.flow.ts +61 -97
  14. package/examples/saucedemo/src/flows/login.flow.ts +11 -2
  15. package/examples/saucedemo/src/flows/shop.flow.ts +13 -13
  16. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +9 -2
  17. package/package.json +1 -1
  18. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -7
  19. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +8 -10
  20. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +11 -10
  21. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +5 -4
  22. package/templates/quickstart/src/flows/checkout.flow.ts +61 -97
  23. package/templates/quickstart/src/flows/login.flow.ts +11 -2
  24. package/templates/quickstart/src/flows/shop.flow.ts +13 -13
  25. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +9 -2
@@ -1,4 +1,4 @@
1
- import { defineMethodBlock, checkpoint, Trait, narrate } from "waygraph";
1
+ import { defineMethodBlock, checkpoint, Trait, narrate, type StubCtx } from "waygraph";
2
2
  import type { CheckoutOverviewPage, OrderComplete } from "../../../../states/checkout.states.js";
3
3
  import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
4
4
 
@@ -8,6 +8,7 @@ import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
8
8
  * Route: saucedemo-web/checkout-step-two/methods/ (= /checkout-step-two.html)
9
9
  *
10
10
  * Places the order on the overview page - form/finish click, not a NavBlock.
11
+ * Stubs use open ctx lifecycles (preferred) - not static slot maps.
11
12
  */
12
13
  export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderComplete>({
13
14
  name: "finish-order",
@@ -19,23 +20,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
19
20
  },
20
21
  resolve: () => checkpoint("OrderComplete"),
21
22
  verify: [Trait.text(CheckoutOverviewSel.completeHeader, "Thank you for your order!")],
22
- stubBefore: {
23
- finish: {
23
+ stubBefore: (ctx: StubCtx) => {
24
+ ctx.ring("finish", {
24
25
  selector: CheckoutOverviewSel.finishBtn,
25
26
  label: "Finish",
26
27
  detail: "tone: warning (yellow) - place order",
27
28
  tone: "warning",
28
29
  weight: "bold",
29
- },
30
+ });
30
31
  },
31
- stubAfter: {
32
- thanks: {
32
+ stubAfter: (ctx: StubCtx) => {
33
+ ctx.ring("thanks", {
33
34
  selector: CheckoutOverviewSel.completeHeader,
34
35
  label: "Order confirmed",
35
36
  detail: "tone: success (green) - thank you!",
36
37
  tone: "success",
37
38
  duration: true,
38
- },
39
+ });
39
40
  },
40
41
  // Shown AFTER act (order already placed) - selectors must match the
41
42
  // completion page, not #finish on the overview.
@@ -22,16 +22,14 @@ export const FillPasswordBlock = defineMethodBlock<LoginPage, LoginPage>({
22
22
  },
23
23
  resolve: () => checkpoint("LoginPage"),
24
24
  stubBefore: (ctx: StubCtx) => {
25
- ctx.highlights({
26
- password: {
27
- selector: LoginSel.password,
28
- label: "Password",
29
- detail: "tone: planned (purple)",
30
- todo: "login-pass",
31
- tone: "planned",
32
- focus: true,
33
- zoom: 1.4,
34
- },
25
+ ctx.ring("password", {
26
+ selector: LoginSel.password,
27
+ label: "Password",
28
+ detail: "tone: planned (purple)",
29
+ todo: "login-pass",
30
+ tone: "planned",
31
+ focus: true,
32
+ zoom: 1.4,
35
33
  });
36
34
  },
37
35
  stubAfter: (ctx: StubCtx) => {
@@ -40,16 +40,17 @@ export const FillUsernameBlock = defineMethodBlock<LoginPage, LoginPage>({
40
40
  { title: "Device showcase", index: 0 },
41
41
  );
42
42
  ctx.zoom(1.35);
43
- ctx.highlights({
44
- username: {
45
- selector: LoginSel.username,
46
- label: "Username",
47
- detail: "tone: info (blue)",
48
- todo: "login-user",
49
- tone: "info",
50
- focus: true,
51
- zoom: 1.4,
52
- },
43
+ const fromMem = ctx.mem?.get(LoginCreds.key)?.username;
44
+ ctx.ring("username", {
45
+ selector: LoginSel.username,
46
+ label: "Username",
47
+ detail: fromMem
48
+ ? `tone: info (blue) - mem ${fromMem}`
49
+ : "tone: info (blue)",
50
+ tone: "info",
51
+ todo: "login-user",
52
+ focus: true,
53
+ zoom: 1.4,
53
54
  });
54
55
  },
55
56
  stubAfter: (ctx: StubCtx) => {
@@ -1,4 +1,4 @@
1
- import type { DefinedBlock } from "waygraph";
1
+ import type { MethodBlock } from "waygraph";
2
2
  import type { LoginPage, LoggedIn } from "../../../states/checkout.states.js";
3
3
  import { SubmitLoginBlock } from "./submit-login.method.block.js";
4
4
 
@@ -7,7 +7,8 @@ import { SubmitLoginBlock } from "./submit-login.method.block.js";
7
7
  * Helper: typed alias over SubmitLoginBlock
8
8
  * Route: saucedemo-web/methods/ (page URL /)
9
9
  *
10
- * DefinedBlock cast so linear Flows can chain .withVerify on Out=LoggedIn;
11
- * runtime still branches on bad credentials via SubmitLoginBlock.
10
+ * MethodBlock cast so map().method() keeps the factory brand while Flows can
11
+ * treat Out as LoggedIn; runtime still branches on bad credentials via
12
+ * SubmitLoginBlock.
12
13
  */
13
- export const SubmitLoginForFlow = SubmitLoginBlock as unknown as DefinedBlock<LoginPage, LoggedIn>;
14
+ export const SubmitLoginForFlow = SubmitLoginBlock as unknown as MethodBlock<LoginPage, LoggedIn>;
@@ -1,13 +1,8 @@
1
- import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
2
- import type {
3
- LoggedIn,
4
- ItemInCart,
5
- CartPage,
6
- CheckoutInfoPage,
7
- CheckoutOverviewPage,
8
- OrderComplete,
9
- } from "../states/checkout.states.js";
10
- import { FfOwnerAuthBlock } from "../blocks/saucedemo-web/ff-owner-auth.block.js";
1
+ import { Engine, withTitle } from "waygraph";
2
+ import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
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 { SubmitLoginForFlow } from "../blocks/saucedemo-web/methods/submit-login-for-flow.js";
11
6
  import { AddToCartBlock } from "../blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.js";
12
7
  import { NavCartBlock } from "../blocks/saucedemo-web/cart/nav-cart.block.js";
13
8
  import { NavCheckoutInfoBlock } from "../blocks/saucedemo-web/cart/nav-checkout-info.block.js";
@@ -16,98 +11,67 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
16
11
  import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
17
12
  import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
18
13
  import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
14
+ import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
19
15
 
20
16
  const engine = new Engine({ headless: false, slowMo: 250 });
21
17
 
22
- // Atomic nav/action per real URL after auth - the shape `waygraph auto` discovers.
23
- // Auth is one FFCompose step (ff-owner-auth) so demo pacing skips login yap;
24
- // use --ff-expand (or loginFlow) when you need per-step auth narration.
25
- //
26
- // Demo narration showcase (waygraph 0.11+ / FFCompose 0.12):
27
- // - FF opaque: one gate for auth; stubs on interesting post-login blocks
28
- // - Block stubs: add-to-cart / finish-order stubAfter
29
- // - Flow fixtures: ticket-style captions
30
- // - Slides on finish-order: multi-step yap with Next between slides
31
- export const checkoutFlow = withHighlightFixtures(
32
- withTitle(
33
- engine.defineFlow<
34
- LoggedIn,
35
- ItemInCart,
36
- CartPage,
37
- CheckoutInfoPage,
38
- CheckoutInfoPage,
39
- CheckoutInfoPage,
40
- CheckoutInfoPage,
41
- CheckoutOverviewPage,
42
- OrderComplete
43
- >([
44
- start,
45
- FfOwnerAuthBlock,
46
- AddToCartBlock,
47
- NavCartBlock,
48
- NavCheckoutInfoBlock,
49
- FillFirstNameBlock,
50
- FillLastNameBlock,
51
- FillPostalCodeBlock,
52
- SubmitCheckoutInfoBlock,
53
- FinishOrderBlock,
54
- end,
55
- ]),
56
- "Owner: Full Checkout",
57
- ),
58
- {
59
- "ff-owner-auth": {
60
- stubAfter: {
61
- inventory: {
62
- label: "Signed in (fast-forward)",
63
- detail: "Auth collapsed via FFCompose - interesting work starts here.",
64
- tag: "AC",
65
- duration: true,
66
- },
67
- },
68
- },
69
- // Kept for --ff-expand (inners become separate steps again: nav-login,
70
- // fill-username, fill-password, submit-login).
71
- "fill-username": {
72
- stubBefore: {
73
- username: {
18
+ /**
19
+ * Owner checkout via map() + .ffStart/.ffEnd (no separate FfOwnerAuthBlock,
20
+ * no HighlightFixtureMap). Auth collapses to one blitz step; post-login
21
+ * blocks keep their own ctx stubBefore/stubAfter. Call-site decorate example
22
+ * on fill-username when --ff-expand shows inners again.
23
+ */
24
+ export const checkoutFlow = withTitle(
25
+ engine
26
+ .map()
27
+ .start()
28
+ .ffStart("ff-owner-auth")
29
+ .gotoPage(NavLoginBlock)
30
+ .method(
31
+ FillUsernameBlock.stubBefore((ctx) => {
32
+ ctx.ring("username", {
33
+ selector: LoginSel.username,
74
34
  label: "Demo user",
75
- detail: "standard_user from Mem - library block stays neutral.",
35
+ detail: "standard_user from Mem - library block stays neutral underneath",
76
36
  tag: "AC",
77
37
  duration: true,
78
- },
79
- },
80
- },
81
- "fill-password": {
82
- stubBefore: {
83
- password: { label: "Password", detail: "Filled from Mem credentials.", duration: 1500 },
84
- },
85
- },
86
- "submit-login": {
87
- stubBefore: {
88
- submit: { label: "Sign in", tag: "GATE", duration: true, fastMode: 500 },
89
- },
90
- },
91
- "add-to-cart": {
92
- stubAfter: {
93
- badge: {
94
- label: "Cart now has the item",
95
- detail: "Badge updates after Add to cart.",
96
- tag: "AC",
97
- duration: true,
98
- },
99
- },
100
- },
101
- "finish-order": {
102
- stubAfter: {
103
- thanks: {
104
- label: "Order placed",
105
- detail: "Completion header is the durable proof.",
38
+ focus: true,
39
+ });
40
+ }),
41
+ )
42
+ .method(
43
+ FillPasswordBlock.stubBefore((ctx) => {
44
+ ctx.ring("password", {
45
+ selector: LoginSel.password,
46
+ label: "Password",
47
+ detail: "Filled from Mem credentials.",
48
+ duration: 1500,
49
+ focus: true,
50
+ });
51
+ }),
52
+ )
53
+ .method(
54
+ SubmitLoginForFlow.stubBefore((ctx) => {
55
+ ctx.ring("submit", {
56
+ selector: LoginSel.loginButton,
57
+ label: "Sign in",
106
58
  tag: "GATE",
107
- duration: 2500,
108
- fastMode: 700,
109
- },
110
- },
111
- },
112
- },
59
+ duration: true,
60
+ fastMode: 500,
61
+ focus: true,
62
+ weight: "bold",
63
+ });
64
+ }),
65
+ )
66
+ .ffEnd()
67
+ .method(AddToCartBlock)
68
+ .gotoPage(NavCartBlock)
69
+ .gotoPage(NavCheckoutInfoBlock)
70
+ .method(FillFirstNameBlock)
71
+ .method(FillLastNameBlock)
72
+ .method(FillPostalCodeBlock)
73
+ .method(SubmitCheckoutInfoBlock)
74
+ .method(FinishOrderBlock)
75
+ .end(),
76
+ "Owner: Full Checkout",
113
77
  );
@@ -1,4 +1,4 @@
1
- import { Engine, start, end, withTitle } from "waygraph";
1
+ import { Engine, withTitle } from "waygraph";
2
2
  import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
3
3
  import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
4
4
  import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
@@ -8,7 +8,16 @@ const engine = new Engine();
8
8
 
9
9
  // The step-mode overlay prepends "Episode N: " itself - the title here is
10
10
  // just the label, not the full heading.
11
+ // map() + ctx stubs on the Method blocks (see fill-username / fill-password /
12
+ // submit-login) - no HighlightFixtureMap.
11
13
  export const loginFlow = withTitle(
12
- engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginForFlow, end]),
14
+ engine
15
+ .map()
16
+ .start()
17
+ .gotoPage(NavLoginBlock)
18
+ .method(FillUsernameBlock)
19
+ .method(FillPasswordBlock)
20
+ .method(SubmitLoginForFlow)
21
+ .end(),
13
22
  "Sign In",
14
23
  );
@@ -1,4 +1,4 @@
1
- import { Engine, start, end, withTitle } from "waygraph";
1
+ import { Engine, withTitle } from "waygraph";
2
2
  import { AddToCartBlock } from "../blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.js";
3
3
  import { NavCartBlock } from "../blocks/saucedemo-web/cart/nav-cart.block.js";
4
4
  import { NavCheckoutInfoBlock } from "../blocks/saucedemo-web/cart/nav-checkout-info.block.js";
@@ -13,17 +13,17 @@ const engine = new Engine();
13
13
  // Episode-2 half of chainFlow(loginFlow, shopFlow) - assumes already LoggedIn
14
14
  // (AddToCartBlock's In is the Checkpoint<string> wildcard).
15
15
  export const shopFlow = withTitle(
16
- engine.defineFlow([
17
- start,
18
- AddToCartBlock,
19
- NavCartBlock,
20
- NavCheckoutInfoBlock,
21
- FillFirstNameBlock,
22
- FillLastNameBlock,
23
- FillPostalCodeBlock,
24
- SubmitCheckoutInfoBlock,
25
- FinishOrderBlock,
26
- end,
27
- ]),
16
+ engine
17
+ .map()
18
+ .start()
19
+ .method(AddToCartBlock)
20
+ .gotoPage(NavCartBlock)
21
+ .gotoPage(NavCheckoutInfoBlock)
22
+ .method(FillFirstNameBlock)
23
+ .method(FillLastNameBlock)
24
+ .method(FillPostalCodeBlock)
25
+ .method(SubmitCheckoutInfoBlock)
26
+ .method(FinishOrderBlock)
27
+ .end(),
28
28
  "Shop & Checkout",
29
29
  );
@@ -1,4 +1,4 @@
1
- import { Engine, start, end, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
1
+ import { Engine, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
2
2
  import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
3
3
  import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
4
4
  import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
@@ -25,7 +25,14 @@ const engine = new Engine();
25
25
  export const viewerBlockedFlow = withExpectedFailure(
26
26
  withTitle(
27
27
  withSessionReset(
28
- engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginBlock, end]),
28
+ engine
29
+ .map()
30
+ .start()
31
+ .gotoPage(NavLoginBlock)
32
+ .method(FillUsernameBlock)
33
+ .method(FillPasswordBlock)
34
+ .method(SubmitLoginBlock)
35
+ .end(),
29
36
  ),
30
37
  "Viewer: Blocked Login Attempt",
31
38
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- import { defineMethodBlock, checkpoint, Trait, narrate } from "waygraph";
1
+ import { defineMethodBlock, checkpoint, Trait, narrate, type StubCtx } from "waygraph";
2
2
  import type { CheckoutOverviewPage, OrderComplete } from "../../../../states/checkout.states.js";
3
3
  import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
4
4
 
@@ -8,6 +8,7 @@ import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
8
8
  * Route: saucedemo-web/checkout-step-two/methods/ (= /checkout-step-two.html)
9
9
  *
10
10
  * Places the order on the overview page - form/finish click, not a NavBlock.
11
+ * Stubs use open ctx lifecycles (preferred) - not static slot maps.
11
12
  */
12
13
  export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderComplete>({
13
14
  name: "finish-order",
@@ -19,23 +20,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
19
20
  },
20
21
  resolve: () => checkpoint("OrderComplete"),
21
22
  verify: [Trait.text(CheckoutOverviewSel.completeHeader, "Thank you for your order!")],
22
- stubBefore: {
23
- finish: {
23
+ stubBefore: (ctx: StubCtx) => {
24
+ ctx.ring("finish", {
24
25
  selector: CheckoutOverviewSel.finishBtn,
25
26
  label: "Finish",
26
27
  detail: "tone: warning (yellow) - place order",
27
28
  tone: "warning",
28
29
  weight: "bold",
29
- },
30
+ });
30
31
  },
31
- stubAfter: {
32
- thanks: {
32
+ stubAfter: (ctx: StubCtx) => {
33
+ ctx.ring("thanks", {
33
34
  selector: CheckoutOverviewSel.completeHeader,
34
35
  label: "Order confirmed",
35
36
  detail: "tone: success (green) - thank you!",
36
37
  tone: "success",
37
38
  duration: true,
38
- },
39
+ });
39
40
  },
40
41
  // Shown AFTER act (order already placed) - selectors must match the
41
42
  // completion page, not #finish on the overview.
@@ -22,16 +22,14 @@ export const FillPasswordBlock = defineMethodBlock<LoginPage, LoginPage>({
22
22
  },
23
23
  resolve: () => checkpoint("LoginPage"),
24
24
  stubBefore: (ctx: StubCtx) => {
25
- ctx.highlights({
26
- password: {
27
- selector: LoginSel.password,
28
- label: "Password",
29
- detail: "tone: planned (purple)",
30
- todo: "login-pass",
31
- tone: "planned",
32
- focus: true,
33
- zoom: 1.4,
34
- },
25
+ ctx.ring("password", {
26
+ selector: LoginSel.password,
27
+ label: "Password",
28
+ detail: "tone: planned (purple)",
29
+ todo: "login-pass",
30
+ tone: "planned",
31
+ focus: true,
32
+ zoom: 1.4,
35
33
  });
36
34
  },
37
35
  stubAfter: (ctx: StubCtx) => {
@@ -40,16 +40,17 @@ export const FillUsernameBlock = defineMethodBlock<LoginPage, LoginPage>({
40
40
  { title: "Device showcase", index: 0 },
41
41
  );
42
42
  ctx.zoom(1.35);
43
- ctx.highlights({
44
- username: {
45
- selector: LoginSel.username,
46
- label: "Username",
47
- detail: "tone: info (blue)",
48
- todo: "login-user",
49
- tone: "info",
50
- focus: true,
51
- zoom: 1.4,
52
- },
43
+ const fromMem = ctx.mem?.get(LoginCreds.key)?.username;
44
+ ctx.ring("username", {
45
+ selector: LoginSel.username,
46
+ label: "Username",
47
+ detail: fromMem
48
+ ? `tone: info (blue) - mem ${fromMem}`
49
+ : "tone: info (blue)",
50
+ tone: "info",
51
+ todo: "login-user",
52
+ focus: true,
53
+ zoom: 1.4,
53
54
  });
54
55
  },
55
56
  stubAfter: (ctx: StubCtx) => {
@@ -1,4 +1,4 @@
1
- import type { DefinedBlock } from "waygraph";
1
+ import type { MethodBlock } from "waygraph";
2
2
  import type { LoginPage, LoggedIn } from "../../../states/checkout.states.js";
3
3
  import { SubmitLoginBlock } from "./submit-login.method.block.js";
4
4
 
@@ -7,7 +7,8 @@ import { SubmitLoginBlock } from "./submit-login.method.block.js";
7
7
  * Helper: typed alias over SubmitLoginBlock
8
8
  * Route: saucedemo-web/methods/ (page URL /)
9
9
  *
10
- * DefinedBlock cast so linear Flows can chain .withVerify on Out=LoggedIn;
11
- * runtime still branches on bad credentials via SubmitLoginBlock.
10
+ * MethodBlock cast so map().method() keeps the factory brand while Flows can
11
+ * treat Out as LoggedIn; runtime still branches on bad credentials via
12
+ * SubmitLoginBlock.
12
13
  */
13
- export const SubmitLoginForFlow = SubmitLoginBlock as unknown as DefinedBlock<LoginPage, LoggedIn>;
14
+ export const SubmitLoginForFlow = SubmitLoginBlock as unknown as MethodBlock<LoginPage, LoggedIn>;