@owlmeans/test-ui 0.1.18-rc.10 → 0.1.18-rc.12

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/test-ui",
4
- "version": "0.1.18-rc.8",
5
- "generatedAt": "2026-08-27T16:32:18.461Z",
4
+ "version": "0.1.18-rc.12",
5
+ "generatedAt": "2026-09-01T12:04:42.699Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -196,3 +196,37 @@ See `[[shadcn-web]]` for the full `@` alias contract and `tests/context.ts` patt
196
196
  - **Max 3-4 tests per component**: render, primary interaction, one edge case.
197
197
  - **Always `close()` the page and `closeBrowser()` in `afterAll`.** A leaked context blocks the bun process from exiting.
198
198
  - **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.
199
+
200
+ ## `mountComponent` waits for `domcontentloaded`, not `load`
201
+
202
+ Playwright's own default is `load`, and it is wrong for any page that is a real application:
203
+ `load` waits for EVERY subresource, so one analytics beacon, long-poll or third-party pixel that
204
+ never settles holds the navigation open until it times out — with the page fully rendered and
205
+ working the whole time. The failure reads as "the site is down", and the first instinct is to go
206
+ looking at the server.
207
+
208
+ `mountComponent` therefore navigates with `domcontentloaded` and lets the spec wait for the
209
+ selector it actually needs. That wait IS the assertion; the load event never was. Pass
210
+ `waitUntil` explicitly for the rare case that wants otherwise.
211
+
212
+ The rule is not `mountComponent`'s alone — **every** navigation helper here follows it, and
213
+ `loginViaSupervisorForm` / `loginViaDispatcher` are the ones that bit: they kept playwright's
214
+ default long after `mountComponent` was fixed, so adding a tag manager to an app broke its whole
215
+ login-driven suite while the browser showed a working login form.
216
+
217
+ ## The consent dialog blocks the login form, and the failure blames the button
218
+
219
+ An OwlMeans app asks for cookie consent **before** it will start an authentication flow, and the
220
+ dialog is a modal with no dismissal — a decision is what the gate is waiting for. Until it is
221
+ answered the overlay intercepts pointer events, so a login form renders, resolves, reports itself
222
+ `visible, enabled and stable`, and still cannot be clicked. Playwright retries for the full timeout
223
+ and then reports `click: Timeout … waiting for getByTestId('supervisor-submit')`, naming the
224
+ button. Nothing is wrong with the button; read the `subtree intercepts pointer events` line, which
225
+ names `[data-consent-dialog]`.
226
+
227
+ `loginViaSupervisorForm` therefore calls `acceptConsent(page)` after navigating. `acceptConsent` is
228
+ exported for specs that drive their own login: it clicks `[data-consent-accept-all]`, waits for the
229
+ dialog to detach, and returns `false` at once when there is no dialog, so an app without the widget
230
+ is unaffected. It accepts **all** categories deliberately — a spec asserting a narrower decision
231
+ must make that decision itself rather than inherit a silent minimum. Pass `consent: 'ignore'` to
232
+ answer the dialog in the spec instead.
@@ -34,6 +34,7 @@ export declare const pregenerateAuthToken: (opts: PregenerateAuthTokenOptions) =
34
34
  */
35
35
  export declare const loginViaDispatcher: (page: Page, baseUrl: string, token: string, opts?: {
36
36
  dispatcherPath?: string;
37
+ waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
37
38
  }) => Promise<void>;
38
39
  export interface SupervisorApiAuthOptions {
39
40
  /** Auth-manager API base URL (the backend serving /authentication/* and /authenticate). */
@@ -78,9 +79,43 @@ export interface SupervisorFormLoginOptions {
78
79
  /** Predicate/URL fragment that signals a successful landing (default: leaving the login path). */
79
80
  expectPath?: string;
80
81
  timeout?: number;
82
+ /**
83
+ * What counts as "arrived" for the initial navigation. Defaults to `domcontentloaded`.
84
+ *
85
+ * See the note on `MountOptions.waitUntil`: `load` is playwright's default and is wrong for a
86
+ * real application, because it waits for every subresource — a tag manager, an analytics beacon
87
+ * or any pixel that never settles holds it open until the navigation times out, with the login
88
+ * form rendered and fillable the whole time.
89
+ */
90
+ waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
91
+ /**
92
+ * What to do about a cookie-consent dialog covering the form. Defaults to `accept`, because an
93
+ * OwlMeans app will not start an authentication flow until consent is answered — see
94
+ * {@link acceptConsent}. Pass `ignore` only in a spec that answers the dialog itself.
95
+ */
96
+ consent?: 'accept' | 'ignore';
81
97
  /** When set, capture the filled login form (`supervisor-login-form.png`) before submit. */
82
98
  screenshotDir?: string;
83
99
  }
100
+ /**
101
+ * Answer the cookie-consent dialog if one is up, so the page underneath can be driven.
102
+ *
103
+ * An OwlMeans app asks for consent BEFORE it will start an authentication flow, and the dialog is
104
+ * a modal with no dismissal — by design, since a decision is what the gate is waiting for. For a
105
+ * test that means the login form renders, resolves, reports itself "visible, enabled and stable"
106
+ * and still cannot be clicked, because the overlay intercepts the pointer. Playwright retries for
107
+ * the full timeout and then blames the button, which is the most misleading failure in this file:
108
+ * nothing is wrong with the form.
109
+ *
110
+ * Accepting is the honest default for a login helper — the flow it is about to drive is exactly
111
+ * what the dialog is gating — but it is best-effort: an app without the widget, or one where the
112
+ * visitor already decided, simply has no dialog and this returns at once.
113
+ *
114
+ * @returns whether a dialog was actually answered.
115
+ */
116
+ export declare const acceptConsent: (page: Page, opts?: {
117
+ timeout?: number;
118
+ }) => Promise<boolean>;
84
119
  /**
85
120
  * Drive the PK-based supervisor login FORM end-to-end (the faithful path that
86
121
  * exercises the real supervisor plugin + registration): navigate to the
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAyB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AASrE,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;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,SAAgB,2BAA2B,KAAG,OAAO,CAAC,MAAM,CAe5F,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SACvB,IAAI,WAAW,MAAM,SAAS,MAAM,SAAS;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,KAC7E,OAAO,CAAC,IAAI,CAOd,CAAA;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;AAgBD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,SACjC,wBAAwB,KAC7B,OAAO,CAAC,MAAM,CAkChB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SAAgB,IAAI,OAAO,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAM1F,CAAA;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,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAID;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,SAC3B,IAAI,QAAQ,0BAA0B,KAC3C,OAAO,CAAC,IAAI,CAmBd,CAAA"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAyB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AASrE,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;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,SAAgB,2BAA2B,KAAG,OAAO,CAAC,MAAM,CAe5F,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SACvB,IAAI,WAAW,MAAM,SAAS,MAAM,SACnC;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;CAAE,KACrG,OAAO,CAAC,IAAI,CAQd,CAAA;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;AAgBD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,SACjC,wBAAwB,KAC7B,OAAO,CAAC,MAAM,CAkChB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SAAgB,IAAI,OAAO,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAM1F,CAAA;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;CACvB;AAID;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,SAClB,IAAI,SAAS;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,CAAC,OAAO,CAajB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,SAC3B,IAAI,QAAQ,0BAA0B,KAC3C,OAAO,CAAC,IAAI,CAiCd,CAAA"}
@@ -39,7 +39,8 @@ export const loginViaDispatcher = async (page, baseUrl, token, opts) => {
39
39
  const path = opts?.dispatcherPath ?? '/dispatcher';
40
40
  const url = new URL(path, baseUrl);
41
41
  url.searchParams.set('token', token);
42
- await page.goto(url.toString());
42
+ // `domcontentloaded`, not playwright's `load` default — see the note on `MountOptions.waitUntil`.
43
+ await page.goto(url.toString(), { waitUntil: opts?.waitUntil ?? 'domcontentloaded' });
43
44
  // Wait until the dispatcher has navigated away (token consumed).
44
45
  await page.waitForURL(u => !u.pathname.startsWith(path), { timeout: 30_000 });
45
46
  };
@@ -106,6 +107,36 @@ export const saveScreenshot = async (page, dir, name) => {
106
107
  return file;
107
108
  };
108
109
  const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor';
110
+ /**
111
+ * Answer the cookie-consent dialog if one is up, so the page underneath can be driven.
112
+ *
113
+ * An OwlMeans app asks for consent BEFORE it will start an authentication flow, and the dialog is
114
+ * a modal with no dismissal — by design, since a decision is what the gate is waiting for. For a
115
+ * test that means the login form renders, resolves, reports itself "visible, enabled and stable"
116
+ * and still cannot be clicked, because the overlay intercepts the pointer. Playwright retries for
117
+ * the full timeout and then blames the button, which is the most misleading failure in this file:
118
+ * nothing is wrong with the form.
119
+ *
120
+ * Accepting is the honest default for a login helper — the flow it is about to drive is exactly
121
+ * what the dialog is gating — but it is best-effort: an app without the widget, or one where the
122
+ * visitor already decided, simply has no dialog and this returns at once.
123
+ *
124
+ * @returns whether a dialog was actually answered.
125
+ */
126
+ export const acceptConsent = async (page, opts) => {
127
+ const dialog = page.locator('[data-consent-dialog]');
128
+ try {
129
+ await dialog.waitFor({ state: 'visible', timeout: opts?.timeout ?? 5_000 });
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ // Accept-all rather than essential-only: a test asserting a narrower decision should make that
135
+ // decision itself, and silently choosing the minimum here would hide it from the spec.
136
+ await page.locator('[data-consent-accept-all]').click();
137
+ await dialog.waitFor({ state: 'detached', timeout: opts?.timeout ?? 5_000 });
138
+ return true;
139
+ };
109
140
  /**
110
141
  * Drive the PK-based supervisor login FORM end-to-end (the faithful path that
111
142
  * exercises the real supervisor plugin + registration): navigate to the
@@ -115,13 +146,26 @@ const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor';
115
146
  export const loginViaSupervisorForm = async (page, opts) => {
116
147
  const path = opts.path ?? DEFAULT_SUPERVISOR_PATH;
117
148
  const timeout = opts.timeout ?? 60_000;
118
- await page.goto(new URL(path, opts.baseUrl).toString());
119
- await page.getByTestId('supervisor-user-id').fill(opts.userId);
120
- await page.getByTestId('supervisor-pk').fill(opts.pk);
149
+ await page.goto(new URL(path, opts.baseUrl).toString(), {
150
+ waitUntil: opts.waitUntil ?? 'domcontentloaded', timeout
151
+ });
152
+ // Wait for the app to MOUNT before deciding whether there is a dialog to answer. `domcontentloaded`
153
+ // returns while the SPA is still booting, and on a dev server that boot is occasionally tens of
154
+ // seconds — so a short consent probe reports "no dialog", the dialog then mounts over the form,
155
+ // and the fill below waits out its own timeout on a field that is present but covered. Either
156
+ // marker proves the app is up: the dialog, or the form itself when consent was already given.
157
+ await page.locator('[data-consent-dialog], [data-testid="supervisor-user-id"]')
158
+ .first().waitFor({ state: 'visible', timeout });
159
+ if (opts.consent !== 'ignore')
160
+ await acceptConsent(page, { timeout });
161
+ // The form renders only AFTER the dialog is dismissed, so these carry the caller's budget rather
162
+ // than playwright's 30s default — the whole point of accepting an explicit `timeout` here.
163
+ await page.getByTestId('supervisor-user-id').fill(opts.userId, { timeout });
164
+ await page.getByTestId('supervisor-pk').fill(opts.pk, { timeout });
121
165
  if (opts.screenshotDir != null) {
122
166
  await saveScreenshot(page, opts.screenshotDir, 'supervisor-login-form');
123
167
  }
124
- await page.getByTestId('supervisor-submit').click();
168
+ await page.getByTestId('supervisor-submit').click({ timeout });
125
169
  if (opts.expectPath != null) {
126
170
  await page.waitForURL(url => url.toString().includes(opts.expectPath), { timeout });
127
171
  }
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/test-ui",
3
- "version": "0.1.18-rc.10",
3
+ "version": "0.1.18-rc.12",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/supervisor.ts CHANGED
@@ -60,12 +60,14 @@ export const pregenerateAuthToken = async (opts: PregenerateAuthTokenOptions): P
60
60
  * (e.g. to force an external IdP) should use {@link loginViaSupervisorForm}.
61
61
  */
62
62
  export const loginViaDispatcher = async (
63
- page: Page, baseUrl: string, token: string, opts?: { dispatcherPath?: string }
63
+ page: Page, baseUrl: string, token: string,
64
+ opts?: { dispatcherPath?: string, waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle' }
64
65
  ): Promise<void> => {
65
66
  const path = opts?.dispatcherPath ?? '/dispatcher'
66
67
  const url = new URL(path, baseUrl)
67
68
  url.searchParams.set('token', token)
68
- await page.goto(url.toString())
69
+ // `domcontentloaded`, not playwright's `load` default — see the note on `MountOptions.waitUntil`.
70
+ await page.goto(url.toString(), { waitUntil: opts?.waitUntil ?? 'domcontentloaded' })
69
71
  // Wait until the dispatcher has navigated away (token consumed).
70
72
  await page.waitForURL(u => !u.pathname.startsWith(path), { timeout: 30_000 })
71
73
  }
@@ -168,12 +170,60 @@ export interface SupervisorFormLoginOptions {
168
170
  /** Predicate/URL fragment that signals a successful landing (default: leaving the login path). */
169
171
  expectPath?: string
170
172
  timeout?: number
173
+ /**
174
+ * What counts as "arrived" for the initial navigation. Defaults to `domcontentloaded`.
175
+ *
176
+ * See the note on `MountOptions.waitUntil`: `load` is playwright's default and is wrong for a
177
+ * real application, because it waits for every subresource — a tag manager, an analytics beacon
178
+ * or any pixel that never settles holds it open until the navigation times out, with the login
179
+ * form rendered and fillable the whole time.
180
+ */
181
+ waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle'
182
+ /**
183
+ * What to do about a cookie-consent dialog covering the form. Defaults to `accept`, because an
184
+ * OwlMeans app will not start an authentication flow until consent is answered — see
185
+ * {@link acceptConsent}. Pass `ignore` only in a spec that answers the dialog itself.
186
+ */
187
+ consent?: 'accept' | 'ignore'
171
188
  /** When set, capture the filled login form (`supervisor-login-form.png`) before submit. */
172
189
  screenshotDir?: string
173
190
  }
174
191
 
175
192
  const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor'
176
193
 
194
+ /**
195
+ * Answer the cookie-consent dialog if one is up, so the page underneath can be driven.
196
+ *
197
+ * An OwlMeans app asks for consent BEFORE it will start an authentication flow, and the dialog is
198
+ * a modal with no dismissal — by design, since a decision is what the gate is waiting for. For a
199
+ * test that means the login form renders, resolves, reports itself "visible, enabled and stable"
200
+ * and still cannot be clicked, because the overlay intercepts the pointer. Playwright retries for
201
+ * the full timeout and then blames the button, which is the most misleading failure in this file:
202
+ * nothing is wrong with the form.
203
+ *
204
+ * Accepting is the honest default for a login helper — the flow it is about to drive is exactly
205
+ * what the dialog is gating — but it is best-effort: an app without the widget, or one where the
206
+ * visitor already decided, simply has no dialog and this returns at once.
207
+ *
208
+ * @returns whether a dialog was actually answered.
209
+ */
210
+ export const acceptConsent = async (
211
+ page: Page, opts?: { timeout?: number }
212
+ ): Promise<boolean> => {
213
+ const dialog = page.locator('[data-consent-dialog]')
214
+ try {
215
+ await dialog.waitFor({ state: 'visible', timeout: opts?.timeout ?? 5_000 })
216
+ } catch {
217
+ return false
218
+ }
219
+ // Accept-all rather than essential-only: a test asserting a narrower decision should make that
220
+ // decision itself, and silently choosing the minimum here would hide it from the spec.
221
+ await page.locator('[data-consent-accept-all]').click()
222
+ await dialog.waitFor({ state: 'detached', timeout: opts?.timeout ?? 5_000 })
223
+
224
+ return true
225
+ }
226
+
177
227
  /**
178
228
  * Drive the PK-based supervisor login FORM end-to-end (the faithful path that
179
229
  * exercises the real supervisor plugin + registration): navigate to the
@@ -185,16 +235,30 @@ export const loginViaSupervisorForm = async (
185
235
  ): Promise<void> => {
186
236
  const path = opts.path ?? DEFAULT_SUPERVISOR_PATH
187
237
  const timeout = opts.timeout ?? 60_000
188
- await page.goto(new URL(path, opts.baseUrl).toString())
238
+ await page.goto(new URL(path, opts.baseUrl).toString(), {
239
+ waitUntil: opts.waitUntil ?? 'domcontentloaded', timeout
240
+ })
241
+
242
+ // Wait for the app to MOUNT before deciding whether there is a dialog to answer. `domcontentloaded`
243
+ // returns while the SPA is still booting, and on a dev server that boot is occasionally tens of
244
+ // seconds — so a short consent probe reports "no dialog", the dialog then mounts over the form,
245
+ // and the fill below waits out its own timeout on a field that is present but covered. Either
246
+ // marker proves the app is up: the dialog, or the form itself when consent was already given.
247
+ await page.locator('[data-consent-dialog], [data-testid="supervisor-user-id"]')
248
+ .first().waitFor({ state: 'visible', timeout })
249
+
250
+ if (opts.consent !== 'ignore') await acceptConsent(page, { timeout })
189
251
 
190
- await page.getByTestId('supervisor-user-id').fill(opts.userId)
191
- await page.getByTestId('supervisor-pk').fill(opts.pk)
252
+ // The form renders only AFTER the dialog is dismissed, so these carry the caller's budget rather
253
+ // than playwright's 30s default — the whole point of accepting an explicit `timeout` here.
254
+ await page.getByTestId('supervisor-user-id').fill(opts.userId, { timeout })
255
+ await page.getByTestId('supervisor-pk').fill(opts.pk, { timeout })
192
256
 
193
257
  if (opts.screenshotDir != null) {
194
258
  await saveScreenshot(page, opts.screenshotDir, 'supervisor-login-form')
195
259
  }
196
260
 
197
- await page.getByTestId('supervisor-submit').click()
261
+ await page.getByTestId('supervisor-submit').click({ timeout })
198
262
 
199
263
  if (opts.expectPath != null) {
200
264
  await page.waitForURL(url => url.toString().includes(opts.expectPath!), { timeout })