@owlmeans/test-ui 0.1.18-rc.47 → 0.1.18-rc.49

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 (59) hide show
  1. package/README.md +8 -6
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/testing-ui/SKILL.md +29 -29
  4. package/build/browser/types.d.ts +22 -0
  5. package/build/browser/types.d.ts.map +1 -0
  6. package/build/browser/types.js +2 -0
  7. package/build/browser/types.js.map +1 -0
  8. package/build/browser.d.ts +8 -16
  9. package/build/browser.d.ts.map +1 -1
  10. package/build/browser.js +38 -41
  11. package/build/browser.js.map +1 -1
  12. package/build/consts.local.d.ts +3 -0
  13. package/build/consts.local.d.ts.map +1 -0
  14. package/build/consts.local.js +3 -0
  15. package/build/consts.local.js.map +1 -0
  16. package/build/index.d.ts +5 -0
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +1 -0
  19. package/build/index.js.map +1 -1
  20. package/build/marketing-consent.d.ts +2 -40
  21. package/build/marketing-consent.d.ts.map +1 -1
  22. package/build/marketing-consent.js +3 -106
  23. package/build/marketing-consent.js.map +1 -1
  24. package/build/mount.d.ts +2 -40
  25. package/build/mount.d.ts.map +1 -1
  26. package/build/mount.js +3 -3
  27. package/build/mount.js.map +1 -1
  28. package/build/page/types.d.ts +134 -0
  29. package/build/page/types.d.ts.map +1 -0
  30. package/build/page/types.js +2 -0
  31. package/build/page/types.js.map +1 -0
  32. package/build/page.d.ts +4 -0
  33. package/build/page.d.ts.map +1 -0
  34. package/build/page.js +160 -0
  35. package/build/page.js.map +1 -0
  36. package/build/supervisor/types.d.ts +56 -0
  37. package/build/supervisor/types.d.ts.map +1 -0
  38. package/build/supervisor/types.js +2 -0
  39. package/build/supervisor/types.js.map +1 -0
  40. package/build/supervisor.d.ts +9 -133
  41. package/build/supervisor.d.ts.map +1 -1
  42. package/build/supervisor.js +65 -177
  43. package/build/supervisor.js.map +1 -1
  44. package/build/types.d.ts +40 -0
  45. package/build/types.d.ts.map +1 -0
  46. package/build/types.js +2 -0
  47. package/build/types.js.map +1 -0
  48. package/package.json +10 -10
  49. package/src/browser/types.ts +22 -0
  50. package/src/browser.ts +43 -40
  51. package/src/consts.local.ts +3 -0
  52. package/src/index.ts +5 -0
  53. package/src/marketing-consent.ts +4 -135
  54. package/src/mount.ts +4 -44
  55. package/src/page/types.ts +137 -0
  56. package/src/page.ts +184 -0
  57. package/src/supervisor/types.ts +55 -0
  58. package/src/supervisor.ts +83 -276
  59. package/src/types.ts +41 -0
package/build/mount.d.ts CHANGED
@@ -1,42 +1,4 @@
1
- import type { Page } from 'playwright';
2
- export interface MountOptions {
3
- /**
4
- * URL the harness is reachable at. The consuming package is in charge
5
- * of producing it — typically a Vite dev server pointed at
6
- * `node_modules/@owlmeans/test-ui/harness/` plus a per-package
7
- * `mount.tsx` that registers the components under test, OR an
8
- * inlined `data:text/html,...` URL for the simplest smoke tests.
9
- */
10
- url: string;
11
- /**
12
- * Component name registered in the harness. Appended as `?component=`.
13
- * Omit when the harness mounts a single fixed root.
14
- */
15
- component?: string;
16
- /**
17
- * JSON-serialisable props passed to the component. Encoded into the
18
- * URL as `?props=<json>` for the harness's `mount.tsx` to read.
19
- */
20
- props?: Record<string, unknown>;
21
- /**
22
- * What counts as "arrived". Defaults to `domcontentloaded`.
23
- *
24
- * NOT `load`, which is playwright's own default and is wrong for any page that is a real
25
- * application: `load` waits for EVERY subresource, and an analytics beacon, a long-poll or a
26
- * third-party pixel that never settles holds it open until the navigation times out — with the
27
- * page fully rendered and working the whole time. The failure reads as "the site is down".
28
- *
29
- * A spec waits for the selector it actually needs; that is the assertion, not the load event.
30
- */
31
- waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
32
- /** Navigation timeout in ms. */
33
- timeout?: number;
34
- }
35
- export interface Mounted {
36
- page: Page;
37
- /** Closes the page's browser context — the shared browser stays alive. */
38
- close: () => Promise<void>;
39
- }
1
+ import type { Mounted, MountOptions } from './types.js';
40
2
  /**
41
3
  * Open a fresh browser context, navigate to the harness URL with the
42
4
  * `component` and `props` query parameters set, and return the `Page`
@@ -47,7 +9,7 @@ export interface Mounted {
47
9
  * try { expect(await page.locator('h1').textContent()).toBe('Sign in') }
48
10
  * finally { await close() }
49
11
  *
50
- * `closeBrowser()` from `afterAll` tears down the shared browser at the end of the suite.
12
+ * `browserHelper.closeBrowser()` from `afterAll` tears down the shared browser at the end of the suite.
51
13
  */
52
14
  export declare const mountComponent: (opts: MountOptions) => Promise<Mounted>;
53
15
  //# sourceMappingURL=mount.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAGtC,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAA;IACV,0EAA0E;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAYD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,SAAgB,YAAY,KAAG,OAAO,CAAC,OAAO,CAYxE,CAAA"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAYvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,SAAgB,YAAY,KAAG,OAAO,CAAC,OAAO,CAYxE,CAAA"}
package/build/mount.js CHANGED
@@ -1,4 +1,4 @@
1
- import { launchBrowser } from './browser.js';
1
+ import { browserHelper } from './browser.js';
2
2
  const buildUrl = (opts) => {
3
3
  const params = new URLSearchParams();
4
4
  if (opts.component != null)
@@ -21,10 +21,10 @@ const buildUrl = (opts) => {
21
21
  * try { expect(await page.locator('h1').textContent()).toBe('Sign in') }
22
22
  * finally { await close() }
23
23
  *
24
- * `closeBrowser()` from `afterAll` tears down the shared browser at the end of the suite.
24
+ * `browserHelper.closeBrowser()` from `afterAll` tears down the shared browser at the end of the suite.
25
25
  */
26
26
  export const mountComponent = async (opts) => {
27
- const browser = await launchBrowser();
27
+ const browser = await browserHelper.launchBrowser();
28
28
  const context = await browser.newContext();
29
29
  const page = await context.newPage();
30
30
  await page.goto(buildUrl(opts), {
@@ -1 +1 @@
1
- {"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AA0C5C,MAAM,QAAQ,GAAG,CAAC,IAAkB,EAAU,EAAE;IAC9C,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACnE,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACvE,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;IAC5B,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,GAAG,CAAA;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9C,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,EAAE,CAAA;AACjC,CAAC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,IAAkB,EAAoB,EAAE;IAC3E,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAA;IAC1C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACpC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,kBAAkB;QAC/C,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC,CAAA;IACF,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC;KAC7C,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAG5C,MAAM,QAAQ,GAAG,CAAC,IAAkB,EAAU,EAAE;IAC9C,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACnE,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACvE,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;IAC5B,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,GAAG,CAAA;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9C,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,EAAE,CAAA;AACjC,CAAC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,IAAkB,EAAoB,EAAE;IAC3E,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,CAAA;IACnD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAA;IAC1C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACpC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,kBAAkB;QAC/C,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC,CAAA;IACF,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC;KAC7C,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,134 @@
1
+ export interface AnswerMarketingConsentOptions {
2
+ /**
3
+ * Which choice to save when the step is shown. `'all'` (the default) ticks the "select all"
4
+ * checkbox (every consent, and the Terms row while it is up — see {@link terms} for what is left
5
+ * of that) before saving; `'none'` saves with every item left unchecked, which is a valid,
6
+ * fully-compliant state on its own; `'skip'` clicks "Skip for now" instead of saving anything —
7
+ * only valid when a Terms box is not on screen (nothing calls it there; see {@link terms}).
8
+ */
9
+ accept?: 'none' | 'all' | 'skip';
10
+ /**
11
+ * When a Terms row is on screen (`appendMarketingConsent({ terms: 'step' })`), `'accept'` (the
12
+ * default) ends with it ticked. `'leave'` ends with it unticked, even after a select-all — for a
13
+ * spec that means to drive the blocked state itself; the confirm click below still fires (with `{ force: true }`, since
14
+ * the control is never truly `disabled`), and this helper then EXPECTS the box to still be up
15
+ * afterward rather than treating that as a failure.
16
+ */
17
+ terms?: 'accept' | 'leave';
18
+ timeout?: number;
19
+ }
20
+ /** What {@link PageHelper.acceptConsent} waits for. */
21
+ export interface AcceptConsentOptions {
22
+ /** How long the dialog may take to appear, and then to go away. Defaults to 5s. */
23
+ timeout?: number;
24
+ }
25
+ export interface DispatcherLoginOptions {
26
+ dispatcherPath?: string;
27
+ /** What counts as "arrived" for the navigation. Defaults to `domcontentloaded` — see `MountOptions.waitUntil`. */
28
+ waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
29
+ /**
30
+ * Answer the marketing-consent step if it is shown right after the dispatcher navigates away.
31
+ * Defaults to `'save'` (select-all and save) — see {@link PageHelper.answerMarketingConsent}. Pass
32
+ * `'ignore'` to skip the call entirely, e.g. in a spec that answers the step itself.
33
+ */
34
+ marketingConsent?: 'save' | 'ignore';
35
+ }
36
+ export interface SupervisorFormLoginOptions {
37
+ baseUrl: string;
38
+ /** Target user id / email. */
39
+ userId: string;
40
+ /** Supervisor private key (e.g. `ed25519:<base64>` from `.env.dev.secrets`). */
41
+ pk: string;
42
+ /** Login path; defaults to the standard supervisor route. */
43
+ path?: string;
44
+ /** Predicate/URL fragment that signals a successful landing (default: leaving the login path). */
45
+ expectPath?: string;
46
+ timeout?: number;
47
+ /**
48
+ * What counts as "arrived" for the initial navigation. Defaults to `domcontentloaded`.
49
+ *
50
+ * See the note on `MountOptions.waitUntil`: `load` is playwright's default and is wrong for a
51
+ * real application, because it waits for every subresource — a tag manager, an analytics beacon
52
+ * or any pixel that never settles holds it open until the navigation times out, with the login
53
+ * form rendered and fillable the whole time.
54
+ */
55
+ waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
56
+ /**
57
+ * What to do about a cookie-consent dialog covering the form. Defaults to `accept`, because an
58
+ * OwlMeans app will not start an authentication flow until consent is answered — see
59
+ * {@link PageHelper.acceptConsent}. Pass `ignore` only in a spec that answers the dialog itself.
60
+ */
61
+ consent?: 'accept' | 'ignore';
62
+ /** When set, capture the filled login form (`supervisor-login-form.png`) before submit. */
63
+ screenshotDir?: string;
64
+ /**
65
+ * Answer the marketing-consent step if it is shown right after landing. Defaults to `'save'`
66
+ * (select-all and save) — see {@link PageHelper.answerMarketingConsent}. Pass `'ignore'` to skip the call
67
+ * entirely, e.g. in a spec that answers the step itself.
68
+ */
69
+ marketingConsent?: 'save' | 'ignore';
70
+ }
71
+ /** Driving one page of an OwlMeans app: consent dialogs, the login flows, screenshots. */
72
+ export interface PageHelper {
73
+ /**
74
+ * Answer the marketing-consent full-page step (`@owlmeans/web-marketing-consent`) if it is shown
75
+ * right after login, so a spec driving {@link PageHelper.loginViaSupervisorForm} or
76
+ * {@link PageHelper.loginViaDispatcher} is never blocked behind it.
77
+ *
78
+ * The screen is opt-in per app, and most apps/environments will not have it wired in — that makes
79
+ * detecting its ABSENCE cheaply the whole point. A plain `waitFor` on `[data-marketing-consent]`
80
+ * alone would cost the full `timeout` on every login of every app that never renders it (the same
81
+ * caveat `acceptConsent` documents for the cookie dialog). Instead this races the step's own root
82
+ * against the generic OwlMeans "the app has already landed" / "still on the login screen" markers
83
+ * this helper's own login flows already wait on (`#app-prompt`, `[data-login-method]`) — by the
84
+ * time a login flow calls this, one of those has typically already rendered, so the race resolves
85
+ * as soon as it does: no fixed sleep, and in the common "step absent" case, no material added
86
+ * latency over what the login flow already paid to get here.
87
+ *
88
+ * @returns whether the step was actually shown and answered (including the error/skip fallback);
89
+ * `false` when it never appeared within `timeout`, which is the expected result everywhere
90
+ * the screen is not yet configured or the visitor already has saved consents.
91
+ * @throws if a Terms box could not be confirmed (a `[data-marketing-consent-terms-error]`, or the
92
+ * box is still up with nowhere left to go — Skip is never offered there on purpose).
93
+ */
94
+ answerMarketingConsent: (opts?: AnswerMarketingConsentOptions) => Promise<boolean>;
95
+ /**
96
+ * Inject a pre-generated bearer into an app that uses the standard owlmeans
97
+ * dispatcher: navigate to `/dispatcher?token=...` so the web app authenticates
98
+ * and redirects home natively. Note: apps that override the DISPATCHER route
99
+ * (e.g. to force an external IdP) should use {@link PageHelper.loginViaSupervisorForm}.
100
+ */
101
+ loginViaDispatcher: (baseUrl: string, token: string, opts?: DispatcherLoginOptions) => Promise<void>;
102
+ /**
103
+ * Save a full-page screenshot to `<dir>/<name>.png`, creating `dir` if needed.
104
+ * Returns the absolute file path. Use a host-backed folder (e.g. the project's
105
+ * `tmp/tests/screenshots`) so the file is visible whether the test runs on the
106
+ * host or inside a dev container.
107
+ */
108
+ saveScreenshot: (dir: string, name: string) => Promise<string>;
109
+ /**
110
+ * Answer the cookie-consent dialog if one is up, so the page underneath can be driven.
111
+ *
112
+ * An OwlMeans app asks for consent BEFORE it will start an authentication flow, and the dialog is
113
+ * a modal with no dismissal — by design, since a decision is what the gate is waiting for. For a
114
+ * test that means the login form renders, resolves, reports itself "visible, enabled and stable"
115
+ * and still cannot be clicked, because the overlay intercepts the pointer. Playwright retries for
116
+ * the full timeout and then blames the button, which is the most misleading failure in this
117
+ * helper: nothing is wrong with the form.
118
+ *
119
+ * Accepting is the honest default for a login helper — the flow it is about to drive is exactly
120
+ * what the dialog is gating — but it is best-effort: an app without the widget, or one where the
121
+ * visitor already decided, simply has no dialog and this returns at once.
122
+ *
123
+ * @returns whether a dialog was actually answered.
124
+ */
125
+ acceptConsent: (opts?: AcceptConsentOptions) => Promise<boolean>;
126
+ /**
127
+ * Drive the PK-based supervisor login FORM end-to-end (the faithful path that
128
+ * exercises the real supervisor plugin + registration): navigate to the
129
+ * supervisor login route, fill the user id + private key, submit, and wait for
130
+ * the app to leave the login screen.
131
+ */
132
+ loginViaSupervisorForm: (opts: SupervisorFormLoginOptions) => Promise<void>;
133
+ }
134
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/page/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,6BAA6B;IAC5C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;IAChC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kHAAkH;IAClH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC7B,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAAsB,EAAE,CAAC,IAAI,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpG;;;;;OAKG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D;;;;;;;;;;;;;;;OAeG;IACH,aAAa,EAAE,CAAC,IAAI,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAChE;;;;;OAKG;IACH,sBAAsB,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/page/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { Page } from 'playwright';
2
+ import type { PageHelper } from './page/types.js';
3
+ export declare const makePageHelper: (page: Page) => PageHelper;
4
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EACmE,UAAU,EACxF,MAAM,iBAAiB,CAAA;AAExB,eAAO,MAAM,cAAc,SAAU,IAAI,KAAG,UA+K3C,CAAA"}
package/build/page.js ADDED
@@ -0,0 +1,160 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { DEFAULT_MARKETING_CONSENT_TIMEOUT, DEFAULT_SUPERVISOR_PATH } from './consts.local.js';
4
+ export const makePageHelper = (page) => {
5
+ const answerMarketingConsent = async (opts) => {
6
+ const timeout = opts?.timeout ?? DEFAULT_MARKETING_CONSENT_TIMEOUT;
7
+ const step = page.locator('[data-marketing-consent]');
8
+ try {
9
+ await page.locator('[data-marketing-consent], #app-prompt, [data-login-method]')
10
+ .first().waitFor({ state: 'visible', timeout });
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ if (await step.count() === 0)
16
+ return false;
17
+ // The step's own status read may still be outstanding (`data-state="loading"`) — wait past it,
18
+ // OR notice it already auto-continued (nothing was pending) and detached on its own. An older
19
+ // `web-marketing-consent` that predates `data-state` entirely satisfies the first selector
20
+ // immediately (a missing attribute is never `"loading"`), so this costs nothing there.
21
+ await Promise.race([
22
+ page.waitForSelector('[data-marketing-consent]:not([data-state="loading"])', { timeout }),
23
+ page.waitForSelector('[data-marketing-consent]', { state: 'detached', timeout }),
24
+ ]).catch(() => undefined);
25
+ // Nothing was pending — it already auto-continued while this waited.
26
+ if (await step.count() === 0)
27
+ return true;
28
+ const terms = page.locator('[data-marketing-consent-terms]');
29
+ const hasTerms = await terms.count() > 0;
30
+ if (!hasTerms && opts?.accept === 'skip') {
31
+ await page.locator('[data-marketing-consent-skip]').click();
32
+ await step.waitFor({ state: 'detached', timeout });
33
+ return true;
34
+ }
35
+ if ((opts?.accept ?? 'all') === 'all') {
36
+ // The step draws "Select all" only when there is more than one row to select; a lone consent
37
+ // is ticked itself. Select all speaks for the Terms row too while it is up, which the choice
38
+ // below then settles either way.
39
+ const all = page.locator('[data-marketing-consent-all]');
40
+ if (await all.count() > 0) {
41
+ await all.click();
42
+ }
43
+ else {
44
+ for (const item of await page.locator('[data-marketing-consent-item]').all()) {
45
+ await item.check();
46
+ }
47
+ }
48
+ }
49
+ // 'none' leaves every item unchecked and clicks nothing else before saving.
50
+ if (hasTerms) {
51
+ const wanted = (opts?.terms ?? 'accept') === 'accept';
52
+ if (wanted && !(await terms.isChecked())) {
53
+ await terms.check();
54
+ }
55
+ else if (!wanted && await terms.isChecked()) {
56
+ await terms.uncheck();
57
+ }
58
+ }
59
+ // `{ force: true }`: the confirm is never the native `disabled` — only `aria-disabled` while a
60
+ // Terms box is unticked, or merely styled muted with nothing changed yet — and Playwright's own
61
+ // actionability check honours `aria-disabled`, which this helper deliberately does not.
62
+ await page.locator('[data-marketing-consent-save]').click({ force: true });
63
+ const outcome = await Promise.race([
64
+ step.waitFor({ state: 'detached', timeout }).then(() => 'saved'),
65
+ page.locator('[data-marketing-consent-error]').waitFor({ state: 'visible', timeout })
66
+ .then(() => 'error'),
67
+ page.locator('[data-marketing-consent-terms-error]').waitFor({ state: 'visible', timeout })
68
+ .then(() => 'terms-error'),
69
+ ]).catch(() => 'error');
70
+ if (outcome === 'terms-error') {
71
+ throw new Error('[@owlmeans/test-ui] the marketing-consent step could not confirm the Terms');
72
+ }
73
+ if (outcome !== 'saved') {
74
+ const skip = page.locator('[data-marketing-consent-skip]');
75
+ if (await skip.count() > 0) {
76
+ console.warn('[@owlmeans/test-ui] marketing consent save did not complete; using the skip link instead');
77
+ await skip.click();
78
+ await step.waitFor({ state: 'detached', timeout }).catch(() => undefined);
79
+ }
80
+ else if (await terms.count() > 0) {
81
+ // No Skip is ever offered while the Terms box is up — a failed/blocked confirm here has no
82
+ // fallback, and silently returning would hide a caller stuck behind a real requirement.
83
+ throw new Error('[@owlmeans/test-ui] the marketing-consent step is still showing its Terms box');
84
+ }
85
+ }
86
+ // Answered either way: saved, or skipped past a flaky save. Never leaves the caller blocked.
87
+ return true;
88
+ };
89
+ const loginViaDispatcher = async (baseUrl, token, opts) => {
90
+ const path = opts?.dispatcherPath ?? '/dispatcher';
91
+ const url = new URL(path, baseUrl);
92
+ url.searchParams.set('token', token);
93
+ // `domcontentloaded`, not playwright's `load` default — see the note on `MountOptions.waitUntil`.
94
+ await page.goto(url.toString(), { waitUntil: opts?.waitUntil ?? 'domcontentloaded' });
95
+ // Wait until the dispatcher has navigated away (token consumed).
96
+ await page.waitForURL(u => !u.pathname.startsWith(path), { timeout: 30_000 });
97
+ if (opts?.marketingConsent !== 'ignore') {
98
+ await answerMarketingConsent({ accept: 'all', timeout: 30_000 });
99
+ }
100
+ };
101
+ const saveScreenshot = async (dir, name) => {
102
+ const target = resolve(dir);
103
+ await mkdir(target, { recursive: true });
104
+ const file = join(target, `${name}.png`);
105
+ await page.screenshot({ path: file, fullPage: true });
106
+ return file;
107
+ };
108
+ const acceptConsent = async (opts) => {
109
+ const dialog = page.locator('[data-consent-dialog]');
110
+ try {
111
+ await dialog.waitFor({ state: 'visible', timeout: opts?.timeout ?? 5_000 });
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ // Accept-all rather than essential-only: a test asserting a narrower decision should make that
117
+ // decision itself, and silently choosing the minimum here would hide it from the spec.
118
+ await page.locator('[data-consent-accept-all]').click();
119
+ await dialog.waitFor({ state: 'detached', timeout: opts?.timeout ?? 5_000 });
120
+ return true;
121
+ };
122
+ const loginViaSupervisorForm = async (opts) => {
123
+ const path = opts.path ?? DEFAULT_SUPERVISOR_PATH;
124
+ const timeout = opts.timeout ?? 60_000;
125
+ await page.goto(new URL(path, opts.baseUrl).toString(), {
126
+ waitUntil: opts.waitUntil ?? 'domcontentloaded', timeout
127
+ });
128
+ // Wait for the app to MOUNT before deciding whether there is a dialog to answer. `domcontentloaded`
129
+ // returns while the SPA is still booting, and on a dev server that boot is occasionally tens of
130
+ // seconds — so a short consent probe reports "no dialog", the dialog then mounts over the form,
131
+ // and the fill below waits out its own timeout on a field that is present but covered. Either
132
+ // marker proves the app is up: the dialog, or the form itself when consent was already given.
133
+ await page.locator('[data-consent-dialog], [data-testid="supervisor-user-id"]')
134
+ .first().waitFor({ state: 'visible', timeout });
135
+ if (opts.consent !== 'ignore')
136
+ await acceptConsent({ timeout });
137
+ // The form renders only AFTER the dialog is dismissed, so these carry the caller's budget rather
138
+ // than playwright's 30s default — the whole point of accepting an explicit `timeout` here.
139
+ await page.getByTestId('supervisor-user-id').fill(opts.userId, { timeout });
140
+ await page.getByTestId('supervisor-pk').fill(opts.pk, { timeout });
141
+ if (opts.screenshotDir != null) {
142
+ await saveScreenshot(opts.screenshotDir, 'supervisor-login-form');
143
+ }
144
+ await page.getByTestId('supervisor-submit').click({ timeout });
145
+ if (opts.expectPath != null) {
146
+ await page.waitForURL(url => url.toString().includes(opts.expectPath), { timeout });
147
+ }
148
+ else {
149
+ await page.waitForURL(url => !url.pathname.startsWith(path), { timeout });
150
+ }
151
+ // Carries the CALLER's own budget rather than the helper's smaller internal default — a slow
152
+ // cold environment (vite re-optimizing, a stage pod still warming) can easily spend most of a
153
+ // short fixed timeout just getting here, leaving too little for the two extra POSTs (terms, then
154
+ // items) a Terms-mode step now makes.
155
+ if (opts.marketingConsent !== 'ignore')
156
+ await answerMarketingConsent({ accept: 'all', timeout });
157
+ };
158
+ return { answerMarketingConsent, loginViaDispatcher, saveScreenshot, acceptConsent, loginViaSupervisorForm };
159
+ };
160
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { AuthRole } from '@owlmeans/auth';
2
+ export interface PregenerateAuthTokenOptions {
3
+ /** Target user id / email the token represents. */
4
+ userId: string;
5
+ /**
6
+ * Private key to sign the bearer with, e.g. `ed25519:<base64>` from
7
+ * `.env.dev.secrets`. MUST be the key the target backend trusts as its own
8
+ * signing key (it verifies bearers against `cfg.alias`/`cfg.service`).
9
+ */
10
+ pk: string;
11
+ scopes?: string[];
12
+ role?: AuthRole;
13
+ entityId?: string;
14
+ profileId?: string;
15
+ /** `source` recorded in the Auth payload (usually the backend service alias). */
16
+ source?: string;
17
+ }
18
+ export interface SupervisorApiAuthOptions {
19
+ /** Auth-manager API base URL (the backend serving /authentication/* and /authenticate). */
20
+ apiBaseUrl: string;
21
+ /** Target user id / email to authenticate (and register on first use). */
22
+ userId: string;
23
+ /** Supervisor private key, e.g. `ed25519:<base64>` from `.env.dev.secrets`. */
24
+ pk: string;
25
+ /** Paths, overridable for non-default routing. */
26
+ paths?: {
27
+ init?: string;
28
+ authenticate?: string;
29
+ dispatch?: string;
30
+ };
31
+ fetchImpl?: typeof fetch;
32
+ }
33
+ /** Bearers for a backend, minted without a browser. */
34
+ export interface SupervisorAuthHelper {
35
+ /**
36
+ * Mint a ready-to-use `ED25519-BASIC-TOKEN ...` bearer offline, reusing
37
+ * `@owlmeans/test-auth`'s `makeBearer`. This is the "set a token directly"
38
+ * fast-path for e2e/integration tests: no UI, no live auth round-trip. The
39
+ * backend accepts it because it is signed by the project's own trusted key.
40
+ *
41
+ * For a faithful end-to-end login (real supervisor plugin + registration), drive
42
+ * {@link PageHelper.loginViaSupervisorForm} instead.
43
+ */
44
+ pregenerateAuthToken: (opts: PregenerateAuthTokenOptions) => Promise<string>;
45
+ /**
46
+ * Pregenerate a real bearer by driving the live PK-based supervisor flow over the
47
+ * backend API (no browser): `/authentication/init` -> sign the challenge with the
48
+ * supervisor key -> `/authentication/authenticate` -> `/authenticate` (dispatcher
49
+ * exchange). This is the faithful "set a token directly via API" path
50
+ * (registers the user on first use), independent of any front-end routing.
51
+ *
52
+ * Returns the final `ED25519-BASIC-TOKEN ...` bearer the backend accepts.
53
+ */
54
+ authenticateViaSupervisorApi: (opts: SupervisorApiAuthOptions) => Promise<string>;
55
+ }
56
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/supervisor/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAG9C,MAAM,WAAW,2BAA2B;IAC1C,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,EAAE,EAAE,MAAM,CAAA;IACV,kDAAkD;IAClD,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,oBAAoB,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5E;;;;;;;;OAQG;IACH,4BAA4B,EAAE,CAAC,IAAI,EAAE,wBAAwB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAClF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/supervisor/types.ts"],"names":[],"mappings":""}
@@ -1,138 +1,14 @@
1
1
  import type { Page } from 'playwright';
2
- import type { AuthRole } from '@owlmeans/auth';
3
- export interface PregenerateAuthTokenOptions {
4
- /** Target user id / email the token represents. */
5
- userId: string;
6
- /**
7
- * Private key to sign the bearer with, e.g. `ed25519:<base64>` from
8
- * `.env.dev.secrets`. MUST be the key the target backend trusts as its own
9
- * signing key (it verifies bearers against `cfg.alias`/`cfg.service`).
10
- */
11
- pk: string;
12
- scopes?: string[];
13
- role?: AuthRole;
14
- entityId?: string;
15
- profileId?: string;
16
- /** `source` recorded in the Auth payload (usually the backend service alias). */
17
- source?: string;
18
- }
19
- /**
20
- * Mint a ready-to-use `ED25519-BASIC-TOKEN ...` bearer offline, reusing
21
- * `@owlmeans/test-auth`'s `makeBearer`. This is the "set a token directly"
22
- * fast-path for e2e/integration tests: no UI, no live auth round-trip. The
23
- * backend accepts it because it is signed by the project's own trusted key.
24
- *
25
- * For a faithful end-to-end login (real supervisor plugin + registration), drive
26
- * {@link loginViaSupervisorForm} instead.
27
- */
28
- export declare const pregenerateAuthToken: (opts: PregenerateAuthTokenOptions) => Promise<string>;
29
- /**
30
- * Inject a pre-generated bearer into an app that uses the standard owlmeans
31
- * dispatcher: navigate to `/dispatcher?token=...` so the web app authenticates
32
- * and redirects home natively. Note: apps that override the DISPATCHER route
33
- * (e.g. to force an external IdP) should use {@link loginViaSupervisorForm}.
34
- */
35
- export declare const loginViaDispatcher: (page: Page, baseUrl: string, token: string, opts?: {
36
- dispatcherPath?: string;
37
- waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
38
- /**
39
- * Answer the marketing-consent step if it is shown right after the dispatcher navigates away.
40
- * Defaults to `'save'` (select-all and save) — see {@link answerMarketingConsent}. Pass
41
- * `'ignore'` to skip the call entirely, e.g. in a spec that answers the step itself.
42
- */
43
- marketingConsent?: 'save' | 'ignore';
44
- }) => Promise<void>;
45
- export interface SupervisorApiAuthOptions {
46
- /** Auth-manager API base URL (the backend serving /authentication/* and /authenticate). */
47
- apiBaseUrl: string;
48
- /** Target user id / email to authenticate (and register on first use). */
49
- userId: string;
50
- /** Supervisor private key, e.g. `ed25519:<base64>` from `.env.dev.secrets`. */
51
- pk: string;
52
- /** Paths, overridable for non-default routing. */
53
- paths?: {
54
- init?: string;
55
- authenticate?: string;
56
- dispatch?: string;
57
- };
58
- fetchImpl?: typeof fetch;
59
- }
60
- /**
61
- * Pregenerate a real bearer by driving the live PK-based supervisor flow over the
62
- * backend API (no browser): `/authentication/init` -> sign the challenge with the
63
- * supervisor key -> `/authentication/authenticate` -> `/authenticate` (dispatcher
64
- * exchange). This is the faithful "set a token directly via API" path
65
- * (registers the user on first use), independent of any front-end routing.
66
- *
67
- * Returns the final `ED25519-BASIC-TOKEN ...` bearer the backend accepts.
68
- */
2
+ import type { AcceptConsentOptions, SupervisorFormLoginOptions } from './page/types.js';
3
+ import type { SupervisorApiAuthOptions, SupervisorAuthHelper } from './supervisor/types.js';
4
+ export declare const createSupervisorAuthHelper: () => SupervisorAuthHelper;
5
+ export declare const supervisorAuthHelper: SupervisorAuthHelper;
6
+ /** @deprecated compat:factory-refactor — use `supervisorAuthHelper.authenticateViaSupervisorApi(…)` */
69
7
  export declare const authenticateViaSupervisorApi: (opts: SupervisorApiAuthOptions) => Promise<string>;
70
- /**
71
- * Save a full-page screenshot to `<dir>/<name>.png`, creating `dir` if needed.
72
- * Returns the absolute file path. Use a host-backed folder (e.g. the project's
73
- * `tmp/tests/screenshots`) so the file is visible whether the test runs on the
74
- * host or inside a dev container.
75
- */
8
+ /** @deprecated compat:factory-refactor — use `makePageHelper(page).saveScreenshot(…)` */
76
9
  export declare const saveScreenshot: (page: Page, dir: string, name: string) => Promise<string>;
77
- export interface SupervisorFormLoginOptions {
78
- baseUrl: string;
79
- /** Target user id / email. */
80
- userId: string;
81
- /** Supervisor private key (e.g. `ed25519:<base64>` from `.env.dev.secrets`). */
82
- pk: string;
83
- /** Login path; defaults to the standard supervisor route. */
84
- path?: string;
85
- /** Predicate/URL fragment that signals a successful landing (default: leaving the login path). */
86
- expectPath?: string;
87
- timeout?: number;
88
- /**
89
- * What counts as "arrived" for the initial navigation. Defaults to `domcontentloaded`.
90
- *
91
- * See the note on `MountOptions.waitUntil`: `load` is playwright's default and is wrong for a
92
- * real application, because it waits for every subresource — a tag manager, an analytics beacon
93
- * or any pixel that never settles holds it open until the navigation times out, with the login
94
- * form rendered and fillable the whole time.
95
- */
96
- waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
97
- /**
98
- * What to do about a cookie-consent dialog covering the form. Defaults to `accept`, because an
99
- * OwlMeans app will not start an authentication flow until consent is answered — see
100
- * {@link acceptConsent}. Pass `ignore` only in a spec that answers the dialog itself.
101
- */
102
- consent?: 'accept' | 'ignore';
103
- /** When set, capture the filled login form (`supervisor-login-form.png`) before submit. */
104
- screenshotDir?: string;
105
- /**
106
- * Answer the marketing-consent step if it is shown right after landing. Defaults to `'save'`
107
- * (select-all and save) — see {@link answerMarketingConsent}. Pass `'ignore'` to skip the call
108
- * entirely, e.g. in a spec that answers the step itself.
109
- */
110
- marketingConsent?: 'save' | 'ignore';
111
- }
112
- /**
113
- * Answer the cookie-consent dialog if one is up, so the page underneath can be driven.
114
- *
115
- * An OwlMeans app asks for consent BEFORE it will start an authentication flow, and the dialog is
116
- * a modal with no dismissal — by design, since a decision is what the gate is waiting for. For a
117
- * test that means the login form renders, resolves, reports itself "visible, enabled and stable"
118
- * and still cannot be clicked, because the overlay intercepts the pointer. Playwright retries for
119
- * the full timeout and then blames the button, which is the most misleading failure in this file:
120
- * nothing is wrong with the form.
121
- *
122
- * Accepting is the honest default for a login helper — the flow it is about to drive is exactly
123
- * what the dialog is gating — but it is best-effort: an app without the widget, or one where the
124
- * visitor already decided, simply has no dialog and this returns at once.
125
- *
126
- * @returns whether a dialog was actually answered.
127
- */
128
- export declare const acceptConsent: (page: Page, opts?: {
129
- timeout?: number;
130
- }) => Promise<boolean>;
131
- /**
132
- * Drive the PK-based supervisor login FORM end-to-end (the faithful path that
133
- * exercises the real supervisor plugin + registration): navigate to the
134
- * supervisor login route, fill the user id + private key, submit, and wait for
135
- * the app to leave the login screen.
136
- */
10
+ /** @deprecated compat:factory-refactor — use `makePageHelper(page).acceptConsent(…)` */
11
+ export declare const acceptConsent: (page: Page, opts?: AcceptConsentOptions) => Promise<boolean>;
12
+ /** @deprecated compat:factory-refactor — use `makePageHelper(page).loginViaSupervisorForm(…)` */
137
13
  export declare const loginViaSupervisorForm: (page: Page, opts: SupervisorFormLoginOptions) => Promise<void>;
138
14
  //# sourceMappingURL=supervisor.d.ts.map