@trackunit/iris-app-playwright 0.2.115 → 0.2.116-alpha-abe4978141a.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/entry.js.map +1 -0
- package/package.json +1 -1
- package/src/credentials/guards.js.map +1 -0
- package/src/credentials/keys.js.map +1 -0
- package/src/credentials/resolveE2EPassword.js.map +1 -0
- package/src/fixtures/acceptTerms.d.ts +74 -0
- package/src/fixtures/acceptTerms.js +209 -0
- package/src/fixtures/acceptTerms.js.map +1 -0
- package/src/fixtures/baseTest.js.map +1 -0
- package/src/fixtures/describe.js.map +1 -0
- package/src/fixtures/featureFlags.fixture.js.map +1 -0
- package/src/fixtures/harRecording.fixture.js.map +1 -0
- package/src/fixtures/hostReadiness.js.map +1 -0
- package/src/fixtures/hydrateStorageState.js.map +1 -0
- package/src/fixtures/irisApp.fixture.js.map +1 -0
- package/src/fixtures/localDevMode.fixture.js.map +1 -0
- package/src/fixtures/login.fixture.d.ts +68 -7
- package/src/fixtures/login.fixture.js +263 -42
- package/src/fixtures/login.fixture.js.map +1 -0
- package/src/fixtures/loginCacheKey.js.map +1 -0
- package/src/fixtures/resolveCredentials.js.map +1 -0
- package/src/fixtures/withTimeout.js.map +1 -0
- package/src/generators/playwright-configuration/generator.js.map +1 -0
- package/src/index.js.map +1 -0
- package/src/plugins/createLogFile.js.map +1 -0
- package/src/plugins/defaultPlaywrightConfig.js.map +1 -0
- package/src/plugins/harRecordingPath.js.map +1 -0
- package/src/plugins/logsReporter.js.map +1 -0
- package/src/plugins/nxPreset.js.map +1 -0
- package/src/plugins/redactHar.js.map +1 -0
- package/src/plugins/redactPatterns.js.map +1 -0
- package/src/plugins/redactSensitive.js.map +1 -0
- package/src/plugins/redactTrace.js.map +1 -0
- package/src/plugins/scanArtifactsForSecrets.js.map +1 -0
- package/src/plugins/setupPlugins.js.map +1 -0
- package/src/plugins/writeFileWithPrettier.js.map +1 -0
- package/src/support.js.map +1 -0
- package/src/utils/Codeowner.js.map +1 -0
- package/src/utils/fileNameBuilder.js.map +1 -0
- package/src/utils/fileUpdater.js.map +1 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.expect = exports.performOktaLogin = exports.test = exports.waitForEnvReady = exports.EnvReadyTimeoutError = exports.waitForMainMenuReady = exports.isMainMenuReady = exports.reserveFeatureBranchLoginBudget = exports.extendTestTimeout = exports.MAIN_MENU_READY_SELECTOR = exports.SESSION_DOCUMENT_RETRY_DELAY_MS = exports.ENV_READY_POLL_INTERVAL_MS = exports.ENV_READY_TIMEOUT_ENV_VAR = exports.ENV_READY_TIMEOUT_MS = exports.MAIN_MENU_READY_TIMEOUT_MS = exports.HIDE_MENU_URL_FRAGMENTS = void 0;
|
|
3
|
+
exports.expect = exports.signIn = exports.performOktaLogin = exports.test = exports.waitForEnvReady = exports.EnvReadyTimeoutError = exports.waitForMainMenuReady = exports.isMainMenuReady = exports.reserveFeatureBranchLoginBudget = exports.extendTestTimeout = exports.MAIN_MENU_READY_SELECTOR = exports.SESSION_DOCUMENT_RETRY_DELAY_MS = exports.ENV_READY_POLL_INTERVAL_MS = exports.ENV_READY_TIMEOUT_ENV_VAR = exports.ENV_READY_TIMEOUT_MS = exports.MAIN_MENU_READY_TIMEOUT_MS = exports.HIDE_MENU_URL_FRAGMENTS = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
|
+
const acceptTerms_1 = require("./acceptTerms");
|
|
5
6
|
const hostReadiness_1 = require("./hostReadiness");
|
|
6
7
|
const hydrateStorageState_1 = require("./hydrateStorageState");
|
|
7
8
|
const loginCacheKey_1 = require("./loginCacheKey");
|
|
@@ -69,8 +70,19 @@ const reserveFeatureBranchLoginBudget = (testInfo) => {
|
|
|
69
70
|
(0, exports.extendTestTimeout)(testInfo, exports.MAIN_MENU_READY_TIMEOUT_MS);
|
|
70
71
|
};
|
|
71
72
|
exports.reserveFeatureBranchLoginBudget = reserveFeatureBranchLoginBudget;
|
|
72
|
-
/**
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Returns whether the main menu is ready or intentionally hidden for the current route.
|
|
75
|
+
*
|
|
76
|
+
* Root cause (GLU-2632): `/terms` is in {@link HIDE_MENU_URL_FRAGMENTS}, so every
|
|
77
|
+
* host treated the unfinished terms prompt as a finished login. Callers on a
|
|
78
|
+
* feature-branch host pass `termsCountsAsReady: false`, so a URL containing
|
|
79
|
+
* `/terms` is not ready even if a menu node is present. An omitted flag stays
|
|
80
|
+
* ready, which keeps today's behaviour for every other host.
|
|
81
|
+
*/
|
|
82
|
+
const isMainMenuReady = ({ selector, fragments, termsCountsAsReady = true, }) => {
|
|
83
|
+
if (!termsCountsAsReady && window.location.pathname.includes("/terms")) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
74
86
|
if (document.querySelector(selector) !== null) {
|
|
75
87
|
return true;
|
|
76
88
|
}
|
|
@@ -81,10 +93,29 @@ exports.isMainMenuReady = isMainMenuReady;
|
|
|
81
93
|
* Waits until Manager's main menu is rendered without a loading skeleton, or
|
|
82
94
|
* until the browser is on a HostLayout hide-menu route (marketplace / auth /
|
|
83
95
|
* terms) where the menu is intentionally absent.
|
|
96
|
+
*
|
|
97
|
+
* On a feature-branch host, `/terms` is excluded: the prompt is not a finished
|
|
98
|
+
* login (GLU-2632). The flag is computed here, in Node, and passed in because
|
|
99
|
+
* {@link isMainMenuReady} is evaluated in the page.
|
|
84
100
|
*/
|
|
85
101
|
const waitForMainMenuReady = async (page, testInfo) => {
|
|
86
102
|
(0, exports.extendTestTimeout)(testInfo, exports.MAIN_MENU_READY_TIMEOUT_MS);
|
|
87
|
-
|
|
103
|
+
try {
|
|
104
|
+
await page.waitForFunction(exports.isMainMenuReady, {
|
|
105
|
+
selector: exports.MAIN_MENU_READY_SELECTOR,
|
|
106
|
+
fragments: exports.HIDE_MENU_URL_FRAGMENTS,
|
|
107
|
+
termsCountsAsReady: !(0, hostReadiness_1.isFeatureBranchHost)(),
|
|
108
|
+
}, { timeout: exports.MAIN_MENU_READY_TIMEOUT_MS });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
// The terms prompt can redirect after the inner shell wait has returned.
|
|
112
|
+
// On a feature-branch host that timeout is the host never becoming ready,
|
|
113
|
+
// before any page assertion. Other hosts keep Playwright's timeout.
|
|
114
|
+
if ((0, hostReadiness_1.isFeatureBranchHost)() && error instanceof test_1.errors.TimeoutError) {
|
|
115
|
+
throw new hostReadiness_1.HostNeverBecameReadyError(error);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
88
119
|
};
|
|
89
120
|
exports.waitForMainMenuReady = waitForMainMenuReady;
|
|
90
121
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -164,44 +195,152 @@ exports.test = test_1.test.extend({
|
|
|
164
195
|
{ scope: "worker" },
|
|
165
196
|
],
|
|
166
197
|
login: async ({ page, baseURL, _loginCache }, provide, testInfo) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
await provide(options => (0, exports.signIn)({ page, baseURL, cache: _loginCache, testInfo, options }));
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
const noopTermsWatcher = () => ({
|
|
202
|
+
hasAccepted: async () => undefined,
|
|
203
|
+
seenRequest: () => false,
|
|
204
|
+
stop: () => undefined,
|
|
205
|
+
});
|
|
206
|
+
/** Suspense fallback that sits in the host layout until the lazy `TermsGate` chunk loads. */
|
|
207
|
+
const TERMS_GATE_FALLBACK_SELECTOR = '[data-testid="manager-app-fallback"]';
|
|
208
|
+
/** How long a ready shell must stay free of a terms request before login treats the gate as skipped. */
|
|
209
|
+
const TERMS_GATE_QUIET_MS = 1000;
|
|
210
|
+
/**
|
|
211
|
+
* Starts the terms-status watcher only on a feature-branch host, and only when the
|
|
212
|
+
* page can listen for requests. An unset or empty `NX_FEATURE_BRANCH_BASE_URL` does
|
|
213
|
+
* not subscribe and does not click Accept (GLU-2632).
|
|
214
|
+
*/
|
|
215
|
+
const startFeatureBranchTermsWatch = (page) => {
|
|
216
|
+
if (!(0, hostReadiness_1.isFeatureBranchHost)() || page.on === undefined || page.off === undefined) {
|
|
217
|
+
return noopTermsWatcher();
|
|
218
|
+
}
|
|
219
|
+
return (0, acceptTerms_1.watchTermsStatus)({ on: page.on, off: page.off });
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* True while the lazy terms chunk has not replaced its Suspense fallback.
|
|
223
|
+
* A page that cannot see that node is treated as already mounted.
|
|
224
|
+
*/
|
|
225
|
+
const isTermsGateStillLoading = async (page) => {
|
|
226
|
+
const fallback = page.locator(TERMS_GATE_FALLBACK_SELECTOR);
|
|
227
|
+
if (fallback.isVisible === undefined) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return fallback.isVisible();
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Finishes the terms prompt, then waits for the main shell, inside the login budget.
|
|
234
|
+
*
|
|
235
|
+
* Root cause (GLU-2632): feature-branch `/terms` is not a finished login, and waiting
|
|
236
|
+
* for the menu never clicks the prompt. `#host-layout-content` is visible while
|
|
237
|
+
* `TermsGate` is still a Suspense fallback, so a single status sample at that moment
|
|
238
|
+
* is `undefined` and the later redirect is never clicked. The watcher stays up until
|
|
239
|
+
* the shell is ready. `hasAccepted === false` or a `/terms` URL starts the accept
|
|
240
|
+
* flow. Both steps share `deadline` ({@link MAIN_MENU_READY_TIMEOUT_MS} after `/env`
|
|
241
|
+
* is ready). If the prompt cannot be finished, or the shell never appears, before
|
|
242
|
+
* that budget ends, this throws {@link HostNeverBecameReadyError}.
|
|
243
|
+
*/
|
|
244
|
+
const finishFeatureBranchTerms = async ({ page, watcher, now, sleep: pause, deadline, }) => {
|
|
245
|
+
if (!(0, hostReadiness_1.isFeatureBranchHost)()) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
let acceptedPrompt = false;
|
|
249
|
+
let quietSince;
|
|
250
|
+
const acceptIfPrompted = async () => {
|
|
251
|
+
const pageUrl = page.url;
|
|
252
|
+
const onTerms = pageUrl !== undefined && (0, acceptTerms_1.isTermsRoute)(pageUrl());
|
|
253
|
+
// The status response stays `false` after a successful accept. Clicking again
|
|
254
|
+
// would wait for another `/terms` redirect that is not coming.
|
|
255
|
+
if (acceptedPrompt && !onTerms) {
|
|
256
|
+
return acceptedPrompt;
|
|
257
|
+
}
|
|
258
|
+
const status = await (0, acceptTerms_1.readTermsStatusWithinDeadline)(watcher, now, pause, deadline);
|
|
259
|
+
if (status !== false && !onTerms) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const getByRole = page.getByRole;
|
|
263
|
+
if (pageUrl === undefined || getByRole === undefined) {
|
|
264
|
+
throw new hostReadiness_1.HostNeverBecameReadyError();
|
|
265
|
+
}
|
|
266
|
+
await (0, acceptTerms_1.acceptTermsWithinBudget)({
|
|
267
|
+
page: {
|
|
268
|
+
url: pageUrl,
|
|
269
|
+
getByRole,
|
|
270
|
+
waitForURL: (predicate, options) => page.waitForURL(predicate, options),
|
|
271
|
+
},
|
|
272
|
+
hasAccepted: status,
|
|
273
|
+
now,
|
|
274
|
+
sleep: pause,
|
|
275
|
+
deadline,
|
|
276
|
+
});
|
|
277
|
+
return true;
|
|
278
|
+
};
|
|
279
|
+
const waitForFunction = page.waitForFunction;
|
|
280
|
+
if (waitForFunction === undefined) {
|
|
281
|
+
await acceptIfPrompted();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const shellInput = {
|
|
285
|
+
selector: exports.MAIN_MENU_READY_SELECTOR,
|
|
286
|
+
fragments: exports.HIDE_MENU_URL_FRAGMENTS,
|
|
287
|
+
termsCountsAsReady: false,
|
|
288
|
+
};
|
|
289
|
+
while (now() < deadline) {
|
|
290
|
+
const started = now();
|
|
291
|
+
acceptedPrompt = (await acceptIfPrompted()) || acceptedPrompt;
|
|
292
|
+
const remaining = deadline - now();
|
|
293
|
+
if (remaining <= 0) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
const slice = Math.min(TERMS_GATE_QUIET_MS, remaining);
|
|
297
|
+
let shellReady = false;
|
|
298
|
+
try {
|
|
299
|
+
await waitForFunction(exports.isMainMenuReady, shellInput, { timeout: slice });
|
|
300
|
+
shellReady = true;
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (!isPlaywrightTimeout(error) && now() < deadline) {
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (shellReady) {
|
|
308
|
+
acceptedPrompt = (await acceptIfPrompted()) || acceptedPrompt;
|
|
309
|
+
const onTerms = page.url !== undefined && (0, acceptTerms_1.isTermsRoute)(page.url());
|
|
310
|
+
const status = await (0, acceptTerms_1.readTermsStatusWithinDeadline)(watcher, now, pause, deadline);
|
|
311
|
+
if (!onTerms && (acceptedPrompt || status === true)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (!onTerms && status !== false && !watcher.seenRequest() && !(await isTermsGateStillLoading(page))) {
|
|
315
|
+
if (quietSince === undefined) {
|
|
316
|
+
quietSince = now();
|
|
181
317
|
}
|
|
182
|
-
if (
|
|
183
|
-
await (0, exports.waitForMainMenuReady)(page, testInfo);
|
|
318
|
+
if (now() - quietSince >= TERMS_GATE_QUIET_MS) {
|
|
184
319
|
return;
|
|
185
320
|
}
|
|
186
321
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
322
|
+
else {
|
|
323
|
+
quietSince = undefined;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (now() >= deadline) {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
if (now() === started) {
|
|
330
|
+
const mark = `${page.url?.() ?? ""}|${watcher.seenRequest()}|${acceptedPrompt}`;
|
|
331
|
+
const left = deadline - now();
|
|
332
|
+
if (left <= 0) {
|
|
333
|
+
break;
|
|
196
334
|
}
|
|
197
|
-
|
|
198
|
-
|
|
335
|
+
await pause(Math.min(TERMS_GATE_QUIET_MS, left));
|
|
336
|
+
const after = `${page.url?.() ?? ""}|${watcher.seenRequest()}|${acceptedPrompt}`;
|
|
337
|
+
if (now() === started && after === mark) {
|
|
338
|
+
throw new hostReadiness_1.HostNeverBecameReadyError();
|
|
199
339
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
throw new hostReadiness_1.HostNeverBecameReadyError();
|
|
343
|
+
};
|
|
205
344
|
const openDefaultRequestContext = async (baseURL) => {
|
|
206
345
|
const context = await test_1.request.newContext({ baseURL });
|
|
207
346
|
return {
|
|
@@ -318,14 +457,18 @@ const gotoSessionDocument = async (page, url, now, deadline, pause) => {
|
|
|
318
457
|
/**
|
|
319
458
|
* Performs the slow path: wait for the branch host to serve `/env`, hit Okta's
|
|
320
459
|
* `/api/v1/authn`, redeem the `sessionToken` via the manager-classic redirect,
|
|
321
|
-
* and wait for the host layout to render.
|
|
322
|
-
*
|
|
460
|
+
* and wait for the host layout to render. On a feature-branch host it then
|
|
461
|
+
* finishes the terms prompt and waits for the main shell inside the same
|
|
462
|
+
* budget. Factored out so the cache hit/miss branches read clearly.
|
|
323
463
|
*
|
|
324
464
|
* `createRequestContext`, `now`, and `sleep` are injectable so a unit test can
|
|
325
|
-
* drive the session handoff without a browser or a real
|
|
465
|
+
* drive the session handoff and the terms prompt without a browser or a real
|
|
466
|
+
* three-minute wait.
|
|
326
467
|
*/
|
|
327
468
|
const performOktaLogin = async ({ page, baseURL, credentials, testInfo, createRequestContext = openDefaultRequestContext, now = Date.now, sleep: pause = sleep, }) => {
|
|
328
469
|
const context = await createRequestContext(baseURL);
|
|
470
|
+
// Before the session navigation: the menu can render before TermsGate redirects.
|
|
471
|
+
const termsWatcher = startFeatureBranchTermsWatch(page);
|
|
329
472
|
try {
|
|
330
473
|
const env = await readHostEnv(context, baseURL, testInfo);
|
|
331
474
|
const domainValue = env.auth?.url;
|
|
@@ -333,9 +476,10 @@ const performOktaLogin = async ({ page, baseURL, credentials, testInfo, createRe
|
|
|
333
476
|
throw new Error(`No domain found from servers /env found env: ${JSON.stringify(env)}`);
|
|
334
477
|
}
|
|
335
478
|
const domain = domainValue;
|
|
336
|
-
// Started once `/env` is ready. The Okta post, the session document
|
|
337
|
-
// handoff
|
|
338
|
-
//
|
|
479
|
+
// Started once `/env` is ready. The Okta post, the session document, the
|
|
480
|
+
// handoff, the terms prompt and the shell wait share this window. Only the
|
|
481
|
+
// document, the handoff and the prompt are retried against it; the post keeps
|
|
482
|
+
// its own fixed timeout (GLU-2631, GLU-2632).
|
|
339
483
|
const readyDeadline = (0, hostReadiness_1.isFeatureBranchHost)() ? now() + exports.MAIN_MENU_READY_TIMEOUT_MS : undefined;
|
|
340
484
|
const authResponse = await context.post(`${domain}/api/v1/authn`, {
|
|
341
485
|
data: {
|
|
@@ -357,12 +501,89 @@ const performOktaLogin = async ({ page, baseURL, credentials, testInfo, createRe
|
|
|
357
501
|
await gotoSessionDocument(page, `/auth/manager-classic#session_token=${sessionToken}&userProfile=true`, now, readyDeadline, pause);
|
|
358
502
|
await waitForSessionHandoff(page, now, readyDeadline);
|
|
359
503
|
await page.locator("#host-layout-content").waitFor({ state: "visible", timeout: 15000 });
|
|
504
|
+
if (readyDeadline !== undefined) {
|
|
505
|
+
await finishFeatureBranchTerms({
|
|
506
|
+
page,
|
|
507
|
+
watcher: termsWatcher,
|
|
508
|
+
now,
|
|
509
|
+
sleep: pause,
|
|
510
|
+
deadline: readyDeadline,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
360
513
|
}
|
|
361
514
|
finally {
|
|
515
|
+
termsWatcher.stop();
|
|
362
516
|
await context.dispose();
|
|
363
517
|
}
|
|
364
518
|
};
|
|
365
519
|
exports.performOktaLogin = performOktaLogin;
|
|
520
|
+
/**
|
|
521
|
+
* Signs in, reusing this worker's cached session when one exists.
|
|
522
|
+
*
|
|
523
|
+
* `now` and `sleep` are injectable so a unit test can drive the restored
|
|
524
|
+
* feature-branch terms wait without a browser or a real three-minute clock.
|
|
525
|
+
* Production uses `Date.now` and the event loop.
|
|
526
|
+
*/
|
|
527
|
+
const signIn = async ({ page, baseURL, cache, testInfo, options, now = Date.now, sleep: pause = sleep, createRequestContext, }) => {
|
|
528
|
+
const credentials = await (0, resolveCredentials_1.resolveCredentials)(options);
|
|
529
|
+
const cacheKey = (0, loginCacheKey_1.buildLoginCacheKey)(baseURL, credentials);
|
|
530
|
+
const cached = cache.get(cacheKey);
|
|
531
|
+
if (cached !== undefined) {
|
|
532
|
+
let cachedSessionRestored = false;
|
|
533
|
+
const termsWatcher = startFeatureBranchTermsWatch(page);
|
|
534
|
+
try {
|
|
535
|
+
try {
|
|
536
|
+
await (0, hydrateStorageState_1.hydrateStorageState)(page.context(), cached);
|
|
537
|
+
await page.goto("/");
|
|
538
|
+
await page.locator("#host-layout-content").waitFor({ state: "visible", timeout: 15000 });
|
|
539
|
+
if ((0, hostReadiness_1.isFeatureBranchHost)()) {
|
|
540
|
+
// The terms wait can use the whole three-minute budget. Reserve it
|
|
541
|
+
// before that wait: the miss-path call below is skipped when restore
|
|
542
|
+
// succeeds, and the menu wait extends the timeout only after this returns.
|
|
543
|
+
(0, exports.reserveFeatureBranchLoginBudget)(testInfo);
|
|
544
|
+
const deadline = now() + exports.MAIN_MENU_READY_TIMEOUT_MS;
|
|
545
|
+
await finishFeatureBranchTerms({
|
|
546
|
+
page,
|
|
547
|
+
watcher: termsWatcher,
|
|
548
|
+
now,
|
|
549
|
+
sleep: pause,
|
|
550
|
+
deadline,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
cachedSessionRestored = true;
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
if (error instanceof hostReadiness_1.HostNeverBecameReadyError) {
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
cache.delete(cacheKey);
|
|
560
|
+
}
|
|
561
|
+
if (cachedSessionRestored) {
|
|
562
|
+
await (0, exports.waitForMainMenuReady)(page, testInfo);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
termsWatcher.stop();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
(0, exports.reserveFeatureBranchLoginBudget)(testInfo);
|
|
571
|
+
await (0, exports.performOktaLogin)({ page, baseURL, credentials, testInfo, now, sleep: pause, createRequestContext });
|
|
572
|
+
await (0, exports.waitForMainMenuReady)(page, testInfo);
|
|
573
|
+
// Capture only after the shell is ready. A feature-branch host can sit
|
|
574
|
+
// on `/terms` with the layout mounted; storing that snapshot would reuse
|
|
575
|
+
// a session whose prompt is not finished (GLU-2632). Errors here would
|
|
576
|
+
// mask the actual login success, so snapshot capture never throws — at
|
|
577
|
+
// worst we'll re-hit Okta next time, which is the pre-cache behaviour.
|
|
578
|
+
try {
|
|
579
|
+
const snapshot = await page.context().storageState();
|
|
580
|
+
cache.set(cacheKey, snapshot);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
/* Snapshot capture is best-effort; failing here is non-fatal. */
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
exports.signIn = signIn;
|
|
366
587
|
var test_2 = require("@playwright/test");
|
|
367
588
|
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
|
|
368
589
|
//# sourceMappingURL=login.fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.fixture.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/login.fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { errors, request, test as base } from \"@playwright/test\";\nimport {\n acceptTermsWithinBudget,\n isTermsRoute,\n readTermsStatusWithinDeadline,\n watchTermsStatus,\n type TermsStatusPage,\n type TermsStatusWatcher,\n} from \"./acceptTerms\";\nimport { HostNeverBecameReadyError, isFeatureBranchHost } from \"./hostReadiness\";\nimport { hydrateStorageState, type CachedStorageState, type StorageStateHydrator } from \"./hydrateStorageState\";\nimport { buildLoginCacheKey } from \"./loginCacheKey\";\nimport { resolveCredentials, type AuthCredentials, type ResolveCredentialsOptions } from \"./resolveCredentials\";\n\nexport type { AuthCredentials };\n\nexport type LoginOptions = ResolveCredentialsOptions;\n\n/** Matches HostLayout's `hideMenuForUrls` — routes where MainMenu is not rendered. */\nexport const HIDE_MENU_URL_FRAGMENTS = [\"/marketplace\", \"/auth/\", \"/terms\"] as const;\n\nexport const MAIN_MENU_READY_TIMEOUT_MS = 180_000;\n\n/**\n * Default budget for the first login on a worker to wait for the branch host\n * to answer `GET /env` with 200. The deploy job only waits for ArgoCD health,\n * which does not cover the Emissary/Envoy route for a freshly created feature\n * branch, so the host can return 404 for several minutes after the deploy job\n * is green. Override per run with {@link ENV_READY_TIMEOUT_ENV_VAR}.\n */\nexport const ENV_READY_TIMEOUT_MS = 300_000;\n\n/**\n * Environment variable that overrides {@link ENV_READY_TIMEOUT_MS} (in\n * milliseconds). Useful for local runs against a fixed host where a long wait\n * only delays the inevitable failure.\n */\nexport const ENV_READY_TIMEOUT_ENV_VAR = \"NX_E2E_ENV_READY_TIMEOUT_MS\";\n\nexport const ENV_READY_POLL_INTERVAL_MS = 5_000;\n\n/** Floor for a single `/env` request so the last attempt before the deadline is still meaningful. */\nconst ENV_READY_MIN_REQUEST_TIMEOUT_MS = 1_000;\n\n/** Fixed timeout for Okta's `/api/v1/authn` call; it is not retried against the login budget. */\nconst OKTA_AUTH_TIMEOUT_MS = 20_000;\n\n/** Single wait for the manager-classic session handoff to leave that page. */\nconst SESSION_HANDOFF_TIMEOUT_MS = 20_000;\n\n/** Gap between reloads of a manager-classic session document that did not load. */\nexport const SESSION_DOCUMENT_RETRY_DELAY_MS = 1_000;\n\nconst resolveEnvReadyTimeoutMs = (): number => {\n const raw = process.env[ENV_READY_TIMEOUT_ENV_VAR];\n if (raw === undefined || raw.trim() === \"\") {\n return ENV_READY_TIMEOUT_MS;\n }\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : ENV_READY_TIMEOUT_MS;\n};\n\nexport const MAIN_MENU_READY_SELECTOR =\n '[data-testid=\"mainMenu\"]:not(:has([data-testid=\"mainMenuItems-skeleton\"], [data-testid=\"menu-items-skeleton\"]))';\n\ntype MainMenuReadinessInput = {\n readonly selector: string;\n readonly fragments: ReadonlyArray<string>;\n /**\n * False on a feature-branch host, so `/terms` is not a finished login.\n * {@link waitForMainMenuReady} always sets this in Node. The predicate runs\n * in the browser, so the default must not call {@link isFeatureBranchHost}:\n * that closure is not serialized, and a dropped argument would throw there.\n * Omitted means ready, which is the staging hide-menu behaviour.\n */\n readonly termsCountsAsReady?: boolean;\n};\n\ntype MainMenuWaitPage = {\n waitForFunction: (\n pageFunction: (input: MainMenuReadinessInput) => boolean,\n input: MainMenuReadinessInput,\n options?: { timeout?: number }\n ) => Promise<unknown>;\n};\n\ntype TestTimeoutInfo = {\n timeout: number;\n setTimeout: (timeout: number) => void;\n};\n\n/**\n * Grows the running test's timeout by `budgetMs` so a readiness wait cannot\n * eat into the test's own budget. A timeout of 0 means \"unlimited\" in\n * Playwright and is left untouched.\n */\nexport const extendTestTimeout = (testInfo: TestTimeoutInfo | undefined, budgetMs: number): void => {\n if (testInfo !== undefined && testInfo.timeout !== 0) {\n testInfo.setTimeout(testInfo.timeout + budgetMs);\n }\n};\n\n/**\n * Gives the feature-branch session handoff its own readiness budget.\n *\n * Root cause (GLU-2631): after `/env` answers, a feature-branch host can still\n * serve a failed session document or stay on `manager-classic` for a while.\n * Those waits share {@link MAIN_MENU_READY_TIMEOUT_MS} after `/env` is ready,\n * so the test timeout is extended by that budget before the slow path. The\n * `/env` wait extends it by its own budget. A host that is not a feature\n * branch is unchanged.\n */\nexport const reserveFeatureBranchLoginBudget = (testInfo?: TestTimeoutInfo): void => {\n if (!isFeatureBranchHost()) {\n return;\n }\n extendTestTimeout(testInfo, MAIN_MENU_READY_TIMEOUT_MS);\n};\n\n/**\n * Returns whether the main menu is ready or intentionally hidden for the current route.\n *\n * Root cause (GLU-2632): `/terms` is in {@link HIDE_MENU_URL_FRAGMENTS}, so every\n * host treated the unfinished terms prompt as a finished login. Callers on a\n * feature-branch host pass `termsCountsAsReady: false`, so a URL containing\n * `/terms` is not ready even if a menu node is present. An omitted flag stays\n * ready, which keeps today's behaviour for every other host.\n */\nexport const isMainMenuReady = ({\n selector,\n fragments,\n termsCountsAsReady = true,\n}: MainMenuReadinessInput): boolean => {\n if (!termsCountsAsReady && window.location.pathname.includes(\"/terms\")) {\n return false;\n }\n if (document.querySelector(selector) !== null) {\n return true;\n }\n return fragments.some(fragment => window.location.pathname.includes(fragment));\n};\n\n/**\n * Waits until Manager's main menu is rendered without a loading skeleton, or\n * until the browser is on a HostLayout hide-menu route (marketplace / auth /\n * terms) where the menu is intentionally absent.\n *\n * On a feature-branch host, `/terms` is excluded: the prompt is not a finished\n * login (GLU-2632). The flag is computed here, in Node, and passed in because\n * {@link isMainMenuReady} is evaluated in the page.\n */\nexport const waitForMainMenuReady = async (page: MainMenuWaitPage, testInfo?: TestTimeoutInfo): Promise<void> => {\n extendTestTimeout(testInfo, MAIN_MENU_READY_TIMEOUT_MS);\n\n try {\n await page.waitForFunction(\n isMainMenuReady,\n {\n selector: MAIN_MENU_READY_SELECTOR,\n fragments: HIDE_MENU_URL_FRAGMENTS,\n termsCountsAsReady: !isFeatureBranchHost(),\n },\n { timeout: MAIN_MENU_READY_TIMEOUT_MS }\n );\n } catch (error) {\n // The terms prompt can redirect after the inner shell wait has returned.\n // On a feature-branch host that timeout is the host never becoming ready,\n // before any page assertion. Other hosts keep Playwright's timeout.\n if (isFeatureBranchHost() && error instanceof errors.TimeoutError) {\n throw new HostNeverBecameReadyError(error);\n }\n throw error;\n }\n};\n\ntype EnvReadyResponse = {\n status: () => number;\n};\n\ntype EnvReadyRequestContext<TResponse extends EnvReadyResponse> = {\n get: (url: string, options?: { timeout?: number }) => Promise<TResponse>;\n};\n\nconst sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));\n\nconst describeError = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n\n/**\n * Nothing is listening on the host (local dev server not started, typo in\n * `baseURL`). Waiting will not change that, so these fail immediately instead\n * of burning the readiness budget. This relies on feature-branch hosts living\n * under a wildcard DNS record (`*.dev.manager.trackunit.com`), so a brand-new\n * branch resolves immediately and `ENOTFOUND` is never a transient state.\n */\nconst isHostUnreachableError = (error: unknown): boolean => /\\b(ECONNREFUSED|ENOTFOUND)\\b/.test(describeError(error));\n\n/** Thrown by {@link waitForEnvReady} when `/env` did not answer 200 inside its budget. */\nexport class EnvReadyTimeoutError extends Error {\n /** @param message the budget, host and last status/error, as reported by the poller */\n public constructor(message: string) {\n super(message);\n this.name = \"EnvReadyTimeoutError\";\n }\n}\n\n/**\n * Polls `GET /env` on the branch host until it returns 200 and resolves with\n * that response, untouched. Non-200 responses (404 while Envoy has no route\n * yet, 503 while pods start) and timed-out requests are retried every\n * {@link ENV_READY_POLL_INTERVAL_MS} until the budget ({@link ENV_READY_TIMEOUT_MS},\n * or {@link ENV_READY_TIMEOUT_ENV_VAR}) is spent. The resulting error names the\n * host and the last status/error so the failure is attributable to the\n * environment rather than to the test. Connection-level failures fail fast.\n */\nexport const waitForEnvReady = async <TResponse extends EnvReadyResponse>(\n context: EnvReadyRequestContext<TResponse>,\n baseURL: string | undefined,\n testInfo?: TestTimeoutInfo\n): Promise<TResponse> => {\n const timeoutMs = resolveEnvReadyTimeoutMs();\n extendTestTimeout(testInfo, timeoutMs);\n\n const envUrl = `${baseURL ?? \"\"}/env`;\n const deadline = Date.now() + timeoutMs;\n let lastOutcome = \"no response\";\n let attempted = false;\n\n for (;;) {\n const remainingMs = deadline - Date.now();\n // Always make one attempt; after that stop as soon as the budget is spent\n // so the error reports the last real outcome rather than a rushed retry.\n if (attempted && remainingMs <= 0) {\n throw new EnvReadyTimeoutError(\n `Timed out after ${timeoutMs} ms waiting for ${envUrl} to return 200 (${lastOutcome})`\n );\n }\n\n // Cap each request so a hanging connection cannot run far past the deadline.\n const requestTimeout = Math.min(\n ENV_READY_POLL_INTERVAL_MS,\n Math.max(remainingMs, ENV_READY_MIN_REQUEST_TIMEOUT_MS)\n );\n attempted = true;\n try {\n const response = await context.get(\"/env\", { timeout: requestTimeout });\n const status = response.status();\n if (status === 200) {\n return response;\n }\n lastOutcome = `last status: ${status}`;\n } catch (error) {\n if (isHostUnreachableError(error)) {\n throw new Error(`Could not reach ${envUrl}, is the server running? (${describeError(error)})`);\n }\n lastOutcome = `last error: ${describeError(error)}`;\n }\n\n await sleep(Math.max(0, Math.min(ENV_READY_POLL_INTERVAL_MS, deadline - Date.now())));\n }\n};\n\nexport interface TrackunitBaseFixtures {\n login: (options?: LoginOptions) => Promise<void>;\n}\n\n/**\n * Worker-scoped, in-memory cache of authenticated `storageState` snapshots,\n * keyed by `(baseURL, username)`. Implementation detail of the `login`\n * fixture — exposed on the fixtures object so Playwright can manage its\n * lifecycle, but prefixed with `_` to signal \"don't touch from tests\".\n */\nexport interface LoginCacheFixtures {\n _loginCache: Map<string, CachedStorageState>;\n}\n\n/**\n * Each worker is its own Node process, so a `Map` is sufficient — no\n * cross-worker sharing concerns, no disk I/O, no cleanup needed. The cache\n * dies with the worker, which is the desired behaviour: a fresh `nx run`\n * always starts with empty caches.\n */\nexport const test = base.extend<TrackunitBaseFixtures, LoginCacheFixtures>({\n _loginCache: [\n async ({}, provide) => {\n await provide(new Map<string, CachedStorageState>());\n },\n { scope: \"worker\" },\n ],\n\n login: async ({ page, baseURL, _loginCache }, provide, testInfo) => {\n await provide(options => signIn({ page, baseURL, cache: _loginCache, testInfo, options }));\n },\n});\n\ntype LoginJsonBody = { auth?: { url?: unknown }; sessionToken?: string };\n\ntype LoginEnvResponse = EnvReadyResponse & { json: () => Promise<LoginJsonBody> };\n\ntype LoginRequestContext = {\n get: (url: string, options?: { timeout?: number }) => Promise<LoginEnvResponse>;\n post: (\n url: string,\n options: {\n data: {\n username: string;\n password: string;\n options: {\n warnBeforePasswordExpired: boolean;\n multiOptionalFactorEnroll: boolean;\n };\n };\n headers: { \"Content-Type\": \"application/json\" };\n timeout: number;\n }\n ) => Promise<{ ok: () => boolean; json: () => Promise<LoginJsonBody> }>;\n dispose: () => Promise<void>;\n};\n\ntype SessionDocument = { ok: () => boolean };\n\ntype TermsButton = {\n isVisible: () => Promise<boolean>;\n click: () => Promise<void>;\n};\n\ntype OktaLoginPage = {\n goto: (url: string, options?: { timeout?: number }) => Promise<SessionDocument | null | undefined>;\n waitForURL: (url: (url: URL) => boolean, options?: { timeout?: number }) => Promise<unknown>;\n locator: (selector: string) => {\n waitFor: (options: { state: \"visible\" | \"hidden\"; timeout: number }) => Promise<unknown>;\n isVisible?: () => Promise<boolean>;\n };\n /** Present on a real Playwright page. Session-handoff fakes omit it. */\n url?: () => string;\n getByRole?: (role: \"button\", options: { name: string; exact?: boolean }) => TermsButton;\n on?: TermsStatusPage[\"on\"];\n off?: TermsStatusPage[\"off\"];\n waitForFunction?: MainMenuWaitPage[\"waitForFunction\"];\n};\n\nconst noopTermsWatcher = (): TermsStatusWatcher => ({\n hasAccepted: async () => undefined,\n seenRequest: () => false,\n stop: () => undefined,\n});\n\n/** Suspense fallback that sits in the host layout until the lazy `TermsGate` chunk loads. */\nconst TERMS_GATE_FALLBACK_SELECTOR = '[data-testid=\"manager-app-fallback\"]';\n\n/** How long a ready shell must stay free of a terms request before login treats the gate as skipped. */\nconst TERMS_GATE_QUIET_MS = 1_000;\n\n/**\n * Starts the terms-status watcher only on a feature-branch host, and only when the\n * page can listen for requests. An unset or empty `NX_FEATURE_BRANCH_BASE_URL` does\n * not subscribe and does not click Accept (GLU-2632).\n */\nconst startFeatureBranchTermsWatch = (page: OktaLoginPage): TermsStatusWatcher => {\n if (!isFeatureBranchHost() || page.on === undefined || page.off === undefined) {\n return noopTermsWatcher();\n }\n return watchTermsStatus({ on: page.on, off: page.off });\n};\n\n/**\n * True while the lazy terms chunk has not replaced its Suspense fallback.\n * A page that cannot see that node is treated as already mounted.\n */\nconst isTermsGateStillLoading = async (page: OktaLoginPage): Promise<boolean> => {\n const fallback = page.locator(TERMS_GATE_FALLBACK_SELECTOR);\n if (fallback.isVisible === undefined) {\n return false;\n }\n return fallback.isVisible();\n};\n\n/**\n * Finishes the terms prompt, then waits for the main shell, inside the login budget.\n *\n * Root cause (GLU-2632): feature-branch `/terms` is not a finished login, and waiting\n * for the menu never clicks the prompt. `#host-layout-content` is visible while\n * `TermsGate` is still a Suspense fallback, so a single status sample at that moment\n * is `undefined` and the later redirect is never clicked. The watcher stays up until\n * the shell is ready. `hasAccepted === false` or a `/terms` URL starts the accept\n * flow. Both steps share `deadline` ({@link MAIN_MENU_READY_TIMEOUT_MS} after `/env`\n * is ready). If the prompt cannot be finished, or the shell never appears, before\n * that budget ends, this throws {@link HostNeverBecameReadyError}.\n */\nconst finishFeatureBranchTerms = async ({\n page,\n watcher,\n now,\n sleep: pause,\n deadline,\n}: {\n page: OktaLoginPage;\n watcher: TermsStatusWatcher;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n deadline: number;\n}): Promise<void> => {\n if (!isFeatureBranchHost()) {\n return;\n }\n\n let acceptedPrompt = false;\n let quietSince: number | undefined;\n\n const acceptIfPrompted = async (): Promise<boolean> => {\n const pageUrl = page.url;\n const onTerms = pageUrl !== undefined && isTermsRoute(pageUrl());\n // The status response stays `false` after a successful accept. Clicking again\n // would wait for another `/terms` redirect that is not coming.\n if (acceptedPrompt && !onTerms) {\n return acceptedPrompt;\n }\n const status = await readTermsStatusWithinDeadline(watcher, now, pause, deadline);\n if (status !== false && !onTerms) {\n return false;\n }\n const getByRole = page.getByRole;\n if (pageUrl === undefined || getByRole === undefined) {\n throw new HostNeverBecameReadyError();\n }\n await acceptTermsWithinBudget({\n page: {\n url: pageUrl,\n getByRole,\n waitForURL: (predicate, options) => page.waitForURL(predicate, options),\n },\n hasAccepted: status,\n now,\n sleep: pause,\n deadline,\n });\n return true;\n };\n\n const waitForFunction = page.waitForFunction;\n if (waitForFunction === undefined) {\n await acceptIfPrompted();\n return;\n }\n\n const shellInput = {\n selector: MAIN_MENU_READY_SELECTOR,\n fragments: HIDE_MENU_URL_FRAGMENTS,\n termsCountsAsReady: false,\n };\n\n while (now() < deadline) {\n const started = now();\n acceptedPrompt = (await acceptIfPrompted()) || acceptedPrompt;\n\n const remaining = deadline - now();\n if (remaining <= 0) {\n break;\n }\n const slice = Math.min(TERMS_GATE_QUIET_MS, remaining);\n let shellReady = false;\n try {\n await waitForFunction(isMainMenuReady, shellInput, { timeout: slice });\n shellReady = true;\n } catch (error) {\n if (!isPlaywrightTimeout(error) && now() < deadline) {\n throw error;\n }\n }\n\n if (shellReady) {\n acceptedPrompt = (await acceptIfPrompted()) || acceptedPrompt;\n const onTerms = page.url !== undefined && isTermsRoute(page.url());\n const status = await readTermsStatusWithinDeadline(watcher, now, pause, deadline);\n if (!onTerms && (acceptedPrompt || status === true)) {\n return;\n }\n if (!onTerms && status !== false && !watcher.seenRequest() && !(await isTermsGateStillLoading(page))) {\n if (quietSince === undefined) {\n quietSince = now();\n }\n if (now() - quietSince >= TERMS_GATE_QUIET_MS) {\n return;\n }\n } else {\n quietSince = undefined;\n }\n }\n\n if (now() >= deadline) {\n break;\n }\n if (now() === started) {\n const mark = `${page.url?.() ?? \"\"}|${watcher.seenRequest()}|${acceptedPrompt}`;\n const left = deadline - now();\n if (left <= 0) {\n break;\n }\n await pause(Math.min(TERMS_GATE_QUIET_MS, left));\n const after = `${page.url?.() ?? \"\"}|${watcher.seenRequest()}|${acceptedPrompt}`;\n if (now() === started && after === mark) {\n throw new HostNeverBecameReadyError();\n }\n }\n }\n\n throw new HostNeverBecameReadyError();\n};\n\nconst openDefaultRequestContext = async (baseURL: string | undefined): Promise<LoginRequestContext> => {\n const context = await request.newContext({ baseURL });\n return {\n // Playwright's `get<T>` types the JSON body; without the pin it is `any`.\n get: (url, options) => context.get<LoginJsonBody>(url, options),\n post: (url, options) => context.post(url, options),\n dispose: () => context.dispose(),\n };\n};\n\n/**\n * Reads `GET /env` through {@link waitForEnvReady}.\n *\n * On a feature-branch host, spending that budget is the host never becoming\n * ready, so the timeout is rethrown as {@link HostNeverBecameReadyError} with\n * the original error as `cause`. An unreachable host still fails fast, and a\n * host that is not a feature branch keeps the `/env` timeout message.\n */\nconst readHostEnv = async (\n context: LoginRequestContext,\n baseURL: string | undefined,\n testInfo: TestTimeoutInfo | undefined\n): Promise<LoginJsonBody> => {\n try {\n const envResponse = await waitForEnvReady(context, baseURL, testInfo);\n return await envResponse.json();\n } catch (error) {\n if (isFeatureBranchHost() && error instanceof EnvReadyTimeoutError) {\n throw new HostNeverBecameReadyError(error);\n }\n throw error;\n }\n};\n\nconst leftManagerClassic = (url: URL): boolean => !url.href.includes(\"manager-classic\");\n\nconst isPlaywrightTimeout = (error: unknown): boolean => error instanceof errors.TimeoutError;\n\n/**\n * Waits until the manager-classic session handoff leaves that page.\n *\n * Root cause (GLU-2631): feature-branch login waited once for 20s. While that\n * host is still booting, the URL stays on manager-classic and `waitForURL`\n * throws a navigation timeout, so the rest of the readiness budget was never\n * used. When the feature-branch gate is on, retry until the URL leaves that\n * page or the deadline ends, then throw {@link HostNeverBecameReadyError}.\n * Each attempt waits up to 20s, capped by the time left, so the loop does not\n * busy-spin. An unset or empty `NX_FEATURE_BRANCH_BASE_URL` keeps the single\n * 20s wait.\n */\nconst waitForSessionHandoff = async (\n page: OktaLoginPage,\n now: () => number,\n deadline: number | undefined\n): Promise<void> => {\n if (deadline === undefined) {\n await page.waitForURL(leftManagerClassic, { timeout: SESSION_HANDOFF_TIMEOUT_MS });\n return;\n }\n\n let current = now();\n let lastTimeout: unknown;\n while (current < deadline) {\n const timeout = Math.min(SESSION_HANDOFF_TIMEOUT_MS, deadline - current);\n try {\n await page.waitForURL(leftManagerClassic, { timeout });\n return;\n } catch (error) {\n if (!isPlaywrightTimeout(error)) {\n throw error;\n }\n lastTimeout = error;\n }\n current = now();\n }\n\n throw new HostNeverBecameReadyError(lastTimeout);\n};\n\nconst documentLoaded = (response: SessionDocument | null | undefined): boolean => {\n if (response === null || response === undefined) {\n return false;\n }\n return response.ok();\n};\n\n/**\n * Opens the manager-classic session URL.\n *\n * A feature-branch host that is still booting can answer `page.goto` with a\n * 502 or an empty document. The URL stays on that page, so waiting for it to\n * change never sees a later healthy load. Retry the navigation until the\n * document loads or the deadline ends. A navigation that uses up the time\n * left throws {@link HostNeverBecameReadyError}. The same session URL is\n * reused: a failed document does not redeem the hash token. With the gate off,\n * this is one `page.goto` and the browser's own navigation timeout.\n */\nconst gotoSessionDocument = async (\n page: OktaLoginPage,\n url: string,\n now: () => number,\n deadline: number | undefined,\n pause: (ms: number) => Promise<void>\n): Promise<void> => {\n if (deadline === undefined) {\n await page.goto(url);\n return;\n }\n\n while (now() < deadline) {\n const remaining = deadline - now();\n if (remaining <= 0) {\n break;\n }\n try {\n const response = await page.goto(url, { timeout: remaining });\n if (documentLoaded(response)) {\n return;\n }\n } catch (error) {\n if (!isPlaywrightTimeout(error)) {\n throw error;\n }\n throw new HostNeverBecameReadyError(error);\n }\n const remainingAfter = deadline - now();\n if (remainingAfter <= 0) {\n break;\n }\n await pause(Math.min(SESSION_DOCUMENT_RETRY_DELAY_MS, remainingAfter));\n }\n\n throw new HostNeverBecameReadyError();\n};\n\n/**\n * Performs the slow path: wait for the branch host to serve `/env`, hit Okta's\n * `/api/v1/authn`, redeem the `sessionToken` via the manager-classic redirect,\n * and wait for the host layout to render. On a feature-branch host it then\n * finishes the terms prompt and waits for the main shell inside the same\n * budget. Factored out so the cache hit/miss branches read clearly.\n *\n * `createRequestContext`, `now`, and `sleep` are injectable so a unit test can\n * drive the session handoff and the terms prompt without a browser or a real\n * three-minute wait.\n */\nexport const performOktaLogin = async ({\n page,\n baseURL,\n credentials,\n testInfo,\n createRequestContext = openDefaultRequestContext,\n now = Date.now,\n sleep: pause = sleep,\n}: {\n page: OktaLoginPage;\n baseURL: string | undefined;\n credentials: AuthCredentials;\n testInfo?: TestTimeoutInfo;\n createRequestContext?: (baseURL: string | undefined) => Promise<LoginRequestContext>;\n /** Injectable clock. Production uses `Date.now`. The handoff budget is {@link MAIN_MENU_READY_TIMEOUT_MS}. */\n now?: () => number;\n /** Injectable pause between session document reloads. Production waits on the event loop. */\n sleep?: (ms: number) => Promise<void>;\n}): Promise<void> => {\n const context = await createRequestContext(baseURL);\n // Before the session navigation: the menu can render before TermsGate redirects.\n const termsWatcher = startFeatureBranchTermsWatch(page);\n try {\n const env = await readHostEnv(context, baseURL, testInfo);\n const domainValue: unknown = env.auth?.url;\n if (typeof domainValue !== \"string\" || domainValue === \"\") {\n throw new Error(`No domain found from servers /env found env: ${JSON.stringify(env)}`);\n }\n const domain = domainValue;\n\n // Started once `/env` is ready. The Okta post, the session document, the\n // handoff, the terms prompt and the shell wait share this window. Only the\n // document, the handoff and the prompt are retried against it; the post keeps\n // its own fixed timeout (GLU-2631, GLU-2632).\n const readyDeadline = isFeatureBranchHost() ? now() + MAIN_MENU_READY_TIMEOUT_MS : undefined;\n\n const authResponse = await context.post(`${domain}/api/v1/authn`, {\n data: {\n username: credentials.username,\n password: credentials.password,\n options: {\n warnBeforePasswordExpired: true,\n multiOptionalFactorEnroll: false,\n },\n },\n headers: { \"Content-Type\": \"application/json\" },\n timeout: OKTA_AUTH_TIMEOUT_MS,\n });\n\n if (!authResponse.ok()) {\n const body = await authResponse.json().catch(() => ({}));\n throw new Error(`Could not get a session token for user: ${credentials.username}, ${JSON.stringify(body)}`);\n }\n\n const { sessionToken } = await authResponse.json();\n await gotoSessionDocument(\n page,\n `/auth/manager-classic#session_token=${sessionToken}&userProfile=true`,\n now,\n readyDeadline,\n pause\n );\n await waitForSessionHandoff(page, now, readyDeadline);\n await page.locator(\"#host-layout-content\").waitFor({ state: \"visible\", timeout: 15000 });\n if (readyDeadline !== undefined) {\n await finishFeatureBranchTerms({\n page,\n watcher: termsWatcher,\n now,\n sleep: pause,\n deadline: readyDeadline,\n });\n }\n } finally {\n termsWatcher.stop();\n await context.dispose();\n }\n};\n\ntype SignInPage = Omit<OktaLoginPage, \"waitForFunction\"> & {\n context: () => StorageStateHydrator & { storageState: () => Promise<CachedStorageState> };\n /** Required so the shell wait after a restored session can run in the page. */\n waitForFunction: MainMenuWaitPage[\"waitForFunction\"];\n};\n\n/**\n * Signs in, reusing this worker's cached session when one exists.\n *\n * `now` and `sleep` are injectable so a unit test can drive the restored\n * feature-branch terms wait without a browser or a real three-minute clock.\n * Production uses `Date.now` and the event loop.\n */\nexport const signIn = async ({\n page,\n baseURL,\n cache,\n testInfo,\n options,\n now = Date.now,\n sleep: pause = sleep,\n createRequestContext,\n}: {\n page: SignInPage;\n baseURL: string | undefined;\n cache: Map<string, CachedStorageState>;\n testInfo?: TestTimeoutInfo;\n options?: LoginOptions;\n now?: () => number;\n sleep?: (ms: number) => Promise<void>;\n createRequestContext?: (baseURL: string | undefined) => Promise<LoginRequestContext>;\n}): Promise<void> => {\n const credentials = await resolveCredentials(options);\n const cacheKey = buildLoginCacheKey(baseURL, credentials);\n const cached = cache.get(cacheKey);\n\n if (cached !== undefined) {\n let cachedSessionRestored = false;\n const termsWatcher = startFeatureBranchTermsWatch(page);\n try {\n try {\n await hydrateStorageState(page.context(), cached);\n await page.goto(\"/\");\n await page.locator(\"#host-layout-content\").waitFor({ state: \"visible\", timeout: 15000 });\n if (isFeatureBranchHost()) {\n // The terms wait can use the whole three-minute budget. Reserve it\n // before that wait: the miss-path call below is skipped when restore\n // succeeds, and the menu wait extends the timeout only after this returns.\n reserveFeatureBranchLoginBudget(testInfo);\n const deadline = now() + MAIN_MENU_READY_TIMEOUT_MS;\n await finishFeatureBranchTerms({\n page,\n watcher: termsWatcher,\n now,\n sleep: pause,\n deadline,\n });\n }\n cachedSessionRestored = true;\n } catch (error) {\n if (error instanceof HostNeverBecameReadyError) {\n throw error;\n }\n cache.delete(cacheKey);\n }\n\n if (cachedSessionRestored) {\n await waitForMainMenuReady(page, testInfo);\n return;\n }\n } finally {\n termsWatcher.stop();\n }\n }\n\n reserveFeatureBranchLoginBudget(testInfo);\n await performOktaLogin({ page, baseURL, credentials, testInfo, now, sleep: pause, createRequestContext });\n await waitForMainMenuReady(page, testInfo);\n\n // Capture only after the shell is ready. A feature-branch host can sit\n // on `/terms` with the layout mounted; storing that snapshot would reuse\n // a session whose prompt is not finished (GLU-2632). Errors here would\n // mask the actual login success, so snapshot capture never throws — at\n // worst we'll re-hit Okta next time, which is the pre-cache behaviour.\n try {\n const snapshot = await page.context().storageState();\n cache.set(cacheKey, snapshot);\n } catch {\n /* Snapshot capture is best-effort; failing here is non-fatal. */\n }\n};\n\nexport { expect } from \"@playwright/test\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loginCacheKey.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/loginCacheKey.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACI,MAAM,kBAAkB,GAAG,CAAC,OAA2B,EAAE,WAA4B,EAAU,EAAE,CACtG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;AADlE,QAAA,kBAAkB,sBACgD","sourcesContent":["import type { AuthCredentials } from \"./resolveCredentials\";\n\n/**\n * Builds the lookup key used by the worker-scoped login cache.\n *\n * The cache reuses an authenticated `storageState` snapshot across every test\n * within a Playwright worker that logs in as the same `(baseURL, username)`\n * pair. Two principles drive the key shape:\n *\n * 1. **Username, not username+password.** Different passwords for the same\n * user are almost always a credential rotation, not a deliberate second\n * identity — if a test really needs different identities it will use a\n * different `username`. Including the password would make a typo silently\n * cache-miss instead of failing loudly, and would also serialise it into\n * the key string. Kept out for both correctness and minor hygiene.\n * 2. **`baseURL` is required.** Okta sessions are domain-bound (the auth\n * cookies are set on the manager origin), so a snapshot from\n * `https://dev.manager.trackunit.com` cannot authenticate a request to\n * `https://stage.manager.trackunit.com`. We always partition by baseURL.\n *\n * `JSON.stringify` over a fixed-shape object guarantees no collision between\n * (undefined baseURL, username \"X\") and (baseURL \"X\", undefined-equivalent\n * username) — `null` and `\"X\"` serialise distinctly, and the field names\n * anchor each value to its slot.\n */\nexport const buildLoginCacheKey = (baseURL: string | undefined, credentials: AuthCredentials): string =>\n JSON.stringify({ baseURL: baseURL ?? null, username: credentials.username });\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveCredentials.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/resolveCredentials.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\nimport * as path from \"path\";\nimport { resolveE2EPassword } from \"../credentials/resolveE2EPassword\";\n\nexport interface AuthCredentials {\n username: string;\n password: string;\n}\n\nexport interface ResolveCredentialsOptions {\n credentials?: AuthCredentials;\n /**\n * Path to a JSON fixture identifying the account. Either name-only\n * (`{ username }`, password resolved at run time) or a full\n * `{ username, password }`. Defaults to `playwright/fixtures/auth.json`\n * relative to cwd.\n */\n fixturePath?: string;\n}\n\nconst isAuthCredentials = (value: unknown): value is AuthCredentials => {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const record: Record<string, unknown> = { ...value };\n return typeof record.username === \"string\" && typeof record.password === \"string\";\n};\n\nconst readFixtureCredentials = (fixturePath: string): AuthCredentials | undefined => {\n if (!existsSync(fixturePath)) {\n return undefined;\n }\n const parsed: unknown = JSON.parse(readFileSync(fixturePath, \"utf-8\"));\n if (!isAuthCredentials(parsed)) {\n return undefined;\n }\n if (parsed.username === \"REPLACE_ME\" || parsed.password === \"REPLACE_ME\") {\n return undefined;\n }\n return parsed;\n};\n\n/**\n * Reads a fixture's `username` when present and usable (non-blank, not the\n * `REPLACE_ME` placeholder), even if the fixture has no `password` — i.e. a\n * name-only fixture, which is what remains once passwords leave the repo.\n */\nconst readFixtureUsername = (fixturePath: string): string | undefined => {\n if (!existsSync(fixturePath)) {\n return undefined;\n }\n const parsed: unknown = JSON.parse(readFileSync(fixturePath, \"utf-8\"));\n if (typeof parsed !== \"object\" || parsed === null) {\n return undefined;\n }\n const record: Record<string, unknown> = { ...parsed };\n const username = record.username;\n if (typeof username !== \"string\" || username === \"\" || username === \"REPLACE_ME\") {\n return undefined;\n }\n return username;\n};\n\n/**\n * Resolves auth credentials for a **single** account (one `login()` call logs in\n * as one user), from the first source that yields a complete pair, tried in\n * order:\n *\n * 1. explicit `options.credentials`;\n * 2. a full JSON fixture (`{ username, password }`);\n * 3. a **name-only** fixture (`{ username }`), whose password is resolved at\n * `login()` time by {@link resolveE2EPassword} — env-first (`E2E_PW_<SLUG>`,\n * set by the CircleCI context in CI) then Vault locally. No `globalSetup` is\n * involved, so this works in `e2e-ui` mode too.\n *\n * Throws if none yield a complete pair. Async because the name-only path (step 3)\n * may reach out to Vault.\n */\nexport const resolveCredentials = async (options?: ResolveCredentialsOptions): Promise<AuthCredentials> => {\n if (options?.credentials !== undefined) {\n return options.credentials;\n }\n const fixturePath = options?.fixturePath ?? path.join(process.cwd(), \"playwright\", \"fixtures\", \"auth.json\");\n const fromFixture = readFixtureCredentials(fixturePath);\n if (fromFixture !== undefined) {\n return fromFixture;\n }\n // Name-only fixture: resolve this one account's password on demand. Throws a\n // clear, account-named error when neither env var nor Vault yields a value.\n const username = readFixtureUsername(fixturePath);\n if (username !== undefined) {\n const password = await resolveE2EPassword(username);\n return { username, password };\n }\n throw new Error(\n `No credentials provided to login(). Tried: options.credentials and ${fixturePath} ` +\n `(a full { username, password } fixture, or a name-only { username } fixture resolved via its ` +\n `E2E_PW_<slug> env var / Vault). Fill in the fixture or pass credentials to login().`\n );\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withTimeout.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/withTimeout.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACI,MAAM,WAAW,GAAG,KAAK,EAAW,SAA2B,EAAE,EAAU,EAAoB,EAAE;IACtG,IAAI,KAAgD,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC;YAAS,CAAC;QACT,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAZW,QAAA,WAAW,eAYtB","sourcesContent":["/**\n * Races `operation` against a timer, rejecting if it doesn't settle within `ms`.\n *\n * Note: this does not cancel `operation` — it keeps running in the background.\n * That is intentional for the HAR teardown path: a bounded `context.close()`\n * returns control so Playwright's built-in context teardown can await the real\n * close within its own budget, rather than our override hogging the shared\n * teardown budget that native video/trace persistence depends on.\n */\nexport const withTimeout = async <TResult>(operation: Promise<TResult>, ms: number): Promise<TResult> => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(`withTimeout: operation exceeded ${ms}ms`)), ms);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n }\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../../libs/iris-app-sdk/iris-app-playwright/src/generators/playwright-configuration/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n formatFiles,\n generateFiles,\n joinPathFragments,\n logger,\n readProjectConfiguration,\n Tree,\n updateJson,\n updateProjectConfiguration,\n} from \"@nx/devkit\";\nimport { PlaywrightConfigurationGeneratorSchema } from \"./schema\";\n\nconst PLAYWRIGHT_TSCONFIG_REFERENCE = \"./playwright/tsconfig.json\";\n\ninterface TsConfigReference {\n path: string;\n}\n\ninterface TsConfigWithReferences {\n references?: Array<TsConfigReference>;\n}\n\n/**\n * Registers the scaffolded `playwright/tsconfig.json` as a project reference in the\n * project's root `tsconfig.json`. Without this, the playwright tsconfig is orphaned:\n * a solution-style root tsconfig only type-checks what it references, so editors and\n * `tsc-validate` never pick up the spec files.\n *\n * No-ops when the project has no root `tsconfig.json`, or when the reference is already\n * present (so re-runs stay idempotent).\n */\nfunction addPlaywrightTsConfigReference(tree: Tree, projectRoot: string): void {\n const tsConfigPath = joinPathFragments(projectRoot, \"tsconfig.json\");\n if (!tree.exists(tsConfigPath)) {\n return;\n }\n\n updateJson<TsConfigWithReferences>(tree, tsConfigPath, tsConfig => {\n const references = tsConfig.references ?? [];\n const alreadyReferenced = references.some(reference => reference.path === PLAYWRIGHT_TSCONFIG_REFERENCE);\n if (alreadyReferenced) {\n return tsConfig;\n }\n\n return {\n ...tsConfig,\n references: [...references, { path: PLAYWRIGHT_TSCONFIG_REFERENCE }],\n };\n });\n}\n\n/**\n * Generates Playwright E2E test configuration for an existing project.\n *\n * Sets up Playwright end-to-end testing infrastructure within an existing project,\n * including configuration files, test targets, and template files that use the public\n * iris-app-playwright library. Does not modify any existing Cypress configuration.\n *\n * @param tree - The NX virtual file system tree\n * @param options - Configuration options for the generator\n * @returns {Promise<() => void>} A function that logs completion message with the path to the generated test file\n * @example\n * ```bash\n * nx generate @trackunit/iris-app-playwright:playwright-configuration --project my-app\n * ```\n */\nexport async function playwrightConfigurationGenerator(\n tree: Tree,\n options: PlaywrightConfigurationGeneratorSchema\n): Promise<() => void> {\n const projectConfig = readProjectConfiguration(tree, options.project);\n const projectRoot = projectConfig.root;\n\n const playwrightConfigPath = joinPathFragments(projectRoot, \"playwright.config.ts\");\n if (tree.exists(playwrightConfigPath)) {\n throw new Error(\n `playwright.config.ts already exists at ${playwrightConfigPath}. Remove it before running this generator.`\n );\n }\n\n const existingProjectConfig = projectConfig;\n\n // We use `nx:run-commands` (not `@nx/playwright:playwright`) because that executor\n // does not honour an `env` option in target configurations — it forwards unknown options\n // as Playwright CLI flags, so `BASE_URL` would never reach `process.env`.\n // `nx:run-commands` sets the configuration's `env` on the spawned process, which is what\n // `defaultPlaywrightConfig()` reads.\n //\n // `cwd` is pinned to `projectRoot` so `process.cwd()` inside Playwright matches the\n // project directory. `login()`'s default fixture path is\n // `path.join(process.cwd(), \"playwright\", \"fixtures\", \"auth.json\")`, and the generator\n // scaffolds that file under `${projectRoot}/playwright/fixtures/auth.json` — without\n // this `cwd`, nx:run-commands would spawn from the workspace root and `login()` would\n // miss the scaffolded fixture (failing unless env vars are set).\n const configurations = {\n local: { env: { BASE_URL: \"http://localhost:3000\" } },\n dev: { env: { BASE_URL: \"https://dev.manager.trackunit.com\" } },\n stage: { env: { BASE_URL: \"https://stage.manager.trackunit.com\" } },\n prod: { env: { BASE_URL: \"https://new.manager.trackunit.com\" } },\n };\n updateProjectConfiguration(tree, options.project, {\n ...existingProjectConfig,\n targets: {\n ...existingProjectConfig.targets,\n \"e2e-ui\": {\n executor: \"nx:run-commands\",\n options: {\n command: `playwright test --config=playwright.config.ts --headed --ui`,\n cwd: projectRoot,\n },\n configurations,\n },\n e2e: {\n executor: \"nx:run-commands\",\n options: {\n command: `playwright test --config=playwright.config.ts`,\n cwd: projectRoot,\n },\n configurations,\n },\n },\n });\n\n generateFiles(tree, joinPathFragments(__dirname, \"files/root\"), projectRoot, {\n ...options,\n tmpl: \"\",\n });\n\n addPlaywrightTsConfigReference(tree, projectRoot);\n\n await formatFiles(tree);\n\n return () => {\n logger.info(\n `\\n\\n✅ 🚀 \\nSet the E2E account username in: \n ${joinPathFragments(projectRoot, \"playwright/fixtures/auth.json\")}\n(the password is resolved at run time from E2E_PW_<SLUG> — the CircleCI context in CI, Vault locally)\nand open this file to get started writing tests:\n ${joinPathFragments(projectRoot, \"playwright/tests/app.spec.ts\")}`\n );\n };\n}\n"]}
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-playwright/src/index.ts"],"names":[],"mappings":";;;AAAA,kEAAwC;AACxC,4EAAkD;AAClD,iEAAuC;AACvC,6DAAmC;AACnC,oEAA0C;AAC1C,iEAAuC;AACvC,0EAAgD;AAChD,4DAAkC;AAClC,kEAAwC;AACxC,8DAAoC;AAEpC,8EAA8E;AAC9E,+EAA+E;AAC/E,kFAAkF;AAClF,iFAAiF;AACjF,6DAAmC;AACnC,2EAAiD","sourcesContent":["export * from \"./plugins/createLogFile\";\nexport * from \"./plugins/defaultPlaywrightConfig\";\nexport * from \"./plugins/logsReporter\";\nexport * from \"./plugins/nxPreset\";\nexport * from \"./plugins/redactSensitive\";\nexport * from \"./plugins/setupPlugins\";\nexport * from \"./plugins/writeFileWithPrettier\";\nexport * from \"./utils/Codeowner\";\nexport * from \"./utils/fileNameBuilder\";\nexport * from \"./utils/fileUpdater\";\n\n// E2E credential resolution: resolve an account's password at runtime by name\n// (env-var first, then Vault) inside the `login()` fixture — no `globalSetup`.\n// Vault access uses the optional `node-vault` peer and an env-supplied endpoint —\n// never hardcoded — so any consumer can wire their own Vault. See ./credentials.\nexport * from \"./credentials/keys\";\nexport * from \"./credentials/resolveE2EPassword\";\n\nexport type { FeatureFlag, FeatureFlagFixtures } from \"./fixtures/featureFlags.fixture\";\nexport type { CachedStorageState } from \"./fixtures/hydrateStorageState\";\nexport type { IrisAppFixtures, IrisAppOptions, StorybookPreviewOptions } from \"./fixtures/irisApp.fixture\";\nexport type { LocalDevModeFixtures } from \"./fixtures/localDevMode.fixture\";\nexport type { AuthCredentials, LoginOptions, TrackunitBaseFixtures } from \"./fixtures/login.fixture\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createLogFile.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/createLogFile.ts"],"names":[],"mappings":";;AAmCA,sCAkCC;;AArED,iDAAoC;AACpC,wDAAwB;AACxB,mEAA2E;AAQ3E,MAAM,aAAa,GAAG,CAAC,GAAQ,EAAW,EAAE;IAC1C,OAAO,CACL,GAAG,CAAC,IAAI,KAAK,iBAAiB;QAC9B,GAAG,CAAC,IAAI,KAAK,kBAAkB;QAC/B,GAAG,CAAC,IAAI,KAAK,eAAe;QAC5B,GAAG,CAAC,IAAI,KAAK,mBAAmB,CACjC,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACI,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,QAAgB,EAChB,wBAAgC,EAChC,IAAgB,EAChB,SAAoB;IAEpB,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;IAClE,MAAM,MAAM,GAAyB;QACnC,IAAA,6CAAqB,EAAC,MAAM,EAAE,GAAG,WAAW,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;KAC1F,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC/D,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,IAAA,6CAAqB,EAAC,MAAM,EAAE,GAAG,WAAW,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IACjH,CAAC;IAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CACnC,GAAG,CAAC,EAAE,CACJ,aAAa,CAAC,GAAG,CAAC;QAClB,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;QACpC,GAAG,CAAC,QAAQ,KAAK,SAAS;QAC1B,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAC1C,CAAC;IACF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CACT,IAAA,6CAAqB,EAAC,MAAM,EAAE,GAAG,WAAW,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,SAAS,CAAC,CAClH,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import fs, { existsSync } from \"fs\";\nimport path from \"path\";\nimport { Formatter, writeFileWithPrettier } from \"./writeFileWithPrettier\";\n\ntype Log = {\n type: string;\n message: string;\n severity: string;\n};\n\nconst isNetworkCall = (log: Log): boolean => {\n return (\n log.type === \"network:request\" ||\n log.type === \"network:response\" ||\n log.type === \"network:fetch\" ||\n log.type === \"network:websocket\"\n );\n};\n\n/**\n * Creates structured log files for Playwright test runs.\n * Generates separate files for all logs, errors, and network errors.\n *\n * Returns a promise that resolves once every file has been formatted and\n * written. Callers (e.g. `LogsReporter.onTestEnd`) must await — or collect\n * and later await — this promise to avoid fire-and-forget writes that\n * Playwright may finish before Prettier settles.\n *\n * @param nxRoot - Absolute path to the NX workspace root\n * @param logsPath - Directory where log files will be written\n * @param fileNameWithoutExtension - Base filename (without extension) for the log files\n * @param logs - Array of log entries to write\n * @param logWriter - Prettier formatter instance for formatting JSON output\n * @returns {Promise<void>} Resolves once all log files have been written\n */\nexport async function createLogFile(\n nxRoot: string,\n logsPath: string,\n fileNameWithoutExtension: string,\n logs: Array<Log>,\n logWriter: Formatter\n): Promise<void> {\n if (!existsSync(logsPath)) {\n fs.mkdirSync(logsPath, { recursive: true });\n }\n\n const logFilePath = path.join(logsPath, fileNameWithoutExtension);\n const writes: Array<Promise<void>> = [\n writeFileWithPrettier(nxRoot, `${logFilePath}-all.json`, JSON.stringify(logs), logWriter),\n ];\n const errorCmds = logs.filter(log => log.severity === \"error\");\n if (errorCmds.length > 0) {\n writes.push(writeFileWithPrettier(nxRoot, `${logFilePath}-errors.json`, JSON.stringify(errorCmds), logWriter));\n }\n\n const networkErrorsCmds = logs.filter(\n log =>\n isNetworkCall(log) &&\n !log.message.includes(\"Status: 200\") &&\n log.severity !== \"success\" &&\n !log.message.includes(\"sentry.io/api/\")\n );\n if (networkErrorsCmds.length > 0) {\n writes.push(\n writeFileWithPrettier(nxRoot, `${logFilePath}-network-errors.json`, JSON.stringify(networkErrorsCmds), logWriter)\n );\n }\n\n await Promise.all(writes);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultPlaywrightConfig.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/defaultPlaywrightConfig.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PlaywrightTestConfig } from \"@playwright/test\";\nimport crypto from \"crypto\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { getCodeowner, toShortTeamName } from \"../utils/Codeowner\";\n\n/**\n * Finds the NX workspace root by walking up the directory tree looking for nx.json or workspace.json.\n *\n * @param startDir - Starting directory for the search (defaults to current working directory)\n * @returns {string} Absolute path to the workspace root\n * @throws Error if workspace root cannot be found\n */\nexport function findWorkspaceRoot(startDir: string = process.cwd()): string {\n let currentDir = startDir;\n while (currentDir !== path.dirname(currentDir)) {\n if (existsSync(path.join(currentDir, \"nx.json\")) || existsSync(path.join(currentDir, \"workspace.json\"))) {\n return currentDir;\n }\n currentDir = path.dirname(currentDir);\n }\n throw new Error(\"Could not find NX workspace root (nx.json or workspace.json not found)\");\n}\n\n// Memoized at module scope so multiple `defaultPlaywrightConfig()` calls within\n// a single process (Playwright loads the config twice — once for discovery, once\n// for the run) share the same ids. Otherwise the junit reporter would write to\n// two different filenames and `metadata.runId` would diverge between the two\n// loads.\nconst runId = crypto.randomUUID();\nconst junitFileSuffix = crypto.randomUUID();\n\n/**\n * Default cap when running against a shared, rate-limited environment (dev,\n * stage, prod, feature branches). Picked deliberately: it stays under Okta's\n * `/api/v1/authn` burst threshold (typically ~5/s) for the initial parallel\n * login fan-out, while still letting most suites finish in roughly half the\n * wall-clock of a fully serial run.\n */\nconst SHARED_ENV_WORKER_CAP = 2;\n\n/**\n * Recognises whether a `BASE_URL` value points at a developer's own machine\n * (Vite dev server, in-cluster preview, etc.) vs a shared, rate-limited\n * environment. We don't try to enumerate every dev pattern — just the few\n * that genuinely don't share an Okta tenant with other workers running\n * elsewhere. Anything else (including unset) is treated conservatively.\n */\nconst isSharedEnvironmentBaseURL = (baseURL: string | undefined): boolean => {\n if (baseURL === undefined || baseURL === \"\") {\n return false;\n }\n try {\n // Node's URL parser wraps IPv6 hostnames in brackets (e.g. `\"[::1]\"`).\n // Strip them so the IPv6 loopback check below works regardless.\n const hostname = new URL(baseURL).hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"0.0.0.0\" || hostname === \"::1\") {\n return false;\n }\n if (hostname.endsWith(\".local\") || hostname.endsWith(\".localhost\")) {\n return false;\n }\n return true;\n } catch {\n // Malformed URLs slip through as \"not shared\" — Playwright will fail\n // loudly on its own URL parsing later, and we'd rather not cap workers\n // based on a value we don't understand.\n return false;\n }\n};\n\nexport interface E2EPluginConfig {\n outputPath?: string;\n}\n\nexport interface E2EProjectConfig {\n testDir?: string;\n}\n\nexport interface E2EBehaviorConfig {\n timeout?: number;\n retries?: number;\n navigationTimeout?: number;\n actionTimeout?: number;\n /**\n * Override Playwright's worker count. Useful when the suite hits external,\n * rate-limited services (Okta `/api/v1/authn` returns `E0000047` once you\n * fan out past a handful of parallel logins against a shared environment).\n *\n * The `login` fixture caches one authenticated `storageState` per worker,\n * so worker count is effectively the upper bound on parallel Okta calls\n * per `(baseURL, username)` pair. Capping workers therefore caps Okta load.\n *\n * Leave unset to use Playwright's default (`Math.ceil(os.cpus().length / 2)`\n * locally, `1` on CI).\n */\n workers?: number | string;\n /**\n * Whether tests within the same file should run in parallel.\n *\n * Defaults to Playwright's behaviour (`undefined`, i.e. the project-level\n * setting wins, which in turn defaults to `false`). Set explicitly when a\n * project wants file-level parallelism or wants to opt out of it.\n */\n fullyParallel?: boolean;\n}\n\nexport interface E2EConfigOptions {\n /**\n * Absolute path to the directory of the calling `playwright.config.ts`. Pass `__dirname`.\n *\n * Required when running under `@nx/playwright` (or any executor that forks Playwright from\n * the workspace root) — without it, `process.cwd()` resolves to the workspace root and\n * codeowner lookup / report paths land outside the intended project directory.\n *\n * When omitted, falls back to `process.cwd()` for backwards compatibility with consumers\n * invoking Playwright directly from their project directory.\n */\n configDir?: string;\n nxRoot?: string;\n outputDirOverride?: string;\n projectConfig?: E2EProjectConfig;\n behaviorConfig?: E2EBehaviorConfig;\n pluginConfig?: E2EPluginConfig;\n}\n\n/**\n * Creates default Playwright configuration for E2E testing.\n * Returns a partial `PlaywrightTestConfig` that projects can spread and extend.\n *\n * @param options - Optional configuration overrides\n * @returns {Partial<PlaywrightTestConfig>} Playwright test configuration\n */\nexport const defaultPlaywrightConfig = (options: E2EConfigOptions = {}): Partial<PlaywrightTestConfig> => {\n const {\n configDir,\n nxRoot: providedNxRoot,\n outputDirOverride,\n projectConfig = {},\n behaviorConfig = {},\n pluginConfig = {},\n } = options;\n\n const callerDir = configDir ?? process.cwd();\n const nxRoot = providedNxRoot ?? findWorkspaceRoot(callerDir);\n const relativePath = path.relative(nxRoot, callerDir);\n const dotsToNxRoot = relativePath\n .split(path.sep)\n .map(_ => \"..\")\n .join(\"/\");\n\n // Playwright owns its own output-dir env var, decoupled from the\n // Cypress-owned NX_E2E_OUTPUT_DIR. On CI this points at\n // `./dist/playwright/executor-N` so Playwright artifacts land in a\n // first-class `dist/playwright` tree (never inside `dist/cypress`).\n const envOutputDirOverride = process.env.NX_PLAYWRIGHT_OUTPUT_DIR || outputDirOverride;\n\n const buildOutputPath = (subPath: string) => {\n if (envOutputDirOverride) {\n // Namespace per project under the (per-executor) override dir. On CI several\n // e2e projects run sequentially on one executor sharing this override\n // (`dist/playwright/executor-N`). Playwright clears `outputDir` at the start\n // of every run, so without the `relativePath` segment each project would\n // wipe the previous project's artifacts (screenshots/video/trace/HAR/logs)\n // — a failing project only kept them if it happened to run last. Nesting\n // `outputDir` and the junit `outputFile` (and, via `setupPlugins`, the\n // derived hars/logs) under the project path keeps every project's failure\n // artifacts isolated. Mirrors the project-scoped local path below.\n return path.join(dotsToNxRoot, envOutputDirOverride, relativePath, subPath);\n }\n return `${dotsToNxRoot}/dist/playwright/${relativePath}/${subPath}`;\n };\n\n const codeowner = toShortTeamName(getCodeowner(callerDir, nxRoot));\n\n // Only set workers / fullyParallel when we have a concrete value. Spreading\n // `undefined` would still create the key on the returned config, which\n // Playwright treats as \"the user said `undefined`\" — surprising semantics\n // we'd rather avoid by leaving the key absent altogether.\n //\n // `workers` resolution order:\n // 1. Explicit `behaviorConfig.workers` always wins.\n // 2. Otherwise, when running against a shared environment (BASE_URL set\n // and not pointing at localhost) we default to `SHARED_ENV_WORKER_CAP`.\n // The `login` fixture's per-worker cache collapses subsequent Okta\n // calls within a worker, but the first call on each worker still races\n // at suite startup — and Okta's `/api/v1/authn` returns `E0000047`\n // (\"rate limit\") once you fan out past a handful of parallel logins.\n // Two workers stays comfortably under that threshold while preserving\n // ~2x wall-clock speedup over fully serial execution.\n // 3. Local/feature-branch runs (`BASE_URL` unset or localhost) keep\n // Playwright's default — those environments aren't rate-limited.\n const baseURL = process.env.NX_FEATURE_BRANCH_BASE_URL ?? process.env.BASE_URL;\n const optional: Partial<PlaywrightTestConfig> = {};\n if (behaviorConfig.workers !== undefined) {\n optional.workers = behaviorConfig.workers;\n } else if (isSharedEnvironmentBaseURL(baseURL)) {\n optional.workers = SHARED_ENV_WORKER_CAP;\n }\n if (behaviorConfig.fullyParallel !== undefined) {\n optional.fullyParallel = behaviorConfig.fullyParallel;\n }\n\n return {\n timeout: behaviorConfig.timeout ?? 30000,\n expect: {\n timeout: behaviorConfig.timeout ?? 20000,\n },\n retries: behaviorConfig.retries ?? 3,\n use: {\n baseURL: process.env.NX_FEATURE_BRANCH_BASE_URL ?? process.env.BASE_URL ?? undefined,\n navigationTimeout: behaviorConfig.navigationTimeout ?? 35000,\n actionTimeout: behaviorConfig.actionTimeout ?? 20000,\n screenshot: \"only-on-failure\",\n video: \"retain-on-failure\",\n trace: \"retain-on-failure\",\n },\n outputDir: pluginConfig.outputPath ?? buildOutputPath(\"test-results\"),\n reporter: [[\"junit\", { outputFile: buildOutputPath(`results-${junitFileSuffix}.xml`) }]],\n testDir: projectConfig.testDir ?? \"./src/e2e\",\n metadata: {\n codeowner,\n runId,\n },\n ...optional,\n };\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harRecordingPath.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/harRecordingPath.ts"],"names":[],"mappings":";;;;AAAA,2BAA4B;AAC5B,mDAA6B;AAE7B,4DAA4D;AAC/C,QAAA,YAAY,GAAG,UAAU,CAAC;AAavC;;;;;;;;;;;;GAYG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAA0B,EAAU,EAAE,CACrE,IAAI,CAAC,IAAI,CAAC,IAAA,WAAM,GAAE,EAAE,wBAAwB,EAAE,GAAG,IAAI,CAAC,MAAM,YAAY,IAAI,CAAC,KAAK,IAAI,oBAAY,EAAE,CAAC,CAAC;AAD3F,QAAA,gBAAgB,oBAC2E","sourcesContent":["import { tmpdir } from \"os\";\nimport * as path from \"path\";\n\n/** Name of the per-test HAR file written by `recordHar`. */\nexport const HAR_FILENAME = \"test.har\";\n\n/**\n * Identifies a single test attempt. `testId` is Playwright's `TestInfo.testId`,\n * which is documented to equal `TestCase.id` in the reporter API — so the\n * `harRecording` fixture (which has `TestInfo`) and `LogsReporter` (which has\n * `TestCase`/`TestResult`) compute the exact same path for the same attempt.\n */\nexport interface HarRecordingPathInfo {\n testId: string;\n retry: number;\n}\n\n/**\n * Absolute path the per-test HAR is recorded to, deliberately **outside** the\n * Playwright `outputDir` (which is uploaded wholesale as CI artifacts).\n *\n * `recordHar` only flushes when the browser context closes, and on a hard test\n * timeout that close completes during Playwright's native context teardown —\n * after the `harRecording` fixture's own teardown has already run. Recording\n * here (a temp dir, never uploaded) means that late flush writes a raw,\n * unredacted HAR (it carries Authorization/Cookie) somewhere CI never stores.\n * `LogsReporter.onTestEnd` runs after all teardown, reads this path, and writes\n * only the **redacted** copy into the artifacts `hars/` folder. Keyed by\n * `testId` + `retry` so parallel workers and retries never collide.\n */\nexport const harRecordingPath = (info: HarRecordingPathInfo): string =>\n path.join(tmpdir(), \"tu-iris-playwright-har\", `${info.testId}-attempt-${info.retry}.${HAR_FILENAME}`);\n"]}
|