@trackunit/iris-app-playwright 0.2.116-alpha-5b28b432cab.0 → 0.2.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/fixtures/hostReadiness.d.ts +0 -5
  3. package/src/fixtures/hostReadiness.js +1 -11
  4. package/src/fixtures/login.fixture.d.ts +7 -75
  5. package/src/fixtures/login.fixture.js +45 -301
  6. package/migrations/entry.js.map +0 -1
  7. package/src/credentials/guards.js.map +0 -1
  8. package/src/credentials/keys.js.map +0 -1
  9. package/src/credentials/resolveE2EPassword.js.map +0 -1
  10. package/src/fixtures/acceptTerms.d.ts +0 -59
  11. package/src/fixtures/acceptTerms.js +0 -136
  12. package/src/fixtures/acceptTerms.js.map +0 -1
  13. package/src/fixtures/baseTest.js.map +0 -1
  14. package/src/fixtures/describe.js.map +0 -1
  15. package/src/fixtures/featureFlags.fixture.js.map +0 -1
  16. package/src/fixtures/harRecording.fixture.js.map +0 -1
  17. package/src/fixtures/hostReadiness.js.map +0 -1
  18. package/src/fixtures/hydrateStorageState.js.map +0 -1
  19. package/src/fixtures/irisApp.fixture.js.map +0 -1
  20. package/src/fixtures/localDevMode.fixture.js.map +0 -1
  21. package/src/fixtures/login.fixture.js.map +0 -1
  22. package/src/fixtures/loginCacheKey.js.map +0 -1
  23. package/src/fixtures/resolveCredentials.js.map +0 -1
  24. package/src/fixtures/withTimeout.js.map +0 -1
  25. package/src/generators/playwright-configuration/generator.js.map +0 -1
  26. package/src/index.js.map +0 -1
  27. package/src/plugins/createLogFile.js.map +0 -1
  28. package/src/plugins/defaultPlaywrightConfig.js.map +0 -1
  29. package/src/plugins/harRecordingPath.js.map +0 -1
  30. package/src/plugins/logsReporter.js.map +0 -1
  31. package/src/plugins/nxPreset.js.map +0 -1
  32. package/src/plugins/redactHar.js.map +0 -1
  33. package/src/plugins/redactPatterns.js.map +0 -1
  34. package/src/plugins/redactSensitive.js.map +0 -1
  35. package/src/plugins/redactTrace.js.map +0 -1
  36. package/src/plugins/scanArtifactsForSecrets.js.map +0 -1
  37. package/src/plugins/setupPlugins.js.map +0 -1
  38. package/src/plugins/writeFileWithPrettier.js.map +0 -1
  39. package/src/support.js.map +0 -1
  40. package/src/utils/Codeowner.js.map +0 -1
  41. package/src/utils/fileNameBuilder.js.map +0 -1
  42. package/src/utils/fileUpdater.js.map +0 -1
@@ -1,59 +0,0 @@
1
- /** Route Manager's `TermsGate` redirects to while the signed-in user has not accepted the terms. */
2
- export declare const TERMS_ROUTE = "/terms";
3
- export declare const TERMS_STATUS_OPERATION = "TermsAndConditionsStatus";
4
- /** Upper bound on documents to click through, so an unexpected flow fails instead of looping forever. */
5
- export declare const MAX_TERMS_DOCUMENTS = 20;
6
- /** Whether `url` is the page `TermsGate` redirects to, not the review-terms preferences page. */
7
- export declare const isTermsRoute: (url: string) => boolean;
8
- export type TermsStatusRequest = {
9
- url: () => string;
10
- /** Present on Playwright requests and test fakes, but deliberately never awaited by the watcher. */
11
- response?: () => Promise<unknown>;
12
- };
13
- type TermsStatusListener = (request: TermsStatusRequest) => void;
14
- export type TermsStatusPage = {
15
- on: (event: "request", listener: TermsStatusListener) => void;
16
- off: (event: "request", listener: TermsStatusListener) => void;
17
- };
18
- export type TermsStatusWatcher = {
19
- /** Increases whenever a terms-status request starts, so a new request resets the shell quiet window. */
20
- revision: () => number;
21
- stop: () => void;
22
- };
23
- /**
24
- * Records when the app starts a `TermsAndConditionsStatus` lookup. `TermsGate` sits inside
25
- * `HostLayout`, so the main menu can finish loading before the gate redirects to `/terms`.
26
- *
27
- * The response is deliberately not awaited: a browser request can remain pending or be discarded
28
- * during navigation. URL and rendered gate state are the authoritative readiness signals.
29
- */
30
- export declare const watchTermsStatus: (page: TermsStatusPage) => TermsStatusWatcher;
31
- export type TermsButton = {
32
- isVisible: () => Promise<boolean>;
33
- click: () => Promise<void>;
34
- };
35
- export type TermsPromptPage = {
36
- url: () => string;
37
- waitForURL: (predicate: (url: URL) => boolean, options?: {
38
- timeout?: number;
39
- }) => Promise<unknown>;
40
- getByRole: (role: "button", options: {
41
- name: string;
42
- exact?: boolean;
43
- }) => TermsButton;
44
- };
45
- /**
46
- * Clicks through Manager's terms flow when the signed-in user is being asked to accept.
47
- *
48
- * Root cause (GLU-2632): the feature-branch prompt is not a finished login, and waiting
49
- * never clicks it. Success is leaving `/terms` — the app reloads to `/` and discards the
50
- * mutation body. Staying on `/terms` until `deadline` is {@link HostNeverBecameReadyError},
51
- * not a generic "still on /terms" failure.
52
- */
53
- export declare const acceptTermsWithinBudget: ({ page, now, sleep: pause, deadline, }: {
54
- page: TermsPromptPage;
55
- now: () => number;
56
- sleep: (ms: number) => Promise<void>;
57
- deadline: number;
58
- }) => Promise<void>;
59
- export {};
@@ -1,136 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.acceptTermsWithinBudget = exports.watchTermsStatus = exports.isTermsRoute = exports.MAX_TERMS_DOCUMENTS = exports.TERMS_STATUS_OPERATION = exports.TERMS_ROUTE = void 0;
4
- const test_1 = require("@playwright/test");
5
- const hostReadiness_1 = require("./hostReadiness");
6
- /** Route Manager's `TermsGate` redirects to while the signed-in user has not accepted the terms. */
7
- exports.TERMS_ROUTE = "/terms";
8
- exports.TERMS_STATUS_OPERATION = "TermsAndConditionsStatus";
9
- /** Upper bound on documents to click through, so an unexpected flow fails instead of looping forever. */
10
- exports.MAX_TERMS_DOCUMENTS = 20;
11
- const TERMS_REDIRECT_TIMEOUT_MS = 20000;
12
- /** Gap between checks while the terms buttons are not on screen yet. */
13
- const TERMS_POLL_INTERVAL_MS = 1000;
14
- const ACCEPT_BUTTON_NAME = "Accept";
15
- const START_BUTTON_NAME = "Start using Manager";
16
- const isPlaywrightTimeout = (error) => error instanceof test_1.errors.TimeoutError;
17
- /** Whether `url` is the page `TermsGate` redirects to, not the review-terms preferences page. */
18
- const isTermsRoute = (url) => {
19
- try {
20
- return new URL(url).pathname === exports.TERMS_ROUTE;
21
- }
22
- catch {
23
- return false;
24
- }
25
- };
26
- exports.isTermsRoute = isTermsRoute;
27
- /**
28
- * Records when the app starts a `TermsAndConditionsStatus` lookup. `TermsGate` sits inside
29
- * `HostLayout`, so the main menu can finish loading before the gate redirects to `/terms`.
30
- *
31
- * The response is deliberately not awaited: a browser request can remain pending or be discarded
32
- * during navigation. URL and rendered gate state are the authoritative readiness signals.
33
- */
34
- const watchTermsStatus = (page) => {
35
- let revision = 0;
36
- const onRequest = request => {
37
- if (!request.url().includes(exports.TERMS_STATUS_OPERATION)) {
38
- return;
39
- }
40
- revision += 1;
41
- };
42
- page.on("request", onRequest);
43
- return {
44
- revision: () => revision,
45
- stop: () => {
46
- page.off("request", onRequest);
47
- },
48
- };
49
- };
50
- exports.watchTermsStatus = watchTermsStatus;
51
- const pathnameOf = (url) => {
52
- try {
53
- return new URL(url).pathname;
54
- }
55
- catch {
56
- return undefined;
57
- }
58
- };
59
- const waitForPathname = async (page, matches, now, pause, deadline) => {
60
- if (matches(pathnameOf(page.url()) ?? "")) {
61
- return;
62
- }
63
- let lastTimeout;
64
- while (now() < deadline) {
65
- const remaining = deadline - now();
66
- const timeout = Math.min(TERMS_REDIRECT_TIMEOUT_MS, remaining);
67
- if (timeout <= 0) {
68
- break;
69
- }
70
- const started = now();
71
- try {
72
- await page.waitForURL(url => matches(url.pathname), { timeout });
73
- if (matches(pathnameOf(page.url()) ?? "")) {
74
- return;
75
- }
76
- }
77
- catch (error) {
78
- if (!isPlaywrightTimeout(error)) {
79
- throw error;
80
- }
81
- lastTimeout = error;
82
- }
83
- // A Playwright timeout already consumed real time. A fake that rejects without
84
- // moving the injected clock still has to yield or this loop never reaches the deadline.
85
- if (now() === started) {
86
- const remainingAfter = deadline - now();
87
- if (remainingAfter <= 0) {
88
- break;
89
- }
90
- await pause(Math.min(TERMS_POLL_INTERVAL_MS, remainingAfter));
91
- }
92
- }
93
- throw new hostReadiness_1.HostNeverBecameReadyError(lastTimeout);
94
- };
95
- /**
96
- * Clicks through Manager's terms flow when the signed-in user is being asked to accept.
97
- *
98
- * Root cause (GLU-2632): the feature-branch prompt is not a finished login, and waiting
99
- * never clicks it. Success is leaving `/terms` — the app reloads to `/` and discards the
100
- * mutation body. Staying on `/terms` until `deadline` is {@link HostNeverBecameReadyError},
101
- * not a generic "still on /terms" failure.
102
- */
103
- const acceptTermsWithinBudget = async ({ page, now, sleep: pause, deadline, }) => {
104
- if (!(0, exports.isTermsRoute)(page.url())) {
105
- return;
106
- }
107
- await waitForPathname(page, pathname => pathname === exports.TERMS_ROUTE, now, pause, deadline);
108
- let documents = 0;
109
- for (;;) {
110
- if (now() >= deadline) {
111
- throw new hostReadiness_1.HostNeverBecameReadyError();
112
- }
113
- const startUsingManager = page.getByRole("button", { name: START_BUTTON_NAME });
114
- if (await startUsingManager.isVisible()) {
115
- await startUsingManager.click();
116
- break;
117
- }
118
- const acceptDocument = page.getByRole("button", { name: ACCEPT_BUTTON_NAME, exact: true });
119
- if (await acceptDocument.isVisible()) {
120
- if (documents >= exports.MAX_TERMS_DOCUMENTS) {
121
- throw new hostReadiness_1.HostNeverBecameReadyError();
122
- }
123
- await acceptDocument.click();
124
- documents += 1;
125
- continue;
126
- }
127
- const remaining = deadline - now();
128
- if (remaining <= 0) {
129
- throw new hostReadiness_1.HostNeverBecameReadyError();
130
- }
131
- await pause(Math.min(TERMS_POLL_INTERVAL_MS, remaining));
132
- }
133
- await waitForPathname(page, pathname => pathname !== exports.TERMS_ROUTE, now, pause, deadline);
134
- };
135
- exports.acceptTermsWithinBudget = acceptTermsWithinBudget;
136
- //# sourceMappingURL=acceptTerms.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"acceptTerms.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/acceptTerms.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAC1C,mDAA4D;AAE5D,oGAAoG;AACvF,QAAA,WAAW,GAAG,QAAQ,CAAC;AAEvB,QAAA,sBAAsB,GAAG,0BAA0B,CAAC;AAEjE,yGAAyG;AAC5F,QAAA,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAM,yBAAyB,GAAG,KAAM,CAAC;AAEzC,wEAAwE;AACxE,MAAM,sBAAsB,GAAG,IAAK,CAAC;AAErC,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AACpC,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,MAAM,mBAAmB,GAAG,CAAC,KAAc,EAAW,EAAE,CAAC,KAAK,YAAY,aAAM,CAAC,YAAY,CAAC;AAE9F,iGAAiG;AAC1F,MAAM,YAAY,GAAG,CAAC,GAAW,EAAW,EAAE;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,KAAK,mBAAW,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AANW,QAAA,YAAY,gBAMvB;AAqBF;;;;;;GAMG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAqB,EAAsB,EAAE;IAC5E,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,MAAM,SAAS,GAAwB,OAAO,CAAC,EAAE;QAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,8BAAsB,CAAC,EAAE,CAAC;YACpD,OAAO;QACT,CAAC;QACD,QAAQ,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE9B,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ;QACxB,IAAI,EAAE,GAAG,EAAE;YACT,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACjC,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAlBW,QAAA,gBAAgB,oBAkB3B;AAaF,MAAM,UAAU,GAAG,CAAC,GAAW,EAAsB,EAAE;IACrD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,KAAK,EAC3B,IAAqB,EACrB,OAAsC,EACtC,GAAiB,EACjB,KAAoC,EACpC,QAAgB,EACD,EAAE;IACjB,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,IAAI,WAAoB,CAAC;IACzB,OAAO,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QACxB,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;QAC/D,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,MAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YACjE,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC1C,OAAO;YACT,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,MAAM,KAAK,CAAC;YACd,CAAC;YACD,WAAW,GAAG,KAAK,CAAC;QACtB,CAAC;QACD,+EAA+E;QAC/E,wFAAwF;QACxF,IAAI,GAAG,EAAE,KAAK,OAAO,EAAE,CAAC;YACtB,MAAM,cAAc,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC;YACxC,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;gBACxB,MAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,yCAAyB,CAAC,WAAW,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,uBAAuB,GAAG,KAAK,EAAE,EAC5C,IAAI,EACJ,GAAG,EACH,KAAK,EAAE,KAAK,EACZ,QAAQ,GAMT,EAAiB,EAAE;IAClB,IAAI,CAAC,IAAA,oBAAY,EAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,mBAAW,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAExF,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,SAAS,CAAC;QACR,IAAI,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,yCAAyB,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAChF,IAAI,MAAM,iBAAiB,CAAC,SAAS,EAAE,EAAE,CAAC;YACxC,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM;QACR,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3F,IAAI,MAAM,cAAc,CAAC,SAAS,EAAE,EAAE,CAAC;YACrC,IAAI,SAAS,IAAI,2BAAmB,EAAE,CAAC;gBACrC,MAAM,IAAI,yCAAyB,EAAE,CAAC;YACxC,CAAC;YACD,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;YAC7B,SAAS,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC;QACnC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,yCAAyB,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,mBAAW,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC1F,CAAC,CAAC;AA5CW,QAAA,uBAAuB,2BA4ClC","sourcesContent":["import { errors } from \"@playwright/test\";\nimport { HostNeverBecameReadyError } from \"./hostReadiness\";\n\n/** Route Manager's `TermsGate` redirects to while the signed-in user has not accepted the terms. */\nexport const TERMS_ROUTE = \"/terms\";\n\nexport const TERMS_STATUS_OPERATION = \"TermsAndConditionsStatus\";\n\n/** Upper bound on documents to click through, so an unexpected flow fails instead of looping forever. */\nexport const MAX_TERMS_DOCUMENTS = 20;\n\nconst TERMS_REDIRECT_TIMEOUT_MS = 20_000;\n\n/** Gap between checks while the terms buttons are not on screen yet. */\nconst TERMS_POLL_INTERVAL_MS = 1_000;\n\nconst ACCEPT_BUTTON_NAME = \"Accept\";\nconst START_BUTTON_NAME = \"Start using Manager\";\n\nconst isPlaywrightTimeout = (error: unknown): boolean => error instanceof errors.TimeoutError;\n\n/** Whether `url` is the page `TermsGate` redirects to, not the review-terms preferences page. */\nexport const isTermsRoute = (url: string): boolean => {\n try {\n return new URL(url).pathname === TERMS_ROUTE;\n } catch {\n return false;\n }\n};\n\nexport type TermsStatusRequest = {\n url: () => string;\n /** Present on Playwright requests and test fakes, but deliberately never awaited by the watcher. */\n response?: () => Promise<unknown>;\n};\n\ntype TermsStatusListener = (request: TermsStatusRequest) => void;\n\nexport type TermsStatusPage = {\n on: (event: \"request\", listener: TermsStatusListener) => void;\n off: (event: \"request\", listener: TermsStatusListener) => void;\n};\n\nexport type TermsStatusWatcher = {\n /** Increases whenever a terms-status request starts, so a new request resets the shell quiet window. */\n revision: () => number;\n stop: () => void;\n};\n\n/**\n * Records when the app starts a `TermsAndConditionsStatus` lookup. `TermsGate` sits inside\n * `HostLayout`, so the main menu can finish loading before the gate redirects to `/terms`.\n *\n * The response is deliberately not awaited: a browser request can remain pending or be discarded\n * during navigation. URL and rendered gate state are the authoritative readiness signals.\n */\nexport const watchTermsStatus = (page: TermsStatusPage): TermsStatusWatcher => {\n let revision = 0;\n\n const onRequest: TermsStatusListener = request => {\n if (!request.url().includes(TERMS_STATUS_OPERATION)) {\n return;\n }\n revision += 1;\n };\n\n page.on(\"request\", onRequest);\n\n return {\n revision: () => revision,\n stop: () => {\n page.off(\"request\", onRequest);\n },\n };\n};\n\nexport type TermsButton = {\n isVisible: () => Promise<boolean>;\n click: () => Promise<void>;\n};\n\nexport type TermsPromptPage = {\n url: () => string;\n waitForURL: (predicate: (url: URL) => boolean, options?: { timeout?: number }) => Promise<unknown>;\n getByRole: (role: \"button\", options: { name: string; exact?: boolean }) => TermsButton;\n};\n\nconst pathnameOf = (url: string): string | undefined => {\n try {\n return new URL(url).pathname;\n } catch {\n return undefined;\n }\n};\n\nconst waitForPathname = async (\n page: TermsPromptPage,\n matches: (pathname: string) => boolean,\n now: () => number,\n pause: (ms: number) => Promise<void>,\n deadline: number\n): Promise<void> => {\n if (matches(pathnameOf(page.url()) ?? \"\")) {\n return;\n }\n\n let lastTimeout: unknown;\n while (now() < deadline) {\n const remaining = deadline - now();\n const timeout = Math.min(TERMS_REDIRECT_TIMEOUT_MS, remaining);\n if (timeout <= 0) {\n break;\n }\n const started = now();\n try {\n await page.waitForURL(url => matches(url.pathname), { timeout });\n if (matches(pathnameOf(page.url()) ?? \"\")) {\n return;\n }\n } catch (error) {\n if (!isPlaywrightTimeout(error)) {\n throw error;\n }\n lastTimeout = error;\n }\n // A Playwright timeout already consumed real time. A fake that rejects without\n // moving the injected clock still has to yield or this loop never reaches the deadline.\n if (now() === started) {\n const remainingAfter = deadline - now();\n if (remainingAfter <= 0) {\n break;\n }\n await pause(Math.min(TERMS_POLL_INTERVAL_MS, remainingAfter));\n }\n }\n\n throw new HostNeverBecameReadyError(lastTimeout);\n};\n\n/**\n * Clicks through Manager's terms flow when the signed-in user is being asked to accept.\n *\n * Root cause (GLU-2632): the feature-branch prompt is not a finished login, and waiting\n * never clicks it. Success is leaving `/terms` — the app reloads to `/` and discards the\n * mutation body. Staying on `/terms` until `deadline` is {@link HostNeverBecameReadyError},\n * not a generic \"still on /terms\" failure.\n */\nexport const acceptTermsWithinBudget = async ({\n page,\n now,\n sleep: pause,\n deadline,\n}: {\n page: TermsPromptPage;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n deadline: number;\n}): Promise<void> => {\n if (!isTermsRoute(page.url())) {\n return;\n }\n\n await waitForPathname(page, pathname => pathname === TERMS_ROUTE, now, pause, deadline);\n\n let documents = 0;\n for (;;) {\n if (now() >= deadline) {\n throw new HostNeverBecameReadyError();\n }\n const startUsingManager = page.getByRole(\"button\", { name: START_BUTTON_NAME });\n if (await startUsingManager.isVisible()) {\n await startUsingManager.click();\n break;\n }\n const acceptDocument = page.getByRole(\"button\", { name: ACCEPT_BUTTON_NAME, exact: true });\n if (await acceptDocument.isVisible()) {\n if (documents >= MAX_TERMS_DOCUMENTS) {\n throw new HostNeverBecameReadyError();\n }\n await acceptDocument.click();\n documents += 1;\n continue;\n }\n const remaining = deadline - now();\n if (remaining <= 0) {\n throw new HostNeverBecameReadyError();\n }\n await pause(Math.min(TERMS_POLL_INTERVAL_MS, remaining));\n }\n\n await waitForPathname(page, pathname => pathname !== TERMS_ROUTE, now, pause, deadline);\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"baseTest.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/baseTest.ts"],"names":[],"mappings":";;;AAAA,2CAAsD;AAQ7C,uFARA,aAAM,OAQA;AAPf,iEAAkE;AAClE,iEAAkE;AAClE,uDAAwD;AACxD,iEAAkE;AAClE,mDAAoD;AAEvC,QAAA,IAAI,GAAG,IAAA,iBAAU,EAAC,2BAAgB,EAAE,oBAAS,EAAE,sBAAW,EAAE,2BAAgB,EAAE,2BAAgB,CAAC,CAAC","sourcesContent":["import { expect, mergeTests } from \"@playwright/test\";\nimport { test as featureFlagsTest } from \"./featureFlags.fixture\";\nimport { test as harRecordingTest } from \"./harRecording.fixture\";\nimport { test as irisAppTest } from \"./irisApp.fixture\";\nimport { test as localDevModeTest } from \"./localDevMode.fixture\";\nimport { test as loginTest } from \"./login.fixture\";\n\nexport const test = mergeTests(harRecordingTest, loginTest, irisAppTest, featureFlagsTest, localDevModeTest);\nexport { expect };\n\nexport type { FeatureFlag, FeatureFlagFixtures } from \"./featureFlags.fixture\";\nexport type { IrisAppFixtures, IrisAppOptions, StorybookPreviewOptions } from \"./irisApp.fixture\";\nexport type { LocalDevModeFixtures } from \"./localDevMode.fixture\";\nexport type { AuthCredentials, LoginOptions, TrackunitBaseFixtures } from \"./login.fixture\";\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/describe.ts"],"names":[],"mappings":";;;AAAA,2CAA0D;AAK1D,MAAM,eAAe,GAAG,CAAC,KAAa,EAAU,EAAE;IAChD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACxC,OAAO,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AACvD,CAAC,CAAC;AAEF;;;GAGG;AACU,QAAA,QAAQ,GAAwD,MAAM,CAAC,MAAM,CACxF,CAAC,KAAa,EAAE,QAA0B,EAAQ,EAAE;IAClD,WAAc,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC5D,CAAC,EACD;IACE,IAAI,EAAE,CAAC,KAAa,EAAE,QAA0B,EAAQ,EAAE;QACxD,WAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,EAAE,CAAC,KAAa,EAAE,QAA0B,EAAQ,EAAE;QACxD,WAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;IACjE,CAAC;CACF,CACF,CAAC","sourcesContent":["import { test as playwrightTest } from \"@playwright/test\";\n\ntype DescribeCallback = () => void;\ntype DescribeFn = (title: string, callback: DescribeCallback) => void;\n\nconst addOwnerToTitle = (title: string): string => {\n const codeowner = process.env.codeowner;\n return codeowner ? `${title} [${codeowner}]` : title;\n};\n\n/**\n * Wrapped `describe` that appends `[codeowner]` to the suite title when the\n * `codeowner` environment variable is set. Drop-in replacement for `test.describe`.\n */\nexport const describe: DescribeFn & { only: DescribeFn; skip: DescribeFn } = Object.assign(\n (title: string, callback: DescribeCallback): void => {\n playwrightTest.describe(addOwnerToTitle(title), callback);\n },\n {\n only: (title: string, callback: DescribeCallback): void => {\n playwrightTest.describe.only(addOwnerToTitle(title), callback);\n },\n skip: (title: string, callback: DescribeCallback): void => {\n playwrightTest.describe.skip(addOwnerToTitle(title), callback);\n },\n }\n);\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"featureFlags.fixture.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/featureFlags.fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import { test as base, type Page, type Response } from \"@playwright/test\";\n\nexport interface FeatureFlag {\n key: string;\n state: boolean;\n}\n\nconst featureFlagsCache = new WeakMap<Page, Array<FeatureFlag>>();\nconst featureFlagsInFlight = new WeakMap<Page, Promise<Array<FeatureFlag> | undefined>>();\nconst listenerInstalled = new WeakSet<Page>();\n\nconst extractFlags = async (response: Response): Promise<Array<FeatureFlag> | undefined> => {\n // The response body is not always retrievable by the time this runs: a\n // navigation (e.g. the post-login redirect) can discard the network resource\n // before `response.json()` reads it, making Playwright reject with\n // \"Protocol error (Network.getResponseBody): No resource with given identifier\n // found\". This listener is a passive observer, so a missed capture is benign —\n // swallow it and report \"no flags\" rather than letting the rejection surface\n // as an unhandled error that fails the in-flight test.\n try {\n const body = await response.json();\n const flags = body?.data?.featureFlags;\n return Array.isArray(flags) ? flags : undefined;\n } catch {\n return undefined;\n }\n};\n\n// Tracks the in-flight extraction promise per page so `configCat` can await it\n// instead of falling through to `page.waitForResponse(...)`, which would wait\n// for the *next* matching response and hang when the original already happened.\nconst trackExtraction = (page: Page, response: Response): Promise<Array<FeatureFlag> | undefined> => {\n const promise = extractFlags(response)\n .then(flags => {\n if (flags !== undefined) {\n featureFlagsCache.set(page, flags);\n }\n return flags;\n })\n .finally(() => {\n if (featureFlagsInFlight.get(page) === promise) {\n featureFlagsInFlight.delete(page);\n }\n });\n featureFlagsInFlight.set(page, promise);\n return promise;\n};\n\nconst installFlagListener = (page: Page): void => {\n if (listenerInstalled.has(page)) {\n return;\n }\n listenerInstalled.add(page);\n\n page.on(\"response\", response => {\n if (response.url().includes(\"ValidateFeatureFlags\") && response.ok()) {\n // Defensive: `trackExtraction` already swallows body-read failures, but\n // guard the floating promise so nothing can become an unhandled rejection\n // that would fail an unrelated in-flight test.\n void trackExtraction(page, response).catch(() => undefined);\n }\n });\n};\n\nexport interface FeatureFlagFixtures {\n /**\n * Call before the navigation that triggers `ValidateFeatureFlags`, `UserPermissions`,\n * and `ActiveSubscription` requests to ensure the responses are captured for `configCat`.\n */\n getValidateFeatureFlags: () => void;\n /**\n * Returns the state of a ConfigCat feature flag from the captured `ValidateFeatureFlags` response.\n * Waits for the response if not yet captured.\n *\n * @param flag - The feature flag key to look up\n */\n configCat: (flag: string) => Promise<boolean>;\n}\n\nexport const test = base.extend<FeatureFlagFixtures>({\n getValidateFeatureFlags: async ({ page }, provide) => {\n await provide(() => {\n installFlagListener(page);\n });\n },\n\n configCat: async ({ page }, provide) => {\n await provide(async (flag: string) => {\n if (!featureFlagsCache.has(page)) {\n installFlagListener(page);\n // If the listener already saw the response, an extraction is in flight —\n // await it before deciding to wait for another network response.\n const inFlight = featureFlagsInFlight.get(page);\n if (inFlight !== undefined) {\n await inFlight;\n }\n if (!featureFlagsCache.has(page)) {\n const response = await page.waitForResponse(\n r => r.url().includes(\"ValidateFeatureFlags\") && r.ok(),\n { timeout: 30000 }\n );\n const parsedFlags = await extractFlags(response);\n if (parsedFlags !== undefined) {\n featureFlagsCache.set(page, parsedFlags);\n }\n }\n }\n const cachedFlags = featureFlagsCache.get(page);\n const found = cachedFlags?.find(ff => ff.key === flag);\n if (found === undefined) {\n throw new Error(`Feature flag '${flag}' not found in ValidateFeatureFlags response`);\n }\n return found.state;\n });\n },\n});\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"harRecording.fixture.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/harRecording.fixture.ts"],"names":[],"mappings":";;;;AAAA,2CAAgD;AAChD,2BAA+B;AAC/B,mDAA6B;AAC7B,kEAA+D;AAC/D,+CAA4C;AAE5C;;;;;;;GAOG;AACU,QAAA,wBAAwB,GAAG,KAAM,CAAC;AAE/C;;;;;;;GAOG;AACU,QAAA,8BAA8B,GAAG,KAAM,CAAC;AAOrD;;;;;;;;;GASG;AACI,MAAM,wBAAwB,GAAG,KAAK,EAC3C,OAAwB,EACxB,UAAuC,EAAE,EAC1B,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAA,yBAAW,EAAC,OAAO,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,cAAc,IAAI,gCAAwB,CAAC,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,0DAA0D;IAC5D,CAAC;AACH,CAAC,CAAC;AAVW,QAAA,wBAAwB,4BAUnC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,IAAI,GAAG,WAAI,CAAC,MAAM,CAAC;IAC9B,cAAc,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC9D,MAAM,OAAO,GAAG,IAAA,mCAAgB,EAAC,QAAQ,CAAC,CAAC;QAC3C,8EAA8E;QAC9E,qEAAqE;QACrE,IAAA,cAAS,EAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,MAAM,OAAO,CAAC;YACZ,GAAG,cAAc;YACjB,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE;QACP,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE;YAC7B,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACvB,MAAM,IAAA,gCAAwB,EAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QACD,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,sCAA8B,EAAE;KAC3D;CACF,CAAC,CAAC","sourcesContent":["import { test as base } from \"@playwright/test\";\nimport { mkdirSync } from \"fs\";\nimport * as path from \"path\";\nimport { harRecordingPath } from \"../plugins/harRecordingPath\";\nimport { withTimeout } from \"./withTimeout\";\n\n/**\n * Budget for the `context.close()` we issue after the test body. Closing is\n * normally fast, but on a hard test timeout the page is often unresponsive and\n * an unbounded close would consume this fixture's teardown budget. We bound it\n * and move on; Playwright's built-in `context` teardown runs afterwards and\n * awaits the real close within its own budget, so native video/trace\n * persistence is never gated behind our close.\n */\nexport const CONTEXT_CLOSE_TIMEOUT_MS = 10_000;\n\n/**\n * Dedicated per-fixture teardown budget for the `context` override (see the tuple\n * passed to `base.extend` below). Without it, this override's teardown draws from\n * the single teardown budget shared with Playwright's built-in `context` teardown —\n * so a slow close could starve native video/trace persistence. Giving the override\n * its own budget decouples the two. It comfortably exceeds the bounded close; the\n * unit test asserts that invariant.\n */\nexport const HAR_CONTEXT_FIXTURE_TIMEOUT_MS = 25_000;\n\n/** Minimal structural view of the browser context teardown needs. */\ninterface ClosableContext {\n close(): Promise<void>;\n}\n\n/**\n * Closes the context (bounded, best-effort).\n *\n * Extracted from the fixture so the teardown bounding can be unit tested without a\n * browser. The bounded close guarantees we return control even if the page is hung,\n * so Playwright's native context teardown (which persists video/trace) is never\n * starved. The HAR is **not** handled here — `LogsReporter.onTestEnd` reads the raw\n * HAR from `harRecordingPath` after all teardown, which is the only point that\n * reliably runs after a late close flushes the HAR on the hard-timeout path.\n */\nexport const closeContextWithinBudget = async (\n context: ClosableContext,\n options: { closeTimeoutMs?: number } = {}\n): Promise<void> => {\n try {\n await withTimeout(context.close(), options.closeTimeoutMs ?? CONTEXT_CLOSE_TIMEOUT_MS);\n } catch {\n // Bounded-out or failed close: leave it to Playwright's built-in context\n // teardown to await the real close within its own budget.\n }\n};\n\n/**\n * Records a per-test HAR without leaking it into stored artifacts.\n *\n * `recordHar` (configured via `contextOptions`) only flushes the HAR to disk when\n * the browser context closes. We record it to a temp path outside `outputDir`\n * (see `harRecordingPath`) so a close that flushes late — e.g. during native\n * teardown on a hard timeout — can never leak an unredacted HAR into uploaded\n * artifacts. `LogsReporter.onTestEnd` is the single owner of that file: it runs\n * after all teardown, redacts + retains it on failure, and always deletes the raw\n * source. Persisting the HAR there (rather than via `testInfo.attach` during this\n * fixture's teardown) is what makes a hard-timeout HAR survive — the attach seam\n * ran before the late close flushed the file and lost it.\n *\n * The `context` override exists only to bound the close so a hung page can't starve\n * native video/trace persistence; it carries its own `timeout`\n * (`HAR_CONTEXT_FIXTURE_TIMEOUT_MS`) so its teardown draws from a dedicated budget\n * rather than the one shared with the built-in `context` teardown.\n */\nexport const test = base.extend({\n contextOptions: async ({ contextOptions }, provide, testInfo) => {\n const harPath = harRecordingPath(testInfo);\n // `recordHar` doesn't create the parent directory; ensure it exists since the\n // path lives in a temp dir outside Playwright's managed output tree.\n mkdirSync(path.dirname(harPath), { recursive: true });\n await provide({\n ...contextOptions,\n recordHar: { path: harPath, mode: \"minimal\" },\n });\n },\n\n context: [\n async ({ context }, provide) => {\n await provide(context);\n await closeContextWithinBudget(context);\n },\n { scope: \"test\", timeout: HAR_CONTEXT_FIXTURE_TIMEOUT_MS },\n ],\n});\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hostReadiness.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/hostReadiness.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,MAAa,yBAA0B,SAAQ,KAAK;IAClD,mHAAmH;IACnH,YAAmB,KAAe;QAChC,KAAK,CAAC,yBAAyB,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAND,8DAMC;AAED,8FAA8F;AAC9F,MAAa,0BAA2B,SAAQ,yBAAyB;IACvE,2EAA2E;IAC3E,YAAmB,SAAiB;QAClC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,oCAAoC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;IAC3E,CAAC;CACF;AAPD,gEAOC;AAED;;;;GAIG;AACI,MAAM,mBAAmB,GAAG,GAAY,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AAA3F,QAAA,mBAAmB,uBAAwE","sourcesContent":["/**\n * Thrown when a feature-branch host never becomes ready.\n *\n * Root cause (GLU-2631): feature-branch login had no shared error whose\n * message is exactly \"host never became ready\", and no gate on a non-empty\n * `NX_FEATURE_BRANCH_BASE_URL`. A host that never served `/env` or a stuck\n * session handoff therefore failed as a page error. Later phases import this\n * error and {@link isFeatureBranchHost}.\n */\nexport class HostNeverBecameReadyError extends Error {\n /** Creates an error whose message is exactly \"host never became ready\". The wait that ended is kept as `cause`. */\n public constructor(cause?: unknown) {\n super(\"host never became ready\", cause === undefined ? undefined : { cause });\n this.name = \"HostNeverBecameReadyError\";\n }\n}\n\n/** Thrown when the terms gate remains visibly loading after its shorter diagnostic budget. */\nexport class TermsGateDidNotSettleError extends HostNeverBecameReadyError {\n /** Creates a readiness error that names the shorter terms-gate timeout. */\n public constructor(timeoutMs: number) {\n super();\n this.name = \"TermsGateDidNotSettleError\";\n this.message = `terms gate did not settle within ${String(timeoutMs)}ms`;\n }\n}\n\n/**\n * True only when `NX_FEATURE_BRANCH_BASE_URL` is a non-empty string.\n *\n * Unset or `\"\"` leaves the gate off. Staging and production do not set the variable.\n */\nexport const isFeatureBranchHost = (): boolean => (process.env.NX_FEATURE_BRANCH_BASE_URL ?? \"\") !== \"\";\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateStorageState.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/hydrateStorageState.ts"],"names":[],"mappings":";;;AAmBA;;;;;;;;;;;;;;;GAeG;AACI,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAA6B,EAAE,KAAyB,EAAiB,EAAE;IACnH,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IAED,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC,OAAsC,EAAE,EAAE;QACrE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC7E,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACjD,IAAI,CAAC;gBACH,yJAAyJ;gBACzJ,oDAAoD;gBACpD,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,CAAC;YAAC,MAAM,CAAC;gBACP,uFAAuF;YACzF,CAAC;QACH,CAAC;IACH,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;AACpB,CAAC,CAAC;AAxBW,QAAA,mBAAmB,uBAwB9B","sourcesContent":["import type { BrowserContext } from \"@playwright/test\";\n\n/**\n * Subset of Playwright's `StorageState` we persist between tests within the\n * same worker. We model it explicitly rather than re-exporting\n * `Awaited<ReturnType<BrowserContext['storageState']>>` so the cache\n * contract is greppable from spec files and Playwright API churn cannot\n * silently widen what we're persisting.\n */\nexport interface CachedStorageState {\n cookies: Parameters<BrowserContext[\"addCookies\"]>[0];\n origins: Array<{\n origin: string;\n localStorage: Array<{ name: string; value: string }>;\n }>;\n}\n\nexport type StorageStateHydrator = Pick<BrowserContext, \"addCookies\" | \"addInitScript\">;\n\n/**\n * Restores cookies and localStorage from a cached `storageState` snapshot\n * into the supplied browser context.\n *\n * Cookies go through `context.addCookies` (synchronous from the page's POV\n * because they're set before any navigation). LocalStorage requires an\n * `addInitScript` because it can only be written by JS executing on the\n * matching origin — Playwright runs the init script in every new document,\n * and we gate it by `window.location.origin` so we don't smuggle localStorage\n * onto unrelated origins (e.g. about:blank, OAuth IdP domains, or iframes\n * pointing at iris-app subdomains).\n *\n * Wrapping each `setItem` in try/catch protects against contexts where\n * localStorage is unavailable (private browsing modes, sandboxed iframes,\n * etc.) — we never want hydration to crash the page load.\n */\nexport const hydrateStorageState = async (context: StorageStateHydrator, state: CachedStorageState): Promise<void> => {\n if (state.cookies.length > 0) {\n await context.addCookies(state.cookies);\n }\n\n if (state.origins.length === 0) {\n return;\n }\n\n await context.addInitScript((origins: CachedStorageState[\"origins\"]) => {\n const match = origins.find(entry => entry.origin === window.location.origin);\n if (match === undefined) {\n return;\n }\n for (const { name, value } of match.localStorage) {\n try {\n // TODO: playwright fixture — this file was exempted when the no-direct-storage-access rule was introduced; hooks are not usable in addInitScript context\n // eslint-disable-next-line no-restricted-properties\n window.localStorage.setItem(name, value);\n } catch {\n /* localStorage may be unavailable (sandboxed iframe, private mode) — skip silently. */\n }\n }\n }, state.origins);\n};\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"irisApp.fixture.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/irisApp.fixture.ts"],"names":[],"mappings":";;;AAAA,2CAAmE;AAkBtD,QAAA,IAAI,GAAG,WAAI,CAAC,MAAM,CAAkB;IAC/C,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,OAAO,CAAC,CAAC,OAAwB,EAAE,EAAE,CACzC,IAAI,CAAC,YAAY,CAAC,uBAAuB,OAAO,EAAE,MAAM,IAAI,YAAY,IAAI,CAAC,CAC9E,CAAC;IACJ,CAAC;IAED,gBAAgB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE;QAC5C,MAAM,OAAO,CAAC,CAAC,OAAiC,EAAE,EAAE,CAClD,IAAI,CAAC,YAAY,CAAC,cAAc,OAAO,EAAE,QAAQ,IAAI,0BAA0B,IAAI,CAAC,CACrF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { test as base, type FrameLocator } from \"@playwright/test\";\n\nexport interface IrisAppOptions {\n testId?: string;\n}\n\nexport interface StorybookPreviewOptions {\n /** Storybook preview iframe element id. Defaults to `storybook-preview-iframe`. */\n iframeId?: string;\n}\n\nexport interface IrisAppFixtures {\n /** Returns a FrameLocator for the iris app iframe. Defaults to `data-testid=\"app-iframe\"`. */\n irisApp: (options?: IrisAppOptions) => FrameLocator;\n /** Returns a FrameLocator for the Storybook preview iframe. Defaults to `id=\"storybook-preview-iframe\"`. */\n storybookPreview: (options?: StorybookPreviewOptions) => FrameLocator;\n}\n\nexport const test = base.extend<IrisAppFixtures>({\n irisApp: async ({ page }, provide) => {\n await provide((options?: IrisAppOptions) =>\n page.frameLocator(`iframe[data-testid=\"${options?.testId ?? \"app-iframe\"}\"]`)\n );\n },\n\n storybookPreview: async ({ page }, provide) => {\n await provide((options?: StorybookPreviewOptions) =>\n page.frameLocator(`iframe[id=\"${options?.iframeId ?? \"storybook-preview-iframe\"}\"]`)\n );\n },\n});\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"localDevMode.fixture.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/fixtures/localDevMode.fixture.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAOnC,QAAA,IAAI,GAAG,WAAI,CAAC,MAAM,CAAuB;IACpD,oBAAoB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE;QAChD,MAAM,OAAO,CAAC,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,KAAK,EAAE,CAAC;YACrD,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9F,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC;YAC5D,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,CAAC,WAAW,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { test as base } from \"@playwright/test\";\n\nexport interface LocalDevModeFixtures {\n /** Navigates to the developer portal and toggles local dev mode on if not already enabled. */\n switchToLocalDevMode: () => Promise<void>;\n}\n\nexport const test = base.extend<LocalDevModeFixtures>({\n switchToLocalDevMode: async ({ page }, provide) => {\n await provide(async () => {\n await page.getByTestId(\"developerPortalNav\").click();\n await page.waitForURL(url => url.toString().includes(\"/iris-sdk-portal\"), { timeout: 15000 });\n const toggle = page.getByTestId(\"localDevModeSwitch-input\");\n if (!(await toggle.isChecked())) {\n await page.getByTestId(\"localDevModeSwitch-thumb\").click();\n }\n });\n },\n});\n"]}
@@ -1 +0,0 @@
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 { test as base, errors, request } from \"@playwright/test\";\nimport {\n acceptTermsWithinBudget,\n isTermsRoute,\n watchTermsStatus,\n type TermsStatusPage,\n type TermsStatusWatcher,\n} from \"./acceptTerms\";\nimport { HostNeverBecameReadyError, isFeatureBranchHost, TermsGateDidNotSettleError } 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 revision: () => 0,\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/** Spinner `TermsGate` renders once its chunk has loaded, while the status lookup is still loading. */\nconst TERMS_GATE_LOADING_SELECTOR = '[data-testid=\"termsGateLoading\"]';\n\n/**\n * How long a ready shell must stay off `/terms`, with the gate no longer loading, before\n * login treats the prompt as not coming.\n */\nexport const TERMS_GATE_QUIET_MS = 1_000;\n\n/** How long a visibly loading terms gate may remain unchanged before login fails specifically. */\nexport const TERMS_GATE_STALL_TIMEOUT_MS = 20_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 // Call through the page. A detached `page.on` runs without `this`, and Playwright\n // then throws `this.listenerCount is not a function` before any request is seen.\n return watchTermsStatus({\n on: (event, listener) => {\n page.on?.(event, listener);\n },\n off: (event, listener) => {\n page.off?.(event, listener);\n },\n });\n};\n\n/**\n * True while `TermsGate` has not decided yet: either the lazy chunk has not replaced its\n * Suspense fallback, or the chunk is mounted and shows its own spinner because the status\n * lookup is still loading. A page that cannot see those nodes is treated as decided.\n */\nconst isTermsGateStillLoading = async (page: OktaLoginPage): Promise<boolean> => {\n for (const selector of [TERMS_GATE_FALLBACK_SELECTOR, TERMS_GATE_LOADING_SELECTOR]) {\n const node = page.locator(selector);\n if (node.isVisible !== undefined && (await node.isVisible())) {\n return true;\n }\n }\n return false;\n};\n\n/** True only for the spinner owned by `TermsGate`, not the shared application Suspense fallback. */\nconst isTermsStatusStillLoading = async (page: OktaLoginPage): Promise<boolean> => {\n const node = page.locator(TERMS_GATE_LOADING_SELECTOR);\n return node.isVisible !== undefined && (await node.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 returning as soon as the layout appears\n * can miss a later redirect. URL and rendered gate state are authoritative: a `/terms`\n * URL starts the accept flow, while a ready shell that stays off `/terms` with no gate\n * loader for {@link TERMS_GATE_QUIET_MS} is finished. Starting a new status request\n * resets that quiet window, but its response is never awaited because browser requests\n * can remain pending across navigation. A visibly loading gate gets the shorter\n * {@link TERMS_GATE_STALL_TIMEOUT_MS} diagnostic budget.\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 let quietRevision = 0;\n let gateLoadingSince: number | undefined;\n\n const acceptIfPrompted = async (): Promise<boolean> => {\n // Call through the page. Detached Playwright methods run without `this` and throw\n // reading `_mainFrame` before terms can finish.\n const pageUrl = page.url;\n if (pageUrl === undefined || !isTermsRoute(pageUrl.call(page))) {\n return false;\n }\n if (page.getByRole === undefined) {\n throw new HostNeverBecameReadyError();\n }\n const readUrl = pageUrl.bind(page);\n const findButton = page.getByRole.bind(page);\n await acceptTermsWithinBudget({\n page: {\n url: readUrl,\n getByRole: findButton,\n waitForURL: (predicate, options) => page.waitForURL(predicate, options),\n },\n now,\n sleep: pause,\n deadline,\n });\n return true;\n };\n\n if (page.waitForFunction === undefined) {\n await acceptIfPrompted();\n return;\n }\n const waitForFunction = page.waitForFunction.bind(page);\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 }\n\n const onTerms = page.url !== undefined && isTermsRoute(page.url());\n const gateLoading = await isTermsGateStillLoading(page);\n const termsStatusLoading = gateLoading && (await isTermsStatusStillLoading(page));\n if (termsStatusLoading) {\n gateLoadingSince ??= now();\n if (now() - gateLoadingSince >= TERMS_GATE_STALL_TIMEOUT_MS) {\n throw new TermsGateDidNotSettleError(TERMS_GATE_STALL_TIMEOUT_MS);\n }\n } else {\n gateLoadingSince = undefined;\n }\n\n if (!shellReady || onTerms || gateLoading) {\n quietSince = undefined;\n } else if (acceptedPrompt) {\n return;\n } else {\n // A request that first appears mid-window restarts it: the app may still be\n // committing a redirect after the current DOM state was observed.\n const revision = watcher.revision();\n if (quietSince === undefined || revision !== quietRevision) {\n quietSince = now();\n quietRevision = revision;\n }\n if (now() - quietSince >= TERMS_GATE_QUIET_MS) {\n return;\n }\n }\n\n if (now() >= deadline) {\n break;\n }\n if (now() === started) {\n const mark = `${page.url?.() ?? \"\"}|${String(watcher.revision())}|${String(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?.() ?? \"\"}|${String(watcher.revision())}|${String(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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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 DELETED
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logsReporter.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/logsReporter.ts"],"names":[],"mappings":";;;;AACA,2BAAgF;AAChF,mDAA6B;AAC7B,8DAA2D;AAC3D,mDAAgD;AAChD,yDAAsD;AACtD,2CAA4C;AAC5C,uDAAoD;AACpD,+CAAgD;AAchD;;;GAGG;AACH,MAAa,YAAY;IAcvB,sDAAsD;IACtD,YAAmB,OAA4B;QAV/C;;;;;;WAMG;QACc,kBAAa,GAAyB,EAAE,CAAC;QAIxD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACrC,CAAC;IAED,qDAAqD;IAC9C,OAAO,CAAC,OAAmB,EAAE,MAAa;QAC/C,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,IAAA,cAAS,EAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,iFAAiF;IAC1E,SAAS,CAAC,IAAc,EAAE,MAAkB;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7D,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7B,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,MAAM,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhF,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC;QAE7E,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,IAAA,iCAAe,EAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;gBAC3D,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACrE,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,IAAA,iCAAe,EAAC,OAAO,CAAC;oBACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;iBAC3D,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAA,6BAAa,EAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YAErG,mEAAmE;YACnE,yEAAyE;YACzE,2DAA2D;YAC3D,sEAAsE;YACtE,uEAAuE;YACvE,sEAAsE;YACtE,qEAAqE;YACrE,oEAAoE;YACpE,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAClD,IAAI,SAAS,KAAK,SAAS,IAAI,IAAA,eAAU,EAAC,SAAS,CAAC,EAAE,CAAC;gBACrD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAA,6BAAe,EAAC,SAAS,CAAC,CAAC,CAAC;YACtD,CAAC;YAED,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,KAAK,CAAC,OAAmB;QACpC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;IAED,qFAAqF;IAC9E,aAAa;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;;;;;OAWG;IACK,kBAAkB,CAAC,MAAkB;QAC3C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,cAAc,GAAG,UAAU,CAAC,IAAI,CAAC;YACvC,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAA,eAAU,EAAC,cAAc,CAAC,EAAE,CAAC;gBAChH,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,IAAA,kBAAa,EAAC,cAAc,EAAE,IAAA,iCAAe,EAAC,IAAA,iBAAY,EAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACxF,CAAC;YAAC,MAAM,CAAC;gBACP,gEAAgE;YAClE,CAAC;QACH,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,MAAkB;QAC3C,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QAC5F,OAAO,UAAU,EAAE,IAAI,CAAC;IAC1B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACK,UAAU,CAAC,SAAiB,EAAE,IAAc,EAAE,MAAkB,EAAE,SAAkB;QAC1F,MAAM,OAAO,GAAG,IAAA,mCAAgB,EAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAA,eAAU,EAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,IAAI,SAAS,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC/C,IAAI,CAAC,IAAA,eAAU,EAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;oBACjC,IAAA,cAAS,EAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClD,CAAC;gBACD,MAAM,WAAW,GAAG,GAAG,IAAA,iCAAe,EAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;gBAChF,IAAA,yBAAa,EAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAA,WAAM,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;CACF;AArJD,oCAqJC;AAED,kBAAe,YAAY,CAAC","sourcesContent":["import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from \"@playwright/test/reporter\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"fs\";\nimport * as path from \"path\";\nimport { fileNameBuilder } from \"../utils/fileNameBuilder\";\nimport { createLogFile } from \"./createLogFile\";\nimport { harRecordingPath } from \"./harRecordingPath\";\nimport { redactHarFile } from \"./redactHar\";\nimport { redactSensitive } from \"./redactSensitive\";\nimport { redactTraceFile } from \"./redactTrace\";\nimport type { Formatter } from \"./writeFileWithPrettier\";\n\nexport interface LogsReporterOptions {\n /** Directory where log files are written on test failure */\n logsFolder: string;\n /** Directory where HAR files are moved on test failure (temp HAR deleted on pass) */\n harsFolder?: string;\n /** Workspace root used by createLogFile to locate prettier.config.mjs */\n nxRoot: string;\n /** Prettier formatter instance */\n formatter: Formatter;\n}\n\n/**\n * Custom Playwright reporter that writes redacted log files on test failure\n * and manages HAR file cleanup (retain on failure, delete on pass).\n */\nexport class LogsReporter implements Reporter {\n private readonly logsFolder: string;\n private readonly harsFolder: string | undefined;\n private readonly nxRoot: string;\n private readonly formatter: Formatter;\n /**\n * Promises of in-flight log file writes from `onTestEnd`. `createLogFile`\n * formats with Prettier (async) and writes via `writeFileSync`; Playwright\n * may finish the run before those formats settle, so we track them here and\n * await them in `onEnd` to surface formatter errors and ensure files exist\n * by the time the process exits.\n */\n private readonly pendingWrites: Array<Promise<void>> = [];\n\n /** @param options - Reporter configuration options */\n public constructor(options: LogsReporterOptions) {\n this.logsFolder = options.logsFolder;\n this.harsFolder = options.harsFolder;\n this.nxRoot = options.nxRoot;\n this.formatter = options.formatter;\n }\n\n /** Creates the logs folder before the run begins. */\n public onBegin(_config: FullConfig, _suite: Suite): void {\n if (!existsSync(this.logsFolder)) {\n mkdirSync(this.logsFolder, { recursive: true });\n }\n }\n\n /** Writes a redacted log file on test failure and manages HAR file retention. */\n public onTestEnd(test: TestCase, result: TestResult): void {\n const titlePath = test.titlePath().filter(t => t.length > 0);\n const describeTitle = titlePath.slice(0, -1).join(\" > \");\n const testTitle = test.title;\n const fullTitle = describeTitle ? `${describeTitle} - ${testTitle}` : testTitle;\n\n const isFailure = result.status === \"failed\" || result.status === \"timedOut\";\n\n if (isFailure) {\n const fileName = fileNameBuilder(fullTitle, \"failed\", result.retry);\n const logs = result.stdout.concat(result.stderr).map(entry => {\n const message = typeof entry === \"string\" ? entry : entry.toString();\n return {\n type: \"stdout\",\n message: redactSensitive(message),\n severity: result.status === \"failed\" ? \"error\" : \"warning\",\n };\n });\n\n this.pendingWrites.push(createLogFile(this.nxRoot, this.logsFolder, fileName, logs, this.formatter));\n\n // Defense in depth for trace artifacts: `login()`'s slow path does\n // `page.goto(\"/auth/manager-classic#session_token=...\")`, and Playwright\n // records the full `goto` URL as the action argument. With\n // `trace: \"retain-on-failure\"` from `defaultPlaywrightConfig`, a live\n // Okta session token would otherwise persist in the retained trace zip\n // alongside the test artifacts. The fragment is client-side only — it\n // never reaches the HTTP server and HAR/log redaction already covers\n // everything that does — so this pass scrubs the remaining surface.\n const tracePath = this.getResultTracePath(result);\n if (tracePath !== undefined && existsSync(tracePath)) {\n this.pendingWrites.push(redactTraceFile(tracePath));\n }\n\n this.redactErrorContext(result);\n }\n\n this.persistHar(fullTitle, test, result, isFailure);\n }\n\n /**\n * Awaits in-flight log writes started in `onTestEnd` before the reporter\n * signals completion. Without this, Playwright may exit before Prettier\n * settles, leaving partial or missing log files and swallowing formatter\n * errors.\n */\n public async onEnd(_result: FullResult): Promise<void> {\n await Promise.all(this.pendingWrites);\n }\n\n /** Returns false so Playwright adds its own terminal reporter alongside this one. */\n public printsToStdio(): boolean {\n return false;\n }\n\n /**\n * Scrubs Playwright's `error-context.md` failure attachment in place.\n *\n * On failure Playwright writes an `error-context.md` under `test-results/`\n * (uploaded as a CI artifact) that embeds the failing assertion's *received*\n * value and a page/aria snapshot. The `login()` slow path navigates to\n * `/auth/manager-classic#session_token=...`, so this file carries a live Okta\n * session token — and would carry the password if it ever surfaced in an\n * assertion's received value. It is neither a trace, HAR, nor a stdout log, so\n * the other redaction passes miss it entirely; scrub it here. Best-effort:\n * redaction never fails the reporter.\n */\n private redactErrorContext(result: TestResult): void {\n for (const attachment of result.attachments) {\n const attachmentPath = attachment.path;\n if (attachmentPath === undefined || !attachmentPath.endsWith(\"error-context.md\") || !existsSync(attachmentPath)) {\n continue;\n }\n try {\n writeFileSync(attachmentPath, redactSensitive(readFileSync(attachmentPath, \"utf-8\")));\n } catch {\n /* Redaction is best-effort; never fail the reporter over it. */\n }\n }\n }\n\n private getResultTracePath(result: TestResult): string | undefined {\n const attachment = result.attachments.find(a => a.name === \"trace\" && a.path !== undefined);\n return attachment?.path;\n }\n\n /**\n * Persists (on failure) or cleans up (always) the per-test HAR recorded by the\n * `harRecording` fixture.\n *\n * The raw HAR is recorded to a deterministic temp path outside `outputDir`\n * (see `harRecordingPath`). `recordHar` only flushes it when the browser\n * context closes — and on a hard timeout that close completes during\n * Playwright's *native* context teardown, after the fixture's own teardown has\n * bounded out. Because `onTestEnd` runs after all teardown, the file is\n * reliably present here regardless of how late the close flushed: this is what\n * makes a hard-timeout HAR survive (the earlier `testInfo.attach` seam ran too\n * early on that path and lost it). On failure we redact (the HAR carries\n * Authorization/Cookie and request/response bodies) and move it into the shared\n * `hars/` artifacts folder; in every case we delete the raw temp source via\n * `finally` so an unredacted HAR never lingers on disk, even if redaction throws.\n */\n private persistHar(fullTitle: string, test: TestCase, result: TestResult, isFailure: boolean): void {\n const harPath = harRecordingPath({ testId: test.id, retry: result.retry });\n if (!existsSync(harPath)) {\n return;\n }\n try {\n if (isFailure && this.harsFolder !== undefined) {\n if (!existsSync(this.harsFolder)) {\n mkdirSync(this.harsFolder, { recursive: true });\n }\n const harFileName = `${fileNameBuilder(fullTitle, \"failed\", result.retry)}.har`;\n redactHarFile(harPath, path.join(this.harsFolder, harFileName));\n }\n } finally {\n rmSync(harPath, { force: true });\n }\n }\n}\n\nexport default LogsReporter;\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"nxPreset.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/nxPreset.ts"],"names":[],"mappings":";;AASA,wCAIC;AAbD,kDAAoD;AAEpD;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC7C,OAAO,IAAA,oBAAW,EAAC,QAAQ,EAAE;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { nxE2EPreset } from \"@nx/playwright/preset\";\n\n/**\n * Creates the standard NX E2E preset configuration for Playwright.\n * Wraps the `@nx/playwright` preset with Trackunit defaults.\n *\n * @param filename - Pass `__filename` from the calling `playwright.config.ts`\n * @returns {ReturnType<typeof nxE2EPreset>} NX Playwright preset configuration\n */\nexport function createNxPreset(filename: string): ReturnType<typeof nxE2EPreset> {\n return nxE2EPreset(filename, {\n testDir: \"./src\",\n });\n}\n"]}