@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/README.md CHANGED
@@ -6,19 +6,21 @@ These are **not** end-to-end UI tests — they exercise individual components in
6
6
 
7
7
  ## Exports
8
8
 
9
- - `launchBrowser(opts?)` — return the shared chromium instance for this `bun test` process. Idempotent.
10
- - `closeBrowser()` — tear it down. Call from `afterAll`.
11
- - `withPage(fn)` — convenience: lease a fresh context+page for the duration of `fn`, then dispose the context.
9
+ - `browserHelper.launchBrowser(opts?)` — return the shared chromium instance for this `bun test` process. Idempotent.
10
+ - `browserHelper.closeBrowser()` — tear it down. Call from `afterAll`.
11
+ - `browserHelper.withPage(fn)` — convenience: lease a fresh context+page for the duration of `fn`, then dispose the context.
12
12
  - `mountComponent({ url, component?, props? })` — opens a fresh context, navigates to a harness URL with the component / props encoded, returns `{ page, close }` so the spec can assert against `page.locator(...)` and dispose the context when done.
13
+ - `makePageHelper(page)` — drives one page of an OwlMeans app: `.acceptConsent(opts?)`, `.answerMarketingConsent(opts?)`, `.loginViaDispatcher(baseUrl, token, opts?)`, `.loginViaSupervisorForm(opts)`, `.saveScreenshot(dir, name)`.
14
+ - `supervisorAuthHelper` — bearers without a browser: `.pregenerateAuthToken(opts)` (signed offline) and `.authenticateViaSupervisorApi(opts)` (the live supervisor flow over the API).
13
15
  - `Browser`, `BrowserContext`, `Page`, `Locator` — re-exports of the Playwright types so consumers don't need a direct `playwright` import.
14
16
 
15
17
  ## Spec shape
16
18
 
17
19
  ```ts
18
20
  import { afterAll, describe, expect, test } from 'bun:test'
19
- import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
21
+ import { browserHelper, mountComponent } from '@owlmeans/test-ui'
20
22
 
21
- afterAll(async () => { await closeBrowser() })
23
+ afterAll(async () => { await browserHelper.closeBrowser() })
22
24
 
23
25
  describe('LoginForm — acceptance', () => {
24
26
  test('renders the heading from the SKILL example', async () => {
@@ -58,7 +60,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
58
60
  your project's skill store (`.agents/skills/`):
59
61
 
60
62
  ```sh
61
- npx @owlmeans/agent-skills@^0.1.18-rc.47
63
+ npx @owlmeans/agent-skills@^0.1.18-rc.49
62
64
  ```
63
65
 
64
66
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/test-ui",
4
- "version": "0.1.18-rc.47",
5
- "generatedAt": "2026-10-03T20:05:07.584Z",
4
+ "version": "0.1.18-rc.49",
5
+ "generatedAt": "2026-10-05T21:03:06.365Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -6,7 +6,7 @@ description: Category-D component-level acceptance tests for OwlMeans Common UI
6
6
 
7
7
  # UI Acceptance Tests — Category D (bun test + Playwright as a library)
8
8
 
9
- **Install:** `"@owlmeans/test-ui": "^0.1.18-rc.47"` in `devDependencies`
9
+ **Install:** `"@owlmeans/test-ui": "^0.1.18-rc.49"` in `devDependencies`
10
10
 
11
11
  `@owlmeans/test-ui` depends on `playwright`, so the browser library arrives with it; add
12
12
  `playwright` to `devDependencies` as well when a spec imports a launcher itself. The Vite harness
@@ -52,13 +52,13 @@ Downloads the chromium binary the `playwright` library drives. CI installs it in
52
52
 
53
53
  | Helper | Purpose |
54
54
  |---|---|
55
- | `launchBrowser(opts?)` | Return the shared chromium for this `bun test` process. Idempotent. |
56
- | `closeBrowser()` | Tear it down. Call from `afterAll`. |
57
- | `withPage(fn)` | Lease a fresh context+page for the duration of `fn`, dispose context on completion. |
55
+ | `browserHelper.launchBrowser(opts?)` | Return the shared chromium for this `bun test` process. Idempotent. |
56
+ | `browserHelper.closeBrowser()` | Tear it down. Call from `afterAll`. |
57
+ | `browserHelper.withPage(fn)` | Lease a fresh context+page for the duration of `fn`, dispose context on completion. |
58
58
  | `mountComponent(opts)` | Open a fresh context, navigate to `opts.url`, return `Mounted` = `{ page, close }`. `component` and `props`, when given, are appended as `?component=` and `?props=`; omit them when the harness mounts a fixed root, which is what every harness here does. |
59
59
  | `MountOptions` | `{ url, component?, props?, waitUntil?, timeout? }` — `waitUntil` defaults to `domcontentloaded`, see below. |
60
- | `acceptConsent(page, { timeout? })` | Wait up to `timeout` (default 5s) for `[data-consent-dialog]`, accept all, wait for it to detach. Returns whether it answered one — and costs that whole wait when it does not. |
61
- | `saveScreenshot(page, dir, name)` | Full-page PNG to `<dir>/<name>.png`, creating `dir`. Returns the absolute path. |
60
+ | `makePageHelper(page).acceptConsent({ timeout? })` | Wait up to `timeout` (default 5s) for `[data-consent-dialog]`, accept all, wait for it to detach. Returns whether it answered one — and costs that whole wait when it does not. |
61
+ | `makePageHelper(page).saveScreenshot(dir, name)` | Full-page PNG to `<dir>/<name>.png`, creating `dir`. Returns the absolute path. |
62
62
  | Re-exports: `Browser`, `BrowserContext`, `Page`, `Locator` | Playwright types — no direct `playwright` import needed. |
63
63
 
64
64
  ### Authenticated specs
@@ -68,11 +68,11 @@ sits behind auth without hand-rolling a token:
68
68
 
69
69
  | Helper | Purpose |
70
70
  |---|---|
71
- | `pregenerateAuthToken(opts)` | Mint an `ED25519-BASIC-TOKEN …` bearer offline from a trusted private key — no browser, no round trip. Options: `{ userId, pk, scopes?, role?, entityId?, profileId?, source? }`. |
72
- | `authenticateViaSupervisorApi(opts)` | Drive the live PK supervisor flow over the backend API (init → sign → authenticate → dispatch), registering the user on first use. Options: `{ apiBaseUrl, userId, pk, paths?, fetchImpl? }`. |
73
- | `loginViaDispatcher(page, baseUrl, token, opts?)` | Inject a bearer through the standard `/dispatcher?token=…` route, wait for the app to navigate away, then answer marketing consent (see below). Options: `{ dispatcherPath?, waitUntil?, marketingConsent? }`. |
74
- | `loginViaSupervisorForm(page, opts)` | Drive the real login form end-to-end — navigate, answer consent, fill user id + key, submit, wait for the landing, then answer marketing consent (see below). Options: `{ baseUrl, userId, pk, path?, expectPath?, timeout?, waitUntil?, consent?, screenshotDir?, marketingConsent? }`. |
75
- | `answerMarketingConsent(page, opts?)` | Answer the `@owlmeans/web-marketing-consent` full-page step if it is shown right after login. Options: `{ accept?: 'all' \| 'none' \| 'skip', terms?: 'accept' \| 'leave', timeout? }`, default `accept: 'all'`, `terms: 'accept'`, `timeout: 30_000`. Returns whether the step was actually shown and answered — `false` means it never appeared. Throws if a Terms box could not be confirmed. |
71
+ | `supervisorAuthHelper.pregenerateAuthToken(opts)` | Mint an `ED25519-BASIC-TOKEN …` bearer offline from a trusted private key — no browser, no round trip. Options: `{ userId, pk, scopes?, role?, entityId?, profileId?, source? }`. |
72
+ | `supervisorAuthHelper.authenticateViaSupervisorApi(opts)` | Drive the live PK supervisor flow over the backend API (init → sign → authenticate → dispatch), registering the user on first use. Options: `{ apiBaseUrl, userId, pk, paths?, fetchImpl? }`. |
73
+ | `makePageHelper(page).loginViaDispatcher(baseUrl, token, opts?)` | Inject a bearer through the standard `/dispatcher?token=…` route, wait for the app to navigate away, then answer marketing consent (see below). Options: `{ dispatcherPath?, waitUntil?, marketingConsent? }`. |
74
+ | `makePageHelper(page).loginViaSupervisorForm(opts)` | Drive the real login form end-to-end — navigate, answer consent, fill user id + key, submit, wait for the landing, then answer marketing consent (see below). Options: `{ baseUrl, userId, pk, path?, expectPath?, timeout?, waitUntil?, consent?, screenshotDir?, marketingConsent? }`. |
75
+ | `makePageHelper(page).answerMarketingConsent(opts?)` | Answer the `@owlmeans/web-marketing-consent` full-page step if it is shown right after login. Options: `{ accept?: 'all' \| 'none' \| 'skip', terms?: 'accept' \| 'leave', timeout? }`, default `accept: 'all'`, `terms: 'accept'`, `timeout: 30_000`. Returns whether the step was actually shown and answered — `false` means it never appeared. Throws if a Terms box could not be confirmed. |
76
76
 
77
77
  These need a project whose backend trusts the private key they sign with, so they belong to specs
78
78
  that run against a real app rather than a mounted component. See `[[supervisor-auth]]` for the
@@ -82,14 +82,14 @@ server and web wiring they assume.
82
82
 
83
83
  ```ts
84
84
  import { afterAll, describe, expect, test } from 'bun:test'
85
- import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
85
+ import { browserHelper, mountComponent } from '@owlmeans/test-ui'
86
86
  import { HARNESS_URL } from './context.js'
87
87
 
88
88
  // Browser work does not fit bun's 5s default: a cold harness compiles the app on first request,
89
89
  // and the first test to run pays for the Vite boot happening in the same process.
90
90
  const TIMEOUT = 30_000
91
91
 
92
- afterAll(async () => { await closeBrowser() })
92
+ afterAll(async () => { await browserHelper.closeBrowser() })
93
93
 
94
94
  // The harness is a real app, so a spec picks its case by opening a path on it.
95
95
  const open = async (path: string) =>
@@ -173,11 +173,11 @@ For a package that only needs to prove the bun-test + chromium pipeline works, s
173
173
 
174
174
  ```ts
175
175
  import { afterAll, describe, expect, test } from 'bun:test'
176
- import { closeBrowser, mountComponent } from '@owlmeans/test-ui'
176
+ import { browserHelper, mountComponent } from '@owlmeans/test-ui'
177
177
 
178
178
  const harness = `data:text/html,${encodeURIComponent('<h1 id="t">hello</h1>')}`
179
179
 
180
- afterAll(async () => { await closeBrowser() })
180
+ afterAll(async () => { await browserHelper.closeBrowser() })
181
181
 
182
182
  describe('chromium smoke', () => {
183
183
  test('reads a heading from a data: URL', async () => {
@@ -262,7 +262,7 @@ See `[[shadcn-web]]` for the full `@` alias contract and `tests/context.ts` patt
262
262
  - **Always pass a timeout as `test`'s third argument.** 30s for a mounted component, 60s for a
263
263
  suite that drives a login or a consent gate. Bun's 5s default is for in-process work, and
264
264
  `bunfig.toml`'s `[test] timeout` is not a way to change it.
265
- - **Always `close()` the page and `closeBrowser()` in `afterAll`.** A leaked context blocks the bun process from exiting.
265
+ - **Always `close()` the page and `browserHelper.closeBrowser()` in `afterAll`.** A leaked context blocks the bun process from exiting.
266
266
  - **Chromium only by default.** Add `firefox` or `webkit` per package only when the feature has documented cross-browser concerns — call `chromium.launch` / `firefox.launch` explicitly in that case.
267
267
 
268
268
  ## `mountComponent` waits for `domcontentloaded`, not `load`
@@ -278,13 +278,13 @@ selector it actually needs. That wait IS the assertion; the load event never was
278
278
  `waitUntil` explicitly for the rare case that wants otherwise.
279
279
 
280
280
  The rule is not `mountComponent`'s alone — **every** navigation helper here follows it, including
281
- `loginViaSupervisorForm` and `loginViaDispatcher`. A helper that navigates on playwright's default
281
+ `makePageHelper(page).loginViaSupervisorForm` and `.loginViaDispatcher`. A helper that navigates on playwright's default
282
282
  turns "someone added a tag manager" into "the whole login-driven suite times out", with the browser
283
283
  showing a working login form the entire time. Any new helper that calls `page.goto` passes
284
284
  `domcontentloaded` and takes a `waitUntil` override.
285
285
 
286
- `saveScreenshot(page, dir, name)` is the fastest way to see what the browser actually had on
287
- screen when an assertion timed out; `loginViaSupervisorForm` takes a `screenshotDir` that captures
286
+ `makePageHelper(page).saveScreenshot(dir, name)` is the fastest way to see what the browser actually had on
287
+ screen when an assertion timed out; `.loginViaSupervisorForm` takes a `screenshotDir` that captures
288
288
  the filled form just before submit.
289
289
 
290
290
  ## The consent dialog blocks the login form, and the failure blames the button
@@ -297,27 +297,27 @@ and then reports `click: Timeout … waiting for getByTestId('supervisor-submit'
297
297
  button. Nothing is wrong with the button; read the `subtree intercepts pointer events` line, which
298
298
  names `[data-consent-dialog]`.
299
299
 
300
- `loginViaSupervisorForm` therefore calls `acceptConsent(page)` after navigating. `acceptConsent` is
301
- exported for specs that drive their own login: it waits for `[data-consent-dialog]` to become
300
+ `makePageHelper(page).loginViaSupervisorForm` therefore calls `.acceptConsent()` after navigating. `.acceptConsent` is
301
+ public for specs that drive their own login: it waits for `[data-consent-dialog]` to become
302
302
  visible, clicks `[data-consent-accept-all]`, waits for the dialog to detach, and reports whether it
303
303
  answered one. It accepts **all** categories deliberately — a spec asserting a narrower decision must
304
304
  make that decision itself rather than inherit a silent minimum.
305
305
 
306
306
  **The `false` return costs the full wait.** It is what the visibility wait rejects into, not a cheap
307
- probe, so a page with no dialog pays the timeout — 5s by `acceptConsent`'s own default, but
308
- `loginViaSupervisorForm` passes its `timeout` straight through and that defaults to `60_000`. On an
307
+ probe, so a page with no dialog pays the timeout — 5s by `makePageHelper(page).acceptConsent`'s own default, but
308
+ `.loginViaSupervisorForm` passes its `timeout` straight through and that defaults to `60_000`. On an
309
309
  app that ships no consent widget the login helper therefore sits for a full minute before it fills
310
310
  the first field, with nothing on screen to explain it. Drive such an app with `consent: 'ignore'`,
311
311
  which skips the call entirely; that is also the switch a spec flips when it wants to answer the
312
312
  dialog itself.
313
313
 
314
- ## `answerMarketingConsent` — cheap absence detection, unlike the cookie dialog
314
+ ## `makePageHelper(page).answerMarketingConsent` — cheap absence detection, unlike the cookie dialog
315
315
 
316
- `loginViaSupervisorForm` and `loginViaDispatcher` both call `answerMarketingConsent(page, { accept:
316
+ `makePageHelper(page).loginViaSupervisorForm` and `.loginViaDispatcher` both call `.answerMarketingConsent({ accept:
317
317
  'all', timeout })` right after they confirm the app has navigated away from login/dispatch, passing
318
318
  their **own** `timeout` through explicitly rather than relying on this helper's internal default —
319
319
  both of them already budget more than one round trip (login + terms + items) into the value they
320
- pick. Both also default `marketingConsent` (their own option, not `answerMarketingConsent`'s) to
320
+ pick. Both also default `marketingConsent` (their own option, not `.answerMarketingConsent`'s) to
321
321
  `'save'` — pass `'ignore'` to skip the call in a spec that drives the step itself.
322
322
  `@owlmeans/web-marketing-consent` renders a full-page step, when shown, with `[data-marketing-consent]`
323
323
  (root, carrying `data-state="loading"` while its status read is outstanding and `"ready"` once it can
@@ -329,15 +329,15 @@ step is optional-only — i.e. `!loading && !terms.needed` — not only after a
329
329
  `[data-marketing-consent-skip-note]`, `[data-marketing-consent-signout]` (shown while a Terms box or
330
330
  the loading state is up) and `[data-marketing-consent-error]`.
331
331
 
332
- Unlike `acceptConsent`, whose `false` return deliberately costs the full wait (see above),
333
- `answerMarketingConsent` does NOT pay the full `timeout` (default `30_000`) when the step is absent.
332
+ Unlike `makePageHelper(page).acceptConsent`, whose `false` return deliberately costs the full wait (see above),
333
+ `.answerMarketingConsent` does NOT pay the full `timeout` (default `30_000`) when the step is absent.
334
334
  The screen is opt-in per app and most applications and environments don't have it wired in yet, and this helper
335
335
  is called unconditionally by both login helpers — a full-timeout tax on every login, on every app,
336
336
  whether or not the screen exists, would be an unacceptable regression to every existing suite. It
337
337
  instead races `[data-marketing-consent]` against the generic "the app has already landed" /
338
338
  "still on the login screen" markers the login helpers themselves already rely on (`#app-prompt`,
339
339
  `[data-login-method]`) via the same `locator('a, b, c').first().waitFor({ state: 'visible' })` idiom
340
- `loginViaSupervisorForm` uses for its own dialog-vs-form race. By the time a login helper calls this,
340
+ `.loginViaSupervisorForm` uses for its own dialog-vs-form race. By the time a login helper calls this,
341
341
  one of those markers has typically already rendered, so on an app without the screen the race
342
342
  resolves as soon as it does — no fixed sleep, and no meaningful added latency over what the login
343
343
  helper already paid to get there.
@@ -0,0 +1,22 @@
1
+ import type { Browser, LaunchOptions, Page } from 'playwright';
2
+ /** The one chromium a `bun test` process shares, and the pages specs drive off it. */
3
+ export interface BrowserHelper {
4
+ /**
5
+ * Launch (or return) a single shared chromium instance for the current
6
+ * `bun test` process. UI specs hold a fixture page off this browser; the
7
+ * suite calls {@link BrowserHelper.closeBrowser} from `afterAll` to tear it down once.
8
+ */
9
+ launchBrowser: (opts?: LaunchOptions) => Promise<Browser>;
10
+ /**
11
+ * Tear down the shared browser. Idempotent — safe to call from multiple
12
+ * `afterAll` hooks.
13
+ */
14
+ closeBrowser: () => Promise<void>;
15
+ /**
16
+ * Convenience that launches the browser, runs a callback with a fresh
17
+ * context + page, and closes the context on completion. The browser
18
+ * itself stays alive across calls so multiple specs share it cheaply.
19
+ */
20
+ withPage: <T>(fn: (page: Page) => Promise<T>) => Promise<T>;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAE9D,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,aAAa,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACzD;;;OAGG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC;;;;OAIG;IACH,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":""}
@@ -1,19 +1,11 @@
1
- import type { Browser, LaunchOptions } from 'playwright';
2
- /**
3
- * Launch (or return) a single shared chromium instance for the current
4
- * `bun test` process. UI specs hold a fixture page off this browser; the
5
- * suite calls `closeBrowser()` from `afterAll` to tear it down once.
6
- */
1
+ import type { Browser, LaunchOptions, Page } from 'playwright';
2
+ import type { BrowserHelper } from './browser/types.js';
3
+ export declare const createBrowserHelper: () => BrowserHelper;
4
+ export declare const browserHelper: BrowserHelper;
5
+ /** @deprecated compat:factory-refactor — use `browserHelper.launchBrowser(…)` */
7
6
  export declare const launchBrowser: (opts?: LaunchOptions) => Promise<Browser>;
8
- /**
9
- * Tear down the shared browser. Idempotent — safe to call from multiple
10
- * `afterAll` hooks.
11
- */
7
+ /** @deprecated compat:factory-refactor — use `browserHelper.closeBrowser()` */
12
8
  export declare const closeBrowser: () => Promise<void>;
13
- /**
14
- * Convenience that launches the browser, runs a callback with a fresh
15
- * context + page, and closes the context on completion. The browser
16
- * itself stays alive across calls so multiple specs share it cheaply.
17
- */
18
- export declare const withPage: <T>(fn: (page: import('playwright').Page) => Promise<T>) => Promise<T>;
9
+ /** @deprecated compat:factory-refactor — use `browserHelper.withPage(…)` */
10
+ export declare const withPage: <T>(fn: (page: Page) => Promise<T>) => Promise<T>;
19
11
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAKxD;;;;GAIG;AACH,eAAO,MAAM,aAAa,UAAgB,aAAa,KAAQ,OAAO,CAAC,OAAO,CAO7E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,IAAI,CAOjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAU,CAAC,MAC1B,CAAC,IAAI,EAAE,OAAO,YAAY,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAClD,OAAO,CAAC,CAAC,CASX,CAAA"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAMvD,eAAO,MAAM,mBAAmB,QAAO,aA+BtC,CAAA;AAED,eAAO,MAAM,aAAa,eAAwB,CAAA;AAElD,iFAAiF;AACjF,eAAO,MAAM,aAAa,UAAgB,aAAa,KAAQ,OAAO,CAAC,OAAO,CACrC,CAAA;AAEzC,+EAA+E;AAC/E,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,IAAI,CAAuC,CAAA;AAEzF,4EAA4E;AAC5E,eAAO,MAAM,QAAQ,GAAU,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAC1C,CAAA"}
package/build/browser.js CHANGED
@@ -1,47 +1,44 @@
1
1
  import { chromium } from 'playwright';
2
+ // Process-wide on purpose: every spec of a `bun test` run shares one browser.
2
3
  let shared;
3
4
  let inflight;
4
- /**
5
- * Launch (or return) a single shared chromium instance for the current
6
- * `bun test` process. UI specs hold a fixture page off this browser; the
7
- * suite calls `closeBrowser()` from `afterAll` to tear it down once.
8
- */
9
- export const launchBrowser = async (opts = {}) => {
10
- if (shared != null)
5
+ export const createBrowserHelper = () => {
6
+ const launchBrowser = async (opts = {}) => {
7
+ if (shared != null)
8
+ return shared;
9
+ if (inflight != null)
10
+ return inflight;
11
+ inflight = chromium.launch({ headless: true, ...opts });
12
+ shared = await inflight;
13
+ inflight = undefined;
11
14
  return shared;
12
- if (inflight != null)
13
- return inflight;
14
- inflight = chromium.launch({ headless: true, ...opts });
15
- shared = await inflight;
16
- inflight = undefined;
17
- return shared;
18
- };
19
- /**
20
- * Tear down the shared browser. Idempotent — safe to call from multiple
21
- * `afterAll` hooks.
22
- */
23
- export const closeBrowser = async () => {
24
- const b = shared;
25
- shared = undefined;
26
- inflight = undefined;
27
- if (b != null) {
28
- await b.close();
29
- }
30
- };
31
- /**
32
- * Convenience that launches the browser, runs a callback with a fresh
33
- * context + page, and closes the context on completion. The browser
34
- * itself stays alive across calls so multiple specs share it cheaply.
35
- */
36
- export const withPage = async (fn) => {
37
- const browser = await launchBrowser();
38
- const context = await browser.newContext();
39
- const page = await context.newPage();
40
- try {
41
- return await fn(page);
42
- }
43
- finally {
44
- await context.close();
45
- }
15
+ };
16
+ const closeBrowser = async () => {
17
+ const b = shared;
18
+ shared = undefined;
19
+ inflight = undefined;
20
+ if (b != null) {
21
+ await b.close();
22
+ }
23
+ };
24
+ const withPage = async (fn) => {
25
+ const browser = await launchBrowser();
26
+ const context = await browser.newContext();
27
+ const page = await context.newPage();
28
+ try {
29
+ return await fn(page);
30
+ }
31
+ finally {
32
+ await context.close();
33
+ }
34
+ };
35
+ return { launchBrowser, closeBrowser, withPage };
46
36
  };
37
+ export const browserHelper = createBrowserHelper();
38
+ /** @deprecated compat:factory-refactor — use `browserHelper.launchBrowser(…)` */
39
+ export const launchBrowser = async (opts = {}) => await browserHelper.launchBrowser(opts);
40
+ /** @deprecated compat:factory-refactor — use `browserHelper.closeBrowser()` */
41
+ export const closeBrowser = async () => await browserHelper.closeBrowser();
42
+ /** @deprecated compat:factory-refactor — use `browserHelper.withPage(…)` */
43
+ export const withPage = async (fn) => await browserHelper.withPage(fn);
47
44
  //# sourceMappingURL=browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAGrC,IAAI,MAA2B,CAAA;AAC/B,IAAI,QAAsC,CAAA;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,IAAI,GAAkB,EAAE,EAAoB,EAAE;IAChF,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,MAAM,CAAA;IACjC,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,QAAQ,CAAA;IACrC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;IACvD,MAAM,GAAG,MAAM,QAAQ,CAAA;IACvB,QAAQ,GAAG,SAAS,CAAA;IACpB,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,IAAmB,EAAE;IACpD,MAAM,CAAC,GAAG,MAAM,CAAA;IAChB,MAAM,GAAG,SAAS,CAAA;IAClB,QAAQ,GAAG,SAAS,CAAA;IACpB,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,CAAC,KAAK,EAAE,CAAA;IACjB,CAAC;AACH,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,EAAmD,EACvC,EAAE;IACd,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,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,8EAA8E;AAC9E,IAAI,MAA2B,CAAA;AAC/B,IAAI,QAAsC,CAAA;AAE1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAkB,EAAE;IACrD,MAAM,aAAa,GAAG,KAAK,EAAE,IAAI,GAAkB,EAAE,EAAoB,EAAE;QACzE,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,MAAM,CAAA;QACjC,IAAI,QAAQ,IAAI,IAAI;YAAE,OAAO,QAAQ,CAAA;QACrC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;QACvD,MAAM,GAAG,MAAM,QAAQ,CAAA;QACvB,QAAQ,GAAG,SAAS,CAAA;QACpB,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,KAAK,IAAmB,EAAE;QAC7C,MAAM,CAAC,GAAG,MAAM,CAAA;QAChB,MAAM,GAAG,SAAS,CAAA;QAClB,QAAQ,GAAG,SAAS,CAAA;QACpB,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,MAAM,CAAC,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,QAAQ,GAAG,KAAK,EAAK,EAA8B,EAAc,EAAE;QACvE,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAA;QACrC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAA;QAC1C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACpC,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;QACvB,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;QACvB,CAAC;IACH,CAAC,CAAA;IAED,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAA;AAElD,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,IAAI,GAAkB,EAAE,EAAoB,EAAE,CAChF,MAAM,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;AAEzC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,IAAmB,EAAE,CAAC,MAAM,aAAa,CAAC,YAAY,EAAE,CAAA;AAEzF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAK,EAA8B,EAAc,EAAE,CAC9E,MAAM,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30000;
2
+ export declare const DEFAULT_SUPERVISOR_PATH = "/authentication/login/pk-supervisor";
3
+ //# sourceMappingURL=consts.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,QAAS,CAAA;AAEvD,eAAO,MAAM,uBAAuB,wCAAwC,CAAA"}
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30_000;
2
+ export const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor';
3
+ //# sourceMappingURL=consts.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAA;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,qCAAqC,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export * from './browser.js';
2
+ export type * from './browser/types.js';
2
3
  export * from './marketing-consent.js';
4
+ export type * from './types.js';
3
5
  export * from './mount.js';
6
+ export * from './page.js';
7
+ export type * from './page/types.js';
4
8
  export * from './supervisor.js';
9
+ export type * from './supervisor/types.js';
5
10
  export type { Browser, BrowserContext, Locator, Page } from 'playwright';
6
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,mBAAmB,oBAAoB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,mBAAmB,iBAAiB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,mBAAmB,uBAAuB,CAAA;AAC1C,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA"}
package/build/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './browser.js';
2
2
  export * from './marketing-consent.js';
3
3
  export * from './mount.js';
4
+ export * from './page.js';
4
5
  export * from './supervisor.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,wBAAwB,CAAA;AACtC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAE5B,cAAc,wBAAwB,CAAA;AAEtC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AAEzB,cAAc,iBAAiB,CAAA"}
@@ -1,43 +1,5 @@
1
1
  import type { Page } from 'playwright';
2
- export interface AnswerMarketingConsentOptions {
3
- /**
4
- * Which choice to save when the step is shown. `'all'` (the default) ticks the "select all"
5
- * checkbox (every consent, and the Terms row while it is up — see {@link terms} for what is left
6
- * of that) before saving; `'none'` saves with every item left unchecked, which is a valid,
7
- * fully-compliant state on its own; `'skip'` clicks "Skip for now" instead of saving anything —
8
- * only valid when a Terms box is not on screen (nothing calls it there; see {@link terms}).
9
- */
10
- accept?: 'none' | 'all' | 'skip';
11
- /**
12
- * When a Terms row is on screen (`appendMarketingConsent({ terms: 'step' })`), `'accept'` (the
13
- * default) ends with it ticked. `'leave'` ends with it unticked, even after a select-all — for a
14
- * spec that means to drive the blocked state itself; the confirm click below still fires (with `{ force: true }`, since
15
- * the control is never truly `disabled`), and this helper then EXPECTS the box to still be up
16
- * afterward rather than treating that as a failure.
17
- */
18
- terms?: 'accept' | 'leave';
19
- timeout?: number;
20
- }
21
- /**
22
- * Answer the marketing-consent full-page step (`@owlmeans/web-marketing-consent`) if it is shown
23
- * right after login, so a spec driving {@link loginViaSupervisorForm} or {@link loginViaDispatcher}
24
- * is never blocked behind it.
25
- *
26
- * The screen is opt-in per app, and most apps/environments will not have it wired in — that makes
27
- * detecting its ABSENCE cheaply the whole point. A plain `waitFor` on `[data-marketing-consent]`
28
- * alone would cost the full `timeout` on every login of every app that never renders it (the same
29
- * caveat `acceptConsent` documents for the cookie dialog). Instead this races the step's own root
30
- * against the generic OwlMeans "the app has already landed" / "still on the login screen" markers
31
- * this file's own login helpers already wait on (`#app-prompt`, `[data-login-method]`) — by the
32
- * time a login helper calls this, one of those has typically already rendered, so the race resolves
33
- * as soon as it does: no fixed sleep, and in the common "step absent" case, no material added
34
- * latency over what the login helper already paid to get here.
35
- *
36
- * @returns whether the step was actually shown and answered (including the error/skip fallback
37
- * below); `false` when it never appeared within `timeout`, which is the expected result everywhere
38
- * the screen is not yet configured or the visitor already has saved consents.
39
- * @throws if a Terms box could not be confirmed (a `[data-marketing-consent-terms-error]`, or the
40
- * box is still up with nowhere left to go — Skip is never offered there on purpose).
41
- */
2
+ import type { AnswerMarketingConsentOptions } from './page/types.js';
3
+ /** @deprecated compat:factory-refactor — use `makePageHelper(page).answerMarketingConsent(…)` */
42
4
  export declare const answerMarketingConsent: (page: Page, opts?: AnswerMarketingConsentOptions) => Promise<boolean>;
43
5
  //# sourceMappingURL=marketing-consent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"marketing-consent.d.ts","sourceRoot":"","sources":["../src/marketing-consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,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;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,SAC3B,IAAI,SAAS,6BAA6B,KAC/C,OAAO,CAAC,OAAO,CA2FjB,CAAA"}
1
+ {"version":3,"file":"marketing-consent.d.ts","sourceRoot":"","sources":["../src/marketing-consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AAEpE,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,SAC3B,IAAI,SAAS,6BAA6B,KAC/C,OAAO,CAAC,OAAO,CAA4D,CAAA"}
@@ -1,107 +1,4 @@
1
- const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30_000;
2
- /**
3
- * Answer the marketing-consent full-page step (`@owlmeans/web-marketing-consent`) if it is shown
4
- * right after login, so a spec driving {@link loginViaSupervisorForm} or {@link loginViaDispatcher}
5
- * is never blocked behind it.
6
- *
7
- * The screen is opt-in per app, and most apps/environments will not have it wired in — that makes
8
- * detecting its ABSENCE cheaply the whole point. A plain `waitFor` on `[data-marketing-consent]`
9
- * alone would cost the full `timeout` on every login of every app that never renders it (the same
10
- * caveat `acceptConsent` documents for the cookie dialog). Instead this races the step's own root
11
- * against the generic OwlMeans "the app has already landed" / "still on the login screen" markers
12
- * this file's own login helpers already wait on (`#app-prompt`, `[data-login-method]`) — by the
13
- * time a login helper calls this, one of those has typically already rendered, so the race resolves
14
- * as soon as it does: no fixed sleep, and in the common "step absent" case, no material added
15
- * latency over what the login helper already paid to get here.
16
- *
17
- * @returns whether the step was actually shown and answered (including the error/skip fallback
18
- * below); `false` when it never appeared within `timeout`, which is the expected result everywhere
19
- * the screen is not yet configured or the visitor already has saved consents.
20
- * @throws if a Terms box could not be confirmed (a `[data-marketing-consent-terms-error]`, or the
21
- * box is still up with nowhere left to go — Skip is never offered there on purpose).
22
- */
23
- export const answerMarketingConsent = async (page, opts) => {
24
- const timeout = opts?.timeout ?? DEFAULT_MARKETING_CONSENT_TIMEOUT;
25
- const step = page.locator('[data-marketing-consent]');
26
- try {
27
- await page.locator('[data-marketing-consent], #app-prompt, [data-login-method]')
28
- .first().waitFor({ state: 'visible', timeout });
29
- }
30
- catch {
31
- return false;
32
- }
33
- if (await step.count() === 0)
34
- return false;
35
- // The step's own status read may still be outstanding (`data-state="loading"`) — wait past it,
36
- // OR notice it already auto-continued (nothing was pending) and detached on its own. An older
37
- // `web-marketing-consent` that predates `data-state` entirely satisfies the first selector
38
- // immediately (a missing attribute is never `"loading"`), so this costs nothing there.
39
- await Promise.race([
40
- page.waitForSelector('[data-marketing-consent]:not([data-state="loading"])', { timeout }),
41
- page.waitForSelector('[data-marketing-consent]', { state: 'detached', timeout }),
42
- ]).catch(() => undefined);
43
- // Nothing was pending — it already auto-continued while this waited.
44
- if (await step.count() === 0)
45
- return true;
46
- const terms = page.locator('[data-marketing-consent-terms]');
47
- const hasTerms = await terms.count() > 0;
48
- if (!hasTerms && opts?.accept === 'skip') {
49
- await page.locator('[data-marketing-consent-skip]').click();
50
- await step.waitFor({ state: 'detached', timeout });
51
- return true;
52
- }
53
- if ((opts?.accept ?? 'all') === 'all') {
54
- // The step draws "Select all" only when there is more than one row to select; a lone consent
55
- // is ticked itself. Select all speaks for the Terms row too while it is up, which the choice
56
- // below then settles either way.
57
- const all = page.locator('[data-marketing-consent-all]');
58
- if (await all.count() > 0) {
59
- await all.click();
60
- }
61
- else {
62
- for (const item of await page.locator('[data-marketing-consent-item]').all()) {
63
- await item.check();
64
- }
65
- }
66
- }
67
- // 'none' leaves every item unchecked and clicks nothing else before saving.
68
- if (hasTerms) {
69
- const wanted = (opts?.terms ?? 'accept') === 'accept';
70
- if (wanted && !(await terms.isChecked())) {
71
- await terms.check();
72
- }
73
- else if (!wanted && await terms.isChecked()) {
74
- await terms.uncheck();
75
- }
76
- }
77
- // `{ force: true }`: the confirm is never the native `disabled` — only `aria-disabled` while a
78
- // Terms box is unticked, or merely styled muted with nothing changed yet — and Playwright's own
79
- // actionability check honours `aria-disabled`, which this helper deliberately does not.
80
- await page.locator('[data-marketing-consent-save]').click({ force: true });
81
- const outcome = await Promise.race([
82
- step.waitFor({ state: 'detached', timeout }).then(() => 'saved'),
83
- page.locator('[data-marketing-consent-error]').waitFor({ state: 'visible', timeout })
84
- .then(() => 'error'),
85
- page.locator('[data-marketing-consent-terms-error]').waitFor({ state: 'visible', timeout })
86
- .then(() => 'terms-error'),
87
- ]).catch(() => 'error');
88
- if (outcome === 'terms-error') {
89
- throw new Error('[@owlmeans/test-ui] the marketing-consent step could not confirm the Terms');
90
- }
91
- if (outcome !== 'saved') {
92
- const skip = page.locator('[data-marketing-consent-skip]');
93
- if (await skip.count() > 0) {
94
- console.warn('[@owlmeans/test-ui] marketing consent save did not complete; using the skip link instead');
95
- await skip.click();
96
- await step.waitFor({ state: 'detached', timeout }).catch(() => undefined);
97
- }
98
- else if (await terms.count() > 0) {
99
- // No Skip is ever offered while the Terms box is up — a failed/blocked confirm here has no
100
- // fallback, and silently returning would hide a caller stuck behind a real requirement.
101
- throw new Error('[@owlmeans/test-ui] the marketing-consent step is still showing its Terms box');
102
- }
103
- }
104
- // Answered either way: saved, or skipped past a flaky save. Never leaves the caller blocked.
105
- return true;
106
- };
1
+ import { makePageHelper } from './page.js';
2
+ /** @deprecated compat:factory-refactor — use `makePageHelper(page).answerMarketingConsent(…)` */
3
+ export const answerMarketingConsent = async (page, opts) => await makePageHelper(page).answerMarketingConsent(opts);
107
4
  //# sourceMappingURL=marketing-consent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"marketing-consent.js","sourceRoot":"","sources":["../src/marketing-consent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"marketing-consent.js","sourceRoot":"","sources":["../src/marketing-consent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAG1C,iGAAiG;AACjG,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EACzC,IAAU,EAAE,IAAoC,EAC9B,EAAE,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA"}