@trackunit/iris-app-playwright 0.2.53 → 0.2.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -2
- package/src/credentials/fixtureAccounts.d.ts +19 -0
- package/src/credentials/fixtureAccounts.js +69 -0
- package/src/credentials/globalSetup.d.ts +68 -0
- package/src/credentials/globalSetup.js +91 -0
- package/src/credentials/guards.d.ts +2 -0
- package/src/credentials/guards.js +7 -0
- package/src/credentials/keys.d.ts +28 -0
- package/src/credentials/keys.js +38 -0
- package/src/credentials/resolveE2EPassword.d.ts +35 -0
- package/src/credentials/resolveE2EPassword.js +124 -0
- package/src/fixtures/resolveCredentials.d.ts +3 -1
- package/src/fixtures/resolveCredentials.js +44 -3
- package/src/index.d.ts +5 -0
- package/src/index.js +11 -0
- package/migrations/entry.js.map +0 -1
- package/src/fixtures/baseTest.js.map +0 -1
- package/src/fixtures/describe.js.map +0 -1
- package/src/fixtures/featureFlags.fixture.js.map +0 -1
- package/src/fixtures/harRecording.fixture.js.map +0 -1
- package/src/fixtures/hydrateStorageState.js.map +0 -1
- package/src/fixtures/irisApp.fixture.js.map +0 -1
- package/src/fixtures/localDevMode.fixture.js.map +0 -1
- package/src/fixtures/login.fixture.js.map +0 -1
- package/src/fixtures/loginCacheKey.js.map +0 -1
- package/src/fixtures/resolveCredentials.js.map +0 -1
- package/src/fixtures/withTimeout.js.map +0 -1
- package/src/generators/playwright-configuration/generator.js.map +0 -1
- package/src/index.js.map +0 -1
- package/src/plugins/createLogFile.js.map +0 -1
- package/src/plugins/defaultPlaywrightConfig.js.map +0 -1
- package/src/plugins/harRecordingPath.js.map +0 -1
- package/src/plugins/logsReporter.js.map +0 -1
- package/src/plugins/nxPreset.js.map +0 -1
- package/src/plugins/redactHar.js.map +0 -1
- package/src/plugins/redactPatterns.js.map +0 -1
- package/src/plugins/redactSensitive.js.map +0 -1
- package/src/plugins/redactTrace.js.map +0 -1
- package/src/plugins/scanArtifactsForSecrets.js.map +0 -1
- package/src/plugins/setupPlugins.js.map +0 -1
- package/src/plugins/writeFileWithPrettier.js.map +0 -1
- package/src/support.js.map +0 -1
- package/src/utils/Codeowner.js.map +0 -1
- package/src/utils/fileNameBuilder.js.map +0 -1
- package/src/utils/fileUpdater.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/iris-app-playwright",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.56",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"generators": "./generators.json",
|
|
@@ -37,7 +37,13 @@
|
|
|
37
37
|
"tslib": "^2.6.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@playwright/test": "^1.60.0"
|
|
40
|
+
"@playwright/test": "^1.60.0",
|
|
41
|
+
"node-vault": "^0.10.2"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"node-vault": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
41
47
|
},
|
|
42
48
|
"migrations": "./migrations.json",
|
|
43
49
|
"type": "commonjs"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a fixture's `username` when the file is a **credential** fixture — a
|
|
3
|
+
* JSON object with a non-blank string `username` that is not the `REPLACE_ME`
|
|
4
|
+
* placeholder. Data fixtures (no `username`) and unparseable files yield
|
|
5
|
+
* `undefined`. The `password` field is intentionally NOT required, so this keeps
|
|
6
|
+
* working after passwords are removed from the repo.
|
|
7
|
+
*/
|
|
8
|
+
export declare const readFixtureAccount: (fixturePath: string) => string | undefined;
|
|
9
|
+
/** Lists the `*.json` files directly under a fixtures directory (non-recursive, sorted). */
|
|
10
|
+
export declare const listFixtureFiles: (fixturesDir: string) => Array<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Discovers the **distinct** account identifiers referenced by **all** credential
|
|
13
|
+
* fixtures directly under `fixturesDir` — every `*.json` with a `username`, not
|
|
14
|
+
* just `auth.json`. A project's `playwright/fixtures/` can hold many credential
|
|
15
|
+
* files (`managere2e.json`, `managere2e-admin.json`, `reporte2e.json`, …), each
|
|
16
|
+
* a different account; this returns one entry per distinct account so every one
|
|
17
|
+
* can be resolved.
|
|
18
|
+
*/
|
|
19
|
+
export declare const discoverFixtureAccounts: (fixturesDir: string) => Array<string>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discoverFixtureAccounts = exports.listFixtureFiles = exports.readFixtureAccount = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const guards_1 = require("./guards");
|
|
8
|
+
/** Fixtures still holding this placeholder are unconfigured and ignored. */
|
|
9
|
+
const PLACEHOLDER = "REPLACE_ME";
|
|
10
|
+
/**
|
|
11
|
+
* Returns a fixture's `username` when the file is a **credential** fixture — a
|
|
12
|
+
* JSON object with a non-blank string `username` that is not the `REPLACE_ME`
|
|
13
|
+
* placeholder. Data fixtures (no `username`) and unparseable files yield
|
|
14
|
+
* `undefined`. The `password` field is intentionally NOT required, so this keeps
|
|
15
|
+
* working after passwords are removed from the repo.
|
|
16
|
+
*/
|
|
17
|
+
const readFixtureAccount = (fixturePath) => {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse((0, fs_1.readFileSync)(fixturePath, "utf-8"));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!(0, guards_1.isRecord)(parsed)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const username = parsed.username;
|
|
29
|
+
if (typeof username !== "string" || username.trim() === "" || username === PLACEHOLDER) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return username;
|
|
33
|
+
};
|
|
34
|
+
exports.readFixtureAccount = readFixtureAccount;
|
|
35
|
+
/** Lists the `*.json` files directly under a fixtures directory (non-recursive, sorted). */
|
|
36
|
+
const listFixtureFiles = (fixturesDir) => {
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = (0, fs_1.readdirSync)(fixturesDir);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return entries
|
|
45
|
+
.filter(name => name.endsWith(".json"))
|
|
46
|
+
.map(name => path.join(fixturesDir, name))
|
|
47
|
+
.sort();
|
|
48
|
+
};
|
|
49
|
+
exports.listFixtureFiles = listFixtureFiles;
|
|
50
|
+
/**
|
|
51
|
+
* Discovers the **distinct** account identifiers referenced by **all** credential
|
|
52
|
+
* fixtures directly under `fixturesDir` — every `*.json` with a `username`, not
|
|
53
|
+
* just `auth.json`. A project's `playwright/fixtures/` can hold many credential
|
|
54
|
+
* files (`managere2e.json`, `managere2e-admin.json`, `reporte2e.json`, …), each
|
|
55
|
+
* a different account; this returns one entry per distinct account so every one
|
|
56
|
+
* can be resolved.
|
|
57
|
+
*/
|
|
58
|
+
const discoverFixtureAccounts = (fixturesDir) => {
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const file of (0, exports.listFixtureFiles)(fixturesDir)) {
|
|
61
|
+
const username = (0, exports.readFixtureAccount)(file);
|
|
62
|
+
if (username !== undefined) {
|
|
63
|
+
seen.add(username);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [...seen];
|
|
67
|
+
};
|
|
68
|
+
exports.discoverFixtureAccounts = discoverFixtureAccounts;
|
|
69
|
+
//# sourceMappingURL=fixtureAccounts.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the account name from a name-only `auth.json` fixture (`{ "username": ... }`).
|
|
3
|
+
* The password field has been removed from the repo; only the identifier remains.
|
|
4
|
+
*/
|
|
5
|
+
export declare const readFixtureUsername: (fixturePath: string) => string;
|
|
6
|
+
export interface InjectE2ECredentialsOptions {
|
|
7
|
+
fixturePath: string;
|
|
8
|
+
env?: NodeJS.ProcessEnv;
|
|
9
|
+
/** Password resolver seam (defaults to the real `resolveE2EPassword`). */
|
|
10
|
+
resolve?: (username: string, options: {
|
|
11
|
+
env: NodeJS.ProcessEnv;
|
|
12
|
+
}) => Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the fixture account's password (env-first, else Vault) and injects
|
|
16
|
+
* `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD` into the process env. Playwright
|
|
17
|
+
* spawns workers after `globalSetup`, so the published `resolveCredentials`
|
|
18
|
+
* env-var path picks these up unchanged — no change to the login fixture or the
|
|
19
|
+
* `await login()` call sites.
|
|
20
|
+
*/
|
|
21
|
+
export declare const injectE2ECredentials: (options: InjectE2ECredentialsOptions) => Promise<void>;
|
|
22
|
+
export interface InjectAllE2ECredentialsOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Directory holding the project's credential fixtures. **Every** `*.json` with
|
|
25
|
+
* a `username` is loaded (not just `auth.json`). Ignored when `usernames` is given.
|
|
26
|
+
*/
|
|
27
|
+
fixturesDir?: string;
|
|
28
|
+
/** Explicit account list, overriding the directory scan. */
|
|
29
|
+
usernames?: ReadonlyArray<string>;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
/** Password resolver seam (defaults to the real `resolveE2EPassword`). */
|
|
32
|
+
resolve?: (username: string, options: {
|
|
33
|
+
env: NodeJS.ProcessEnv;
|
|
34
|
+
}) => Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves **every** account referenced by the project's credential fixtures and
|
|
38
|
+
* injects one env var per account: `E2E_PW_<SLUG> = password`.
|
|
39
|
+
*
|
|
40
|
+
* A project's `playwright/fixtures/` can reference many accounts across many
|
|
41
|
+
* files (e.g. `apps/manager/playwright/fixtures`), and a single test can log in
|
|
42
|
+
* as more than one. A single `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD` pair
|
|
43
|
+
* can't carry those, so we set a per-account variable instead; the published
|
|
44
|
+
* `resolveCredentials` then resolves each fixture's `username` to its
|
|
45
|
+
* `E2E_PW_<SLUG>` value at `login()` time. In CI the variables are already
|
|
46
|
+
* present (the CircleCI context), so `resolveE2EPassword` returns them without
|
|
47
|
+
* touching Vault; locally they are resolved from Vault.
|
|
48
|
+
*/
|
|
49
|
+
export declare const injectAllE2ECredentials: (options: InjectAllE2ECredentialsOptions) => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Minimal shape of the Playwright `FullConfig` fields we use — avoids a runtime
|
|
52
|
+
* dependency on `@playwright/test` in this lib. `rootDir` is the config's base
|
|
53
|
+
* directory (the project directory, e.g. `libs/auth/main`), so the fixture sits
|
|
54
|
+
* at `<rootDir>/playwright/fixtures/auth.json`.
|
|
55
|
+
*/
|
|
56
|
+
interface PlaywrightConfigLike {
|
|
57
|
+
rootDir?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Playwright `globalSetup` entry point. Resolves credentials for **all** account
|
|
61
|
+
* fixtures under the running project's `playwright/fixtures/` (every `*.json`,
|
|
62
|
+
* not just `auth.json`), located relative to the config's `rootDir`.
|
|
63
|
+
* `process.cwd()` is NOT used as the base: when the `e2e` target runs
|
|
64
|
+
* `playwright test --config=<project>`, cwd is the nx workspace root, not the
|
|
65
|
+
* project.
|
|
66
|
+
*/
|
|
67
|
+
declare const globalSetup: (config?: PlaywrightConfigLike) => Promise<void>;
|
|
68
|
+
export default globalSetup;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.injectAllE2ECredentials = exports.injectE2ECredentials = exports.readFixtureUsername = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const fixtureAccounts_1 = require("./fixtureAccounts");
|
|
8
|
+
const guards_1 = require("./guards");
|
|
9
|
+
const keys_1 = require("./keys");
|
|
10
|
+
const resolveE2EPassword_1 = require("./resolveE2EPassword");
|
|
11
|
+
/**
|
|
12
|
+
* Reads the account name from a name-only `auth.json` fixture (`{ "username": ... }`).
|
|
13
|
+
* The password field has been removed from the repo; only the identifier remains.
|
|
14
|
+
*/
|
|
15
|
+
const readFixtureUsername = (fixturePath) => {
|
|
16
|
+
if (!(0, fs_1.existsSync)(fixturePath)) {
|
|
17
|
+
throw new Error(`E2E credentials: fixture not found at "${fixturePath}".`);
|
|
18
|
+
}
|
|
19
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(fixturePath, "utf-8"));
|
|
20
|
+
if (!(0, guards_1.isRecord)(parsed) || typeof parsed.username !== "string" || parsed.username === "") {
|
|
21
|
+
throw new Error(`E2E credentials: fixture "${fixturePath}" has no "username".`);
|
|
22
|
+
}
|
|
23
|
+
return parsed.username;
|
|
24
|
+
};
|
|
25
|
+
exports.readFixtureUsername = readFixtureUsername;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the fixture account's password (env-first, else Vault) and injects
|
|
28
|
+
* `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD` into the process env. Playwright
|
|
29
|
+
* spawns workers after `globalSetup`, so the published `resolveCredentials`
|
|
30
|
+
* env-var path picks these up unchanged — no change to the login fixture or the
|
|
31
|
+
* `await login()` call sites.
|
|
32
|
+
*/
|
|
33
|
+
const injectE2ECredentials = async (options) => {
|
|
34
|
+
const env = options.env ?? process.env;
|
|
35
|
+
const resolve = options.resolve ?? resolveE2EPassword_1.resolveE2EPassword;
|
|
36
|
+
const username = (0, exports.readFixtureUsername)(options.fixturePath);
|
|
37
|
+
const password = await resolve(username, { env });
|
|
38
|
+
env.PLAYWRIGHT_USERNAME = username;
|
|
39
|
+
env.PLAYWRIGHT_PASSWORD = password;
|
|
40
|
+
};
|
|
41
|
+
exports.injectE2ECredentials = injectE2ECredentials;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves **every** account referenced by the project's credential fixtures and
|
|
44
|
+
* injects one env var per account: `E2E_PW_<SLUG> = password`.
|
|
45
|
+
*
|
|
46
|
+
* A project's `playwright/fixtures/` can reference many accounts across many
|
|
47
|
+
* files (e.g. `apps/manager/playwright/fixtures`), and a single test can log in
|
|
48
|
+
* as more than one. A single `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD` pair
|
|
49
|
+
* can't carry those, so we set a per-account variable instead; the published
|
|
50
|
+
* `resolveCredentials` then resolves each fixture's `username` to its
|
|
51
|
+
* `E2E_PW_<SLUG>` value at `login()` time. In CI the variables are already
|
|
52
|
+
* present (the CircleCI context), so `resolveE2EPassword` returns them without
|
|
53
|
+
* touching Vault; locally they are resolved from Vault.
|
|
54
|
+
*/
|
|
55
|
+
const injectAllE2ECredentials = async (options) => {
|
|
56
|
+
const env = options.env ?? process.env;
|
|
57
|
+
const resolve = options.resolve ?? resolveE2EPassword_1.resolveE2EPassword;
|
|
58
|
+
const usernames = options.usernames ?? (options.fixturesDir !== undefined ? (0, fixtureAccounts_1.discoverFixtureAccounts)(options.fixturesDir) : []);
|
|
59
|
+
// Resolve every account before mutating `env`. Two distinct accounts can slug
|
|
60
|
+
// to the same `E2E_PW_<SLUG>` key; silently overwriting would run tests with
|
|
61
|
+
// the wrong account's password, so reject the collision (mirrors the publish
|
|
62
|
+
// side's `buildFields`). Resolving up front also keeps injection atomic: a
|
|
63
|
+
// late resolver failure never leaves a partially-injected environment.
|
|
64
|
+
const resolved = new Map();
|
|
65
|
+
for (const username of usernames) {
|
|
66
|
+
const key = (0, keys_1.deriveEnvKey)(username);
|
|
67
|
+
const existing = resolved.get(key);
|
|
68
|
+
if (existing !== undefined) {
|
|
69
|
+
throw new Error(`E2E credentials: accounts "${existing.username}" and "${username}" both map to ${key} — cannot inject both.`);
|
|
70
|
+
}
|
|
71
|
+
resolved.set(key, { username, password: await resolve(username, { env }) });
|
|
72
|
+
}
|
|
73
|
+
for (const [key, { password }] of resolved) {
|
|
74
|
+
env[key] = password;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.injectAllE2ECredentials = injectAllE2ECredentials;
|
|
78
|
+
/**
|
|
79
|
+
* Playwright `globalSetup` entry point. Resolves credentials for **all** account
|
|
80
|
+
* fixtures under the running project's `playwright/fixtures/` (every `*.json`,
|
|
81
|
+
* not just `auth.json`), located relative to the config's `rootDir`.
|
|
82
|
+
* `process.cwd()` is NOT used as the base: when the `e2e` target runs
|
|
83
|
+
* `playwright test --config=<project>`, cwd is the nx workspace root, not the
|
|
84
|
+
* project.
|
|
85
|
+
*/
|
|
86
|
+
const globalSetup = async (config) => {
|
|
87
|
+
const baseDir = config?.rootDir ?? process.cwd();
|
|
88
|
+
await (0, exports.injectAllE2ECredentials)({ fixturesDir: path.join(baseDir, "playwright", "fixtures") });
|
|
89
|
+
};
|
|
90
|
+
exports.default = globalSetup;
|
|
91
|
+
//# sourceMappingURL=globalSetup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isRecord = void 0;
|
|
4
|
+
/** Narrows `unknown` to a plain object without a type assertion. */
|
|
5
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
6
|
+
exports.isRecord = isRecord;
|
|
7
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-key derivation for the manager E2E password lookup (GLU-1451 / GLU-1452).
|
|
3
|
+
*
|
|
4
|
+
* An `auth.json` fixture identifies its account by `username` only — the password
|
|
5
|
+
* is resolved at runtime by a stable key derived from the name: from a CircleCI
|
|
6
|
+
* env variable in CI and from Vault locally.
|
|
7
|
+
*
|
|
8
|
+
* The `E2E_PW_<SLUG>` convention MUST stay in sync with the publish side in
|
|
9
|
+
* `devtools/e2e-okta-rotation/slug.ts` (which writes the CircleCI/Vault values).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Uppercase slug of an account name: non-alphanumeric runs collapse to a single
|
|
13
|
+
* `_`, with leading/trailing separators trimmed. Keeps account names with spaces
|
|
14
|
+
* and dashes valid as env-var/Vault-field keys.
|
|
15
|
+
*
|
|
16
|
+
* `Team Helios - E2E Test` -> `TEAM_HELIOS_E2E_TEST`
|
|
17
|
+
*/
|
|
18
|
+
export declare const slugifyUsername: (username: string) => string;
|
|
19
|
+
/** The environment-variable key for an account's password: `E2E_PW_<SLUG>`. */
|
|
20
|
+
export declare const deriveEnvKey: (username: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* The Vault field key for an account's password. This is the **same** key as the
|
|
23
|
+
* env variable (`E2E_PW_<SLUG>`): the publish side writes each account's password
|
|
24
|
+
* to a Vault field named by `devtools/e2e-okta-rotation/slug.ts`'s `envKey()`
|
|
25
|
+
* (`E2E_PW_<SLUG>`), so the resolver must read it back under that exact key. A
|
|
26
|
+
* lowercased-slug field would never be found once Vault is seeded.
|
|
27
|
+
*/
|
|
28
|
+
export declare const deriveVaultField: (username: string) => string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Credential-key derivation for the manager E2E password lookup (GLU-1451 / GLU-1452).
|
|
4
|
+
*
|
|
5
|
+
* An `auth.json` fixture identifies its account by `username` only — the password
|
|
6
|
+
* is resolved at runtime by a stable key derived from the name: from a CircleCI
|
|
7
|
+
* env variable in CI and from Vault locally.
|
|
8
|
+
*
|
|
9
|
+
* The `E2E_PW_<SLUG>` convention MUST stay in sync with the publish side in
|
|
10
|
+
* `devtools/e2e-okta-rotation/slug.ts` (which writes the CircleCI/Vault values).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.deriveVaultField = exports.deriveEnvKey = exports.slugifyUsername = void 0;
|
|
14
|
+
/**
|
|
15
|
+
* Uppercase slug of an account name: non-alphanumeric runs collapse to a single
|
|
16
|
+
* `_`, with leading/trailing separators trimmed. Keeps account names with spaces
|
|
17
|
+
* and dashes valid as env-var/Vault-field keys.
|
|
18
|
+
*
|
|
19
|
+
* `Team Helios - E2E Test` -> `TEAM_HELIOS_E2E_TEST`
|
|
20
|
+
*/
|
|
21
|
+
const slugifyUsername = (username) => username
|
|
22
|
+
.toUpperCase()
|
|
23
|
+
.replace(/[^A-Z0-9]+/g, "_")
|
|
24
|
+
.replace(/^_+|_+$/g, "");
|
|
25
|
+
exports.slugifyUsername = slugifyUsername;
|
|
26
|
+
/** The environment-variable key for an account's password: `E2E_PW_<SLUG>`. */
|
|
27
|
+
const deriveEnvKey = (username) => `E2E_PW_${(0, exports.slugifyUsername)(username)}`;
|
|
28
|
+
exports.deriveEnvKey = deriveEnvKey;
|
|
29
|
+
/**
|
|
30
|
+
* The Vault field key for an account's password. This is the **same** key as the
|
|
31
|
+
* env variable (`E2E_PW_<SLUG>`): the publish side writes each account's password
|
|
32
|
+
* to a Vault field named by `devtools/e2e-okta-rotation/slug.ts`'s `envKey()`
|
|
33
|
+
* (`E2E_PW_<SLUG>`), so the resolver must read it back under that exact key. A
|
|
34
|
+
* lowercased-slug field would never be found once Vault is seeded.
|
|
35
|
+
*/
|
|
36
|
+
const deriveVaultField = (username) => (0, exports.deriveEnvKey)(username);
|
|
37
|
+
exports.deriveVaultField = deriveVaultField;
|
|
38
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type VaultKvVersion = 1 | 2;
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural view of the `node-vault` client's KV read: it returns
|
|
4
|
+
* `{ data: ... }` (KV v1) or `{ data: { data: ... } }` (KV v2). Kept as an
|
|
5
|
+
* interface so tests inject a fake and neither `node-vault` nor a real
|
|
6
|
+
* Vault/VPN is needed. `read` returns `unknown` so callers narrow structurally
|
|
7
|
+
* (no `as` cast at the boundary).
|
|
8
|
+
*/
|
|
9
|
+
export interface VaultReader {
|
|
10
|
+
read(secretPath: string): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface ResolveE2EPasswordOptions {
|
|
13
|
+
/** Process env to read (env-first precedence). Defaults to `process.env`. */
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
/**
|
|
16
|
+
* Vault client factory (local path). Returns `undefined` when Vault can't be
|
|
17
|
+
* reached/authenticated (e.g. no `~/.vault-token`). Defaults to a `node-vault`
|
|
18
|
+
* client using the local token, matching `libs/server/vault`.
|
|
19
|
+
*/
|
|
20
|
+
createVault?: (env: NodeJS.ProcessEnv) => Promise<VaultReader | undefined>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves an account's E2E password by name.
|
|
24
|
+
*
|
|
25
|
+
* Order: env variable `E2E_PW_<SLUG>` (CI path, never touches Vault) -> Vault
|
|
26
|
+
* field `<slug>` (local path). Throws a clear, account-named, redacted error on
|
|
27
|
+
* miss. The password is never included in any error/diagnostic (on failure there
|
|
28
|
+
* is no resolved value to leak).
|
|
29
|
+
*
|
|
30
|
+
* @param username - Account identifier from the fixture (`auth.json` `username`).
|
|
31
|
+
* @param options - Process env and Vault-client seams.
|
|
32
|
+
* @returns {Promise<string>} The resolved password.
|
|
33
|
+
* @throws when the fixture is a placeholder, or neither env nor Vault yields a value.
|
|
34
|
+
*/
|
|
35
|
+
export declare const resolveE2EPassword: (username: string, options?: ResolveE2EPasswordOptions) => Promise<string>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveE2EPassword = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const os = tslib_1.__importStar(require("os"));
|
|
7
|
+
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const redactSensitive_1 = require("../plugins/redactSensitive");
|
|
9
|
+
const guards_1 = require("./guards");
|
|
10
|
+
const keys_1 = require("./keys");
|
|
11
|
+
/** Logical KV path the publish side writes to (see devtools/e2e-okta-rotation). */
|
|
12
|
+
const DEFAULT_VAULT_PATH = "secret/manager/e2e-playwright";
|
|
13
|
+
/** KV v2 stores secrets at `mount/data/rest`; KV v1 uses the logical path verbatim. */
|
|
14
|
+
const toKvV2DataPath = (logicalPath) => {
|
|
15
|
+
const segments = logicalPath.replace(/^\/+|\/+$/g, "").split("/");
|
|
16
|
+
const [mount, ...rest] = segments;
|
|
17
|
+
return [mount, "data", ...rest].join("/");
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Reads a field from a node-vault KV read result. KV v2 nests the fields under
|
|
21
|
+
* `data.data`; KV v1 puts them directly on `data`. Narrows structurally.
|
|
22
|
+
*/
|
|
23
|
+
const readVaultField = (result, field) => {
|
|
24
|
+
if (!(0, guards_1.isRecord)(result)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const outer = result.data;
|
|
28
|
+
if (!(0, guards_1.isRecord)(outer)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const fields = (0, guards_1.isRecord)(outer.data) ? outer.data : outer;
|
|
32
|
+
const value = fields[field];
|
|
33
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Default Vault client: reads the local `~/.vault-token` and points `node-vault`
|
|
37
|
+
* at the endpoint from `E2E_VAULT_ENDPOINT` (or `VAULT_ENDPOINT`).
|
|
38
|
+
*
|
|
39
|
+
* The Vault address is **never hardcoded** — it is supplied by the environment,
|
|
40
|
+
* so any consumer of this package can opt into their own Vault. `node-vault` is
|
|
41
|
+
* an optional peer dependency, imported lazily and only on the local path, so
|
|
42
|
+
* consumers that only use the env-var (CI) path never load it.
|
|
43
|
+
*/
|
|
44
|
+
const defaultCreateVault = async (env) => {
|
|
45
|
+
const home = env.HOME ?? os.homedir();
|
|
46
|
+
let token;
|
|
47
|
+
try {
|
|
48
|
+
token = (0, fs_1.readFileSync)(path.join(home, ".vault-token"), "utf-8").trim();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined; // not logged in / not on VPN
|
|
52
|
+
}
|
|
53
|
+
if (token === "") {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const endpoint = env.E2E_VAULT_ENDPOINT ?? env.VAULT_ENDPOINT;
|
|
57
|
+
if (endpoint === undefined || endpoint === "") {
|
|
58
|
+
throw new Error("E2E credentials: a Vault token was found but no endpoint — set E2E_VAULT_ENDPOINT (or VAULT_ENDPOINT) to your Vault address.");
|
|
59
|
+
}
|
|
60
|
+
// Imported lazily so consumers on the env-var path (and unit tests, which
|
|
61
|
+
// inject `createVault`) never load the optional `node-vault` peer. Its absence
|
|
62
|
+
// is an expected configuration state, so fail with the resolver's actionable,
|
|
63
|
+
// redacted style instead of leaking a raw `ERR_MODULE_NOT_FOUND` stack.
|
|
64
|
+
const nodeVault = await Promise.resolve().then(() => tslib_1.__importStar(require("node-vault"))).catch(() => undefined);
|
|
65
|
+
if (nodeVault === undefined) {
|
|
66
|
+
return fail("E2E credentials: install the optional `node-vault` peer dependency for local Vault lookup, " +
|
|
67
|
+
"or set the E2E_PW_<SLUG> env variable.");
|
|
68
|
+
}
|
|
69
|
+
const client = nodeVault.default({ endpoint, token, requestOptions: { strictSSL: true } });
|
|
70
|
+
return { read: secretPath => client.read(secretPath) };
|
|
71
|
+
};
|
|
72
|
+
/** Throws an error whose message is routed through `redactSensitive` (defence in depth). */
|
|
73
|
+
const fail = (message) => {
|
|
74
|
+
throw new Error((0, redactSensitive_1.redactSensitive)(message));
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Resolves an account's E2E password by name.
|
|
78
|
+
*
|
|
79
|
+
* Order: env variable `E2E_PW_<SLUG>` (CI path, never touches Vault) -> Vault
|
|
80
|
+
* field `<slug>` (local path). Throws a clear, account-named, redacted error on
|
|
81
|
+
* miss. The password is never included in any error/diagnostic (on failure there
|
|
82
|
+
* is no resolved value to leak).
|
|
83
|
+
*
|
|
84
|
+
* @param username - Account identifier from the fixture (`auth.json` `username`).
|
|
85
|
+
* @param options - Process env and Vault-client seams.
|
|
86
|
+
* @returns {Promise<string>} The resolved password.
|
|
87
|
+
* @throws when the fixture is a placeholder, or neither env nor Vault yields a value.
|
|
88
|
+
*/
|
|
89
|
+
const resolveE2EPassword = async (username, options = {}) => {
|
|
90
|
+
if (username === "" || username === "REPLACE_ME") {
|
|
91
|
+
return fail(`E2E credentials: fixture account name is unresolved ("${username}").`);
|
|
92
|
+
}
|
|
93
|
+
const env = options.env ?? process.env;
|
|
94
|
+
const key = (0, keys_1.deriveEnvKey)(username);
|
|
95
|
+
// CI path: an injected env variable wins and Vault is never contacted.
|
|
96
|
+
const fromEnv = env[key];
|
|
97
|
+
if (fromEnv !== undefined && fromEnv !== "") {
|
|
98
|
+
return fromEnv;
|
|
99
|
+
}
|
|
100
|
+
// Local path: read the password from Vault by the same key.
|
|
101
|
+
const createVault = options.createVault ?? defaultCreateVault;
|
|
102
|
+
const vault = await createVault(env);
|
|
103
|
+
if (vault === undefined) {
|
|
104
|
+
return fail(`E2E credentials for "${username}": no env variable (${key}) and Vault is unavailable ` +
|
|
105
|
+
`(not authenticated / not on VPN). Run \`vault login -method=oidc -path=okta\` on VPN, or set the env variable.`);
|
|
106
|
+
}
|
|
107
|
+
const kvVersion = env.E2E_VAULT_KV_VERSION === "1" ? 1 : 2;
|
|
108
|
+
const logicalPath = env.E2E_VAULT_PATH ?? DEFAULT_VAULT_PATH;
|
|
109
|
+
const readPath = kvVersion === 2 ? toKvV2DataPath(logicalPath) : logicalPath;
|
|
110
|
+
let result;
|
|
111
|
+
try {
|
|
112
|
+
result = await vault.read(readPath);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return fail(`E2E credentials for "${username}": Vault read of "${readPath}" failed.`);
|
|
116
|
+
}
|
|
117
|
+
const fromVault = readVaultField(result, (0, keys_1.deriveVaultField)(username));
|
|
118
|
+
if (fromVault === undefined) {
|
|
119
|
+
return fail(`E2E credentials for "${username}": no field "${(0, keys_1.deriveVaultField)(username)}" at Vault path "${readPath}".`);
|
|
120
|
+
}
|
|
121
|
+
return fromVault;
|
|
122
|
+
};
|
|
123
|
+
exports.resolveE2EPassword = resolveE2EPassword;
|
|
124
|
+
//# sourceMappingURL=resolveE2EPassword.js.map
|
|
@@ -8,7 +8,9 @@ export interface ResolveCredentialsOptions {
|
|
|
8
8
|
fixturePath?: string;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Resolves auth credentials from (in order): explicit options,
|
|
11
|
+
* Resolves auth credentials from (in order): explicit options, a full JSON
|
|
12
|
+
* fixture (`{ username, password }`), a **name-only** fixture whose password
|
|
13
|
+
* comes from its `E2E_PW_<SLUG>` env var, then `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD`.
|
|
12
14
|
* Throws if none yield a complete credential pair.
|
|
13
15
|
*/
|
|
14
16
|
export declare const resolveCredentials: (options?: ResolveCredentialsOptions) => AuthCredentials;
|
|
@@ -4,6 +4,7 @@ exports.resolveCredentials = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const keys_1 = require("../credentials/keys");
|
|
7
8
|
const isAuthCredentials = (value) => {
|
|
8
9
|
if (typeof value !== "object" || value === null) {
|
|
9
10
|
return false;
|
|
@@ -25,7 +26,38 @@ const readFixtureCredentials = (fixturePath) => {
|
|
|
25
26
|
return parsed;
|
|
26
27
|
};
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
+
* Reads a fixture's `username` when present and usable (non-blank, not the
|
|
30
|
+
* `REPLACE_ME` placeholder), even if the fixture has no `password` — i.e. a
|
|
31
|
+
* name-only fixture, which is what remains once passwords leave the repo.
|
|
32
|
+
*/
|
|
33
|
+
const readFixtureUsername = (fixturePath) => {
|
|
34
|
+
if (!(0, fs_1.existsSync)(fixturePath)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(fixturePath, "utf-8"));
|
|
38
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const record = { ...parsed };
|
|
42
|
+
const username = record.username;
|
|
43
|
+
if (typeof username !== "string" || username === "" || username === "REPLACE_ME") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return username;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the password for a name-only fixture from the per-account env var set
|
|
50
|
+
* by the credential `globalSetup` (locally, from Vault) or by the CircleCI
|
|
51
|
+
* context (CI): `E2E_PW_<SLUG>`. Returns `undefined` when it is not set.
|
|
52
|
+
*/
|
|
53
|
+
const resolvePasswordFromEnv = (username) => {
|
|
54
|
+
const password = process.env[(0, keys_1.deriveEnvKey)(username)];
|
|
55
|
+
return password !== undefined && password !== "" ? password : undefined;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Resolves auth credentials from (in order): explicit options, a full JSON
|
|
59
|
+
* fixture (`{ username, password }`), a **name-only** fixture whose password
|
|
60
|
+
* comes from its `E2E_PW_<SLUG>` env var, then `PLAYWRIGHT_USERNAME`/`PLAYWRIGHT_PASSWORD`.
|
|
29
61
|
* Throws if none yield a complete credential pair.
|
|
30
62
|
*/
|
|
31
63
|
const resolveCredentials = (options) => {
|
|
@@ -37,13 +69,22 @@ const resolveCredentials = (options) => {
|
|
|
37
69
|
if (fromFixture !== undefined) {
|
|
38
70
|
return fromFixture;
|
|
39
71
|
}
|
|
72
|
+
// Name-only fixture: the password lives in the per-account env var.
|
|
73
|
+
const username = readFixtureUsername(fixturePath);
|
|
74
|
+
if (username !== undefined) {
|
|
75
|
+
const password = resolvePasswordFromEnv(username);
|
|
76
|
+
if (password !== undefined) {
|
|
77
|
+
return { username, password };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
40
80
|
const envUsername = process.env.PLAYWRIGHT_USERNAME;
|
|
41
81
|
const envPassword = process.env.PLAYWRIGHT_PASSWORD;
|
|
42
82
|
if (envUsername !== undefined && envUsername !== "" && envPassword !== undefined && envPassword !== "") {
|
|
43
83
|
return { username: envUsername, password: envPassword };
|
|
44
84
|
}
|
|
45
|
-
throw new Error(`No credentials provided to login(). Tried: options.credentials, ${fixturePath}
|
|
46
|
-
`
|
|
85
|
+
throw new Error(`No credentials provided to login(). Tried: options.credentials, ${fixturePath} ` +
|
|
86
|
+
`(incl. its E2E_PW_<slug> env var for a name-only fixture), PLAYWRIGHT_USERNAME/PLAYWRIGHT_PASSWORD env vars. ` +
|
|
87
|
+
`Fill in the fixture, set the env vars, or pass credentials to login().`);
|
|
47
88
|
};
|
|
48
89
|
exports.resolveCredentials = resolveCredentials;
|
|
49
90
|
//# sourceMappingURL=resolveCredentials.js.map
|
package/src/index.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ export * from "./plugins/writeFileWithPrettier";
|
|
|
8
8
|
export * from "./utils/Codeowner";
|
|
9
9
|
export * from "./utils/fileNameBuilder";
|
|
10
10
|
export * from "./utils/fileUpdater";
|
|
11
|
+
export * from "./credentials/fixtureAccounts";
|
|
12
|
+
export * from "./credentials/globalSetup";
|
|
13
|
+
export { default as e2eCredentialsGlobalSetup } from "./credentials/globalSetup";
|
|
14
|
+
export * from "./credentials/keys";
|
|
15
|
+
export * from "./credentials/resolveE2EPassword";
|
|
11
16
|
export type { FeatureFlag, FeatureFlagFixtures } from "./fixtures/featureFlags.fixture";
|
|
12
17
|
export type { CachedStorageState } from "./fixtures/hydrateStorageState";
|
|
13
18
|
export type { IrisAppFixtures, IrisAppOptions, StorybookPreviewOptions } from "./fixtures/irisApp.fixture";
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.e2eCredentialsGlobalSetup = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
5
|
tslib_1.__exportStar(require("./plugins/createLogFile"), exports);
|
|
5
6
|
tslib_1.__exportStar(require("./plugins/defaultPlaywrightConfig"), exports);
|
|
@@ -11,4 +12,14 @@ tslib_1.__exportStar(require("./plugins/writeFileWithPrettier"), exports);
|
|
|
11
12
|
tslib_1.__exportStar(require("./utils/Codeowner"), exports);
|
|
12
13
|
tslib_1.__exportStar(require("./utils/fileNameBuilder"), exports);
|
|
13
14
|
tslib_1.__exportStar(require("./utils/fileUpdater"), exports);
|
|
15
|
+
// Opt-in E2E credential resolution: resolve an account's password at runtime by
|
|
16
|
+
// name (env-var first, then Vault). Vault access uses the optional `node-vault`
|
|
17
|
+
// peer and an env-supplied endpoint — never hardcoded — so any consumer can wire
|
|
18
|
+
// their own Vault. See ./credentials.
|
|
19
|
+
tslib_1.__exportStar(require("./credentials/fixtureAccounts"), exports);
|
|
20
|
+
tslib_1.__exportStar(require("./credentials/globalSetup"), exports);
|
|
21
|
+
var globalSetup_1 = require("./credentials/globalSetup");
|
|
22
|
+
Object.defineProperty(exports, "e2eCredentialsGlobalSetup", { enumerable: true, get: function () { return tslib_1.__importDefault(globalSetup_1).default; } });
|
|
23
|
+
tslib_1.__exportStar(require("./credentials/keys"), exports);
|
|
24
|
+
tslib_1.__exportStar(require("./credentials/resolveE2EPassword"), exports);
|
|
14
25
|
//# sourceMappingURL=index.js.map
|
package/migrations/entry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-playwright/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\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":"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, request, type Page } from \"@playwright/test\";\nimport { hydrateStorageState, type CachedStorageState } from \"./hydrateStorageState\";\nimport { buildLoginCacheKey } from \"./loginCacheKey\";\nimport { resolveCredentials, type AuthCredentials, type ResolveCredentialsOptions } from \"./resolveCredentials\";\n\nexport type { AuthCredentials };\n\nexport type LoginOptions = ResolveCredentialsOptions;\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) => {\n const loginFn = async (options?: LoginOptions) => {\n const credentials = resolveCredentials(options);\n const cacheKey = buildLoginCacheKey(baseURL, credentials);\n const cached = _loginCache.get(cacheKey);\n\n if (cached !== undefined) {\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 return;\n } catch {\n _loginCache.delete(cacheKey);\n }\n }\n\n await performOktaLogin({ page, baseURL, credentials });\n\n // Capture the fully-authenticated state for reuse by later tests in\n // this worker. Errors here would mask the actual login success, so we\n // never let snapshot capture throw — at worst we'll re-hit Okta next\n // time, which is the pre-cache behaviour.\n try {\n const snapshot = await page.context().storageState();\n _loginCache.set(cacheKey, snapshot);\n } catch {\n /* Snapshot capture is best-effort; failing here is non-fatal. */\n }\n };\n\n await provide(loginFn);\n },\n});\n\n/**\n * Performs the slow path: hit Okta's `/api/v1/authn`, redeem the\n * `sessionToken` via the manager-classic redirect, and wait for the host\n * layout to render. Factored out so the cache hit/miss branches read clearly.\n */\nconst performOktaLogin = async ({\n page,\n baseURL,\n credentials,\n}: {\n page: Page;\n baseURL: string | undefined;\n credentials: AuthCredentials;\n}): Promise<void> => {\n const context = await request.newContext({ baseURL });\n try {\n const envResponse = await context.get(\"/env\");\n const env = await envResponse.json();\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 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: 20000,\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 page.goto(`/auth/manager-classic#session_token=${sessionToken}&userProfile=true`);\n await page.waitForURL(url => !url.href.includes(\"manager-classic\"), { timeout: 20000 });\n await page.locator(\"#host-layout-content\").waitFor({ state: \"visible\", timeout: 15000 });\n } finally {\n await context.dispose();\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":";;;;AAAA,2BAA8C;AAC9C,mDAA6B;AAa7B,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE;IACrE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,GAAG,KAAK,EAAE,CAAC;IACrD,OAAO,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;AACpF,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,WAAmB,EAA+B,EAAE;IAClF,IAAI,CAAC,IAAA,eAAU,EAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,YAAY,IAAI,MAAM,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QACzE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;;GAGG;AACI,MAAM,kBAAkB,GAAG,CAAC,OAAmC,EAAmB,EAAE;IACzF,IAAI,OAAO,EAAE,WAAW,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,OAAO,CAAC,WAAW,CAAC;IAC7B,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAC5G,MAAM,WAAW,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACxD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACpD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACpD,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,EAAE,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACvG,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IAC1D,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mEAAmE,WAAW,sDAAsD;QAClI,0FAA0F,CAC7F,CAAC;AACJ,CAAC,CAAC;AAlBW,QAAA,kBAAkB,sBAkB7B","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\nimport * as path from \"path\";\n\nexport interface AuthCredentials {\n username: string;\n password: string;\n}\n\nexport interface ResolveCredentialsOptions {\n credentials?: AuthCredentials;\n /** Path to a JSON file with `{ username, password }`. Defaults to `playwright/fixtures/auth.json` relative to cwd. */\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 * Resolves auth credentials from (in order): explicit options, JSON fixture file, env vars.\n * Throws if none yield a complete credential pair.\n */\nexport const resolveCredentials = (options?: ResolveCredentialsOptions): 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 const envUsername = process.env.PLAYWRIGHT_USERNAME;\n const envPassword = process.env.PLAYWRIGHT_PASSWORD;\n if (envUsername !== undefined && envUsername !== \"\" && envPassword !== undefined && envPassword !== \"\") {\n return { username: envUsername, password: envPassword };\n }\n throw new Error(\n `No credentials provided to login(). Tried: options.credentials, ${fixturePath}, PLAYWRIGHT_USERNAME/PLAYWRIGHT_PASSWORD env vars. ` +\n `Fill in playwright/fixtures/auth.json, set the env vars, 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✅ 🚀 \\nPlease update credentials in: \n ${joinPathFragments(projectRoot, \"playwright/fixtures/auth.json\")}\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","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\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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redactHar.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/redactHar.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, writeFileSync } from \"fs\";\nimport { redactSensitive, SENSITIVE_KEYS } from \"./redactSensitive\";\n\nconst SENSITIVE_HEADER_NAMES = new Set(\n [...SENSITIVE_KEYS, \"authorization\", \"cookie\", \"set-cookie\", \"x-api-key\", \"x-auth-token\", \"proxy-authorization\"].map(\n n => n.toLowerCase()\n )\n);\n\ninterface HarHeader {\n name?: unknown;\n value?: unknown;\n}\n\ninterface HarCookie {\n name?: unknown;\n value?: unknown;\n}\n\ninterface HarPostData {\n text?: unknown;\n mimeType?: unknown;\n params?: unknown;\n}\n\ninterface HarRequest {\n url?: unknown;\n headers?: unknown;\n cookies?: unknown;\n queryString?: unknown;\n postData?: unknown;\n}\n\ninterface HarResponse {\n url?: unknown;\n headers?: unknown;\n cookies?: unknown;\n content?: unknown;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst redactHeaderArray = (headers: unknown): unknown => {\n if (!Array.isArray(headers)) return headers;\n return headers.map((h: HarHeader) => {\n if (!isRecord(h)) return h;\n const name = typeof h.name === \"string\" ? h.name : \"\";\n if (SENSITIVE_HEADER_NAMES.has(name.toLowerCase())) {\n return { ...h, value: \"***\" };\n }\n return h;\n });\n};\n\nconst redactCookieArray = (cookies: unknown): unknown => {\n if (!Array.isArray(cookies)) return cookies;\n return cookies.map((c: HarCookie) => (isRecord(c) ? { ...c, value: \"***\" } : c));\n};\n\nconst redactQueryString = (qs: unknown): unknown => {\n if (!Array.isArray(qs)) return qs;\n return qs.map(entry => {\n if (!isRecord(entry)) return entry;\n const name = typeof entry.name === \"string\" ? entry.name : \"\";\n if (SENSITIVE_HEADER_NAMES.has(name.toLowerCase())) {\n return { ...entry, value: \"***\" };\n }\n return entry;\n });\n};\n\nconst redactStringField = (value: unknown): unknown => (typeof value === \"string\" ? redactSensitive(value) : value);\n\nconst redactPostData = (postData: unknown): unknown => {\n if (!isRecord(postData)) return postData;\n const next: HarPostData & Record<string, unknown> = { ...postData };\n if (typeof postData.text === \"string\") {\n next.text = redactSensitive(postData.text);\n }\n if (Array.isArray(postData.params)) {\n next.params = postData.params.map(p => {\n if (!isRecord(p)) return p;\n const name = typeof p.name === \"string\" ? p.name : \"\";\n if (SENSITIVE_HEADER_NAMES.has(name.toLowerCase())) {\n return { ...p, value: \"***\" };\n }\n return p;\n });\n }\n return next;\n};\n\nconst redactContent = (content: unknown): unknown => {\n if (!isRecord(content)) return content;\n if (typeof content.text === \"string\") {\n return { ...content, text: redactSensitive(content.text) };\n }\n return content;\n};\n\nconst redactRequest = (request: unknown): unknown => {\n if (!isRecord(request)) return request;\n const next: HarRequest & Record<string, unknown> = { ...request };\n next.url = redactStringField(request.url);\n next.headers = redactHeaderArray(request.headers);\n next.cookies = redactCookieArray(request.cookies);\n next.queryString = redactQueryString(request.queryString);\n next.postData = redactPostData(request.postData);\n return next;\n};\n\nconst redactResponse = (response: unknown): unknown => {\n if (!isRecord(response)) return response;\n const next: HarResponse & Record<string, unknown> = { ...response };\n next.url = redactStringField(response.url);\n next.headers = redactHeaderArray(response.headers);\n next.cookies = redactCookieArray(response.cookies);\n next.content = redactContent(response.content);\n return next;\n};\n\n/**\n * Strips sensitive headers, cookies, query parameters, and bodies from a HAR document.\n * Returns a new object — the input is not mutated.\n *\n * Redacts:\n * - Headers matching SENSITIVE_HEADER_NAMES (Authorization, Cookie, Set-Cookie, etc.)\n * - All cookie values (request & response)\n * - Query-string parameters with sensitive names\n * - Form params with sensitive names\n * - JSON-like text in request/response bodies via redactSensitive\n */\nexport const redactHar = (har: unknown): unknown => {\n if (!isRecord(har)) return har;\n const log = har.log;\n if (!isRecord(log)) return har;\n const entries = log.entries;\n if (!Array.isArray(entries)) return har;\n\n const redactedEntries = entries.map(entry => {\n if (!isRecord(entry)) return entry;\n return {\n ...entry,\n request: redactRequest(entry.request),\n response: redactResponse(entry.response),\n };\n });\n\n return {\n ...har,\n log: {\n ...log,\n entries: redactedEntries,\n },\n };\n};\n\n/**\n * Reads a HAR file, redacts sensitive content, and writes it to a new path.\n * The source file is unchanged. Returns the bytes written.\n */\nexport const redactHarFile = (sourcePath: string, destPath: string): void => {\n const raw = readFileSync(sourcePath, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n const redacted = redactHar(parsed);\n writeFileSync(destPath, JSON.stringify(redacted));\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redactPatterns.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/redactPatterns.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH;;;;;GAKG;AACU,QAAA,GAAG,GAAG,WAAW,CAAC;AAC/B,+CAA+C;AAClC,QAAA,KAAK,GAAG,GAAG,WAAG,GAAG,CAAC;AAC/B,iDAAiD;AACpC,QAAA,QAAQ,GAAG,KAAK,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,GAAW,EAAE,CAAC,aAAa,CAAC;AAEtD,MAAM,iBAAiB,GAAG,IAAI,MAAM,CAAC,GAAG,aAAK,QAAQ,aAAK,YAAY,aAAK,EAAE,EAAE,GAAG,CAAC,CAAC;AACpF,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAAC,GAAG,aAAK,MAAM,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,aAAK,OAAO,EAAE,GAAG,CAAC,CAAC;AAEpG,uFAAuF;AACvF,MAAM,kBAAkB,GAAG,GAAW,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,aAAK,QAAQ,aAAK,YAAY,aAAK,UAAU,aAAK,GAAG,EAAE,IAAI,CAAC,CAAC;AAErH;;;;GAIG;AACH,MAAM,cAAc,GAAG,CAAC,UAAkB,EAAW,EAAE,CACrD,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAE7E,0EAA0E;AACnE,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAU,EAAE,CACzD,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,UAAU,CAAC,EAAE,CAC7C,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,KAAK,gBAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CACtG,CAAC;AAHS,QAAA,kBAAkB,sBAG3B;AAEJ;;;;;;;GAOG;AACI,MAAM,YAAY,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC1D,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,EAAE,CAAC;QAC7D,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,SAAS;QACX,CAAC;QACD,KAAK,MAAM,UAAU,IAAI,UAAU,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC;YACnE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAZW,QAAA,YAAY,gBAYvB","sourcesContent":["/**\n * Redaction/detection primitives shared by the redactor (`redactSensitive`) and\n * the structural scanner (`scanArtifactsForUnredactedSecrets`) so the two\n * recognise the same shapes and cannot drift. In particular, browser-cookie\n * detection is centralised here: it must be property-order independent (a\n * value-last cookie is still a cookie) and must not misfire on ordinary\n * `{ name, value, path }` structures.\n */\n\n/**\n * Matches any run of backslashes preceding a quote. A JSON string nested inside\n * another escapes its quotes once per level of nesting (`\"` → `\\\"` → `\\\\\\\"` →\n * …), so the escape prefix grows without bound; matching an arbitrary run makes\n * the patterns escape-depth agnostic.\n */\nexport const ESC = \"(?:\\\\\\\\)*\";\n/** A JSON double-quote at any escape depth. */\nexport const QUOTE = `${ESC}\"`;\n/** The marker a redacted value is reduced to. */\nexport const REDACTED = \"***\";\n\n/**\n * Strong, cookie-specific marker keys. A Playwright `storageState` cookie always\n * carries these; generic keys like `path`/`expires` are deliberately excluded so\n * an ordinary `{ name, value, path }` object is not mistaken for a cookie\n * (which would redact useful debugging data and produce false positives).\n */\nconst COOKIE_MARKERS = [\"domain\", \"httpOnly\", \"sameSite\", \"secure\"];\n\n/**\n * Matches a flat JSON object literal (no nested braces) — one `addCookies`\n * entry. JSON only escapes `\"` and `\\`, never braces, so this still isolates a\n * cookie object at any escape depth.\n */\nconst flatObjectPattern = (): RegExp => /\\{[^{}]*\\}/g;\n\nconst valueFieldPattern = new RegExp(`${QUOTE}value${QUOTE}\\\\s*:\\\\s*${QUOTE}`, \"i\");\nconst cookieMarkerPattern = new RegExp(`${QUOTE}(?:${COOKIE_MARKERS.join(\"|\")})${QUOTE}\\\\s*:`, \"i\");\n\n/** Captures the `value` string of an object (group 2), for redaction or inspection. */\nconst cookieValuePattern = (): RegExp => new RegExp(`(${QUOTE}value${QUOTE}\\\\s*:\\\\s*${QUOTE})(.*?)(${QUOTE})`, \"gi\");\n\n/**\n * A flat JSON object is treated as a browser cookie when it carries a `value`\n * field together with a strong cookie marker — evaluated over the whole object,\n * so property order does not matter and a value-last cookie is still detected.\n */\nconst isCookieObject = (flatObject: string): boolean =>\n valueFieldPattern.test(flatObject) && cookieMarkerPattern.test(flatObject);\n\n/** Redacts the `value` of every browser-cookie object found in `text`. */\nexport const redactCookieValues = (text: string): string =>\n text.replace(flatObjectPattern(), flatObject =>\n isCookieObject(flatObject) ? flatObject.replace(cookieValuePattern(), `$1${REDACTED}$3`) : flatObject\n );\n\n/**\n * The `value` of every browser-cookie object in `text` (after any redaction has\n * run). The structural scanner treats a cookie whose value is not `REDACTED` as\n * a leak.\n *\n * @param text - Text to inspect (a trace stream, resource body, or plain file).\n * @returns {Array<string>} One entry per cookie `value` found.\n */\nexport const cookieValues = (text: string): Array<string> => {\n const values: Array<string> = [];\n for (const objectMatch of text.matchAll(flatObjectPattern())) {\n const flatObject = objectMatch[0];\n if (!isCookieObject(flatObject)) {\n continue;\n }\n for (const valueMatch of flatObject.matchAll(cookieValuePattern())) {\n values.push(valueMatch[2] ?? \"\");\n }\n }\n return values;\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redactSensitive.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/redactSensitive.ts"],"names":[],"mappings":";;;AAAA,qDAA6D;AAE7D;;;;;GAKG;AACU,QAAA,cAAc,GAAG;IAC5B,UAAU;IACV,cAAc;IACd,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;IACd,eAAe;IACf,SAAS;IACT,UAAU;IACV,eAAe;IACf,QAAQ;IACR,SAAS;CACV,CAAC;AAEF;;;;;;GAMG;AACU,QAAA,iBAAiB,GAAG;IAC/B,eAAe;IACf,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,cAAc;IACd,qBAAqB;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,eAAe,GAAG,CAAC,OAAe,EAAU,EAAE;IACzD,IAAI,QAAQ,GAAG,OAAO,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,sBAAc,EAAE,CAAC;QACjC,yEAAyE;QACzE,sEAAsE;QACtE,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,sBAAK,GAAG,GAAG,GAAG,sBAAK,YAAY,sBAAK,QAAQ,sBAAK,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;QACnH,uEAAuE;QACvE,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/E,CAAC;IACD,6EAA6E;IAC7E,uEAAuE;IACvE,4EAA4E;IAC5E,QAAQ,GAAG,IAAA,mCAAkB,EAAC,QAAQ,CAAC,CAAC;IACxC,KAAK,MAAM,MAAM,IAAI,yBAAiB,EAAE,CAAC;QACvC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,gBAAgB,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1G,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAjBW,QAAA,eAAe,mBAiB1B","sourcesContent":["import { QUOTE, redactCookieValues } from \"./redactPatterns\";\n\n/**\n * Keys whose values must be redacted when they appear as JSON properties\n * (`\"key\": \"value\"`) or URL-encoded params (`key=value`). Both camelCase and\n * snake_case variants are listed because Okta uses `session_token` in its\n * redirect URL while the rest of the codebase uses `sessionToken`.\n */\nexport const SENSITIVE_KEYS = [\n \"password\",\n \"sessionToken\",\n \"session_token\",\n \"accessToken\",\n \"access_token\",\n \"refreshToken\",\n \"refresh_token\",\n \"idToken\",\n \"id_token\",\n \"authorization\",\n \"apiKey\",\n \"api_key\",\n];\n\n/**\n * HTTP header names whose values must be redacted when they appear in\n * `Header-Name: value` form (e.g. log lines printed by HTTP clients, request\n * dumps in test output). Header redaction is line-scoped because header\n * values can contain `=` and `:` characters that would otherwise confuse the\n * URL/JSON patterns.\n */\nexport const SENSITIVE_HEADERS = [\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"proxy-authorization\",\n];\n\n/**\n * Redacts sensitive key-value pairs from a string in JSON, escaped-JSON,\n * URL-encoded, cookie, and `Header: value` formats.\n *\n * Replaces:\n * - `\"key\": \"<anything>\"` → `\"key\": \"***\"` (case-insensitive), at any JSON\n * escape depth — `\"key\":\"v\"`, `\\\"key\\\":\\\"v\\\"`, `\\\\\\\"key\\\\\\\":\\\\\\\"v\\\\\\\"`, … —\n * which covers request/response bodies and doubly-nested localStorage values\n * stored in Playwright trace entries.\n * - `\"value\": \"<anything>\"` → `\"value\": \"***\"` when the enclosing object is a\n * browser cookie (see `redactCookieValues` — property-order independent),\n * e.g. `addCookies` params carrying an Okta session cookie.\n * - `key=<anything>` → `key=***` (URL-encoded, value terminates at `&`/whitespace/`\"`)\n * - `Header-Name: <rest-of-line>` → `Header-Name: ***` (case-insensitive, line-scoped)\n *\n * @param message - The string to redact sensitive values from\n * @returns {string} The string with sensitive values replaced by `***`\n */\nexport const redactSensitive = (message: string): string => {\n let redacted = message;\n for (const key of SENSITIVE_KEYS) {\n // JSON string value at any escape depth: \"key\":\"v\" / \\\"key\\\":\\\"v\\\" / … .\n // The value is matched lazily up to the next (equally escaped) quote.\n redacted = redacted.replace(new RegExp(`(${QUOTE}${key}${QUOTE}\\\\s*:\\\\s*${QUOTE}).*?(${QUOTE})`, \"gi\"), \"$1***$2\");\n // URL-encoded: key=value (value terminates at &, whitespace, or quote)\n redacted = redacted.replace(new RegExp(`(${key}=)[^&\\\\s\"]+`, \"gi\"), \"$1***\");\n }\n // Cookie value under the generic `value` key (the cached Okta session cookie\n // replayed by `addCookies`). Handled via the shared, order-independent\n // cookie-shape helper so redaction and the structural scanner cannot drift.\n redacted = redactCookieValues(redacted);\n for (const header of SENSITIVE_HEADERS) {\n redacted = redacted.replace(new RegExp(`(^|[\\\\r\\\\n])(${header}\\\\s*:\\\\s*)[^\\\\r\\\\n]*`, \"gim\"), \"$1$2***\");\n }\n return redacted;\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redactTrace.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/redactTrace.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, writeFileSync } from \"fs\";\nimport JSZip from \"jszip\";\nimport { redactSensitive, SENSITIVE_HEADERS, SENSITIVE_KEYS } from \"./redactSensitive\";\n\n/**\n * File suffixes inside a Playwright trace.zip whose contents are JSON Lines\n * with action/network metadata — i.e. the places where a `page.goto(url)`\n * call's URL argument is recorded verbatim.\n */\nconst TEXT_TRACE_SUFFIXES: ReadonlyArray<string> = [\".trace\", \".network\", \".stacks\"];\n\nconst isTextTraceEntry = (relPath: string): boolean => TEXT_TRACE_SUFFIXES.some(s => relPath.endsWith(s));\n\n/**\n * Lower-cased substrings whose presence is a necessary precondition for\n * `redactSensitive` to alter a buffer: it only rewrites JSON/URL matches keyed\n * on a `SENSITIVE_KEYS`/`SENSITIVE_HEADERS` name, or a cookie `value`. Keep this\n * a superset of `redactSensitive`'s triggers so the prefilter never skips an\n * entry it would change.\n */\nconst SENSITIVE_MARKERS: ReadonlyArray<string> = [\n ...new Set([...SENSITIVE_KEYS, ...SENSITIVE_HEADERS, \"value\"].map(s => s.toLowerCase())),\n];\n\n/**\n * True when `buf` may contain a value `redactSensitive` would redact, decided by\n * a case-insensitive byte scan — no UTF-8 decode. A `false` result guarantees\n * redaction is a no-op, letting the caller skip the decode/round-trip on the\n * binary bulk of a trace (screencast frames, screenshots, fonts) that dominates\n * a real `trace.zip`, with byte-identical output.\n */\nconst mightContainSensitive = (buf: Buffer): boolean => {\n const lower = Buffer.from(buf);\n for (let i = 0; i < lower.length; i++) {\n const byte = lower[i];\n if (byte !== undefined && byte >= 0x41 && byte <= 0x5a) {\n lower[i] = byte + 0x20; // ASCII upper-case → lower-case\n }\n }\n return SENSITIVE_MARKERS.some(marker => lower.includes(marker));\n};\n\n/**\n * Returns the UTF-8 decoding of `buf` only when it round-trips losslessly (the\n * re-encoded text is byte-identical to the input). This is how we tell a textual\n * resource body (a network request/response the Trace Viewer renders) from a\n * binary one (screenshot, font): a JPEG/PNG/woff decodes to replacement chars\n * and fails the round-trip, so we never re-encode — and therefore never corrupt —\n * a binary resource. Returns `undefined` for binary content.\n */\nconst asLosslessUtf8 = (buf: Buffer): string | undefined => {\n const text = buf.toString(\"utf8\");\n return Buffer.from(text, \"utf8\").equals(buf) ? text : undefined;\n};\n\n/**\n * Rewrites a Playwright trace.zip in place with sensitive values stripped.\n *\n * Two surfaces carry secrets in a retained trace:\n *\n * 1. The `.trace`/`.network`/`.stacks` JSONL streams — e.g. the `page.goto`\n * against `/auth/manager-classic#session_token=...` records the full URL as\n * the action argument, and API-call params are inlined as `jsonData`. The\n * `login()` cache-hit path also replays the cached storageState here via\n * `addCookies` (the Okta session cookie, under the generic `value` key) and\n * `addInitScript` (the `okta-token-storage` access/id/refresh tokens, nested\n * two escape levels deep) — both handled by `redactSensitive`'s cookie and\n * escape-depth-agnostic rules.\n * 2. The hashed `resources/<sha1>` entries — Playwright stores network\n * request/response *bodies* here. The `login()` slow path POSTs the account\n * password to `/api/v1/authn`, so that body — carrying the cleartext\n * password — is retained as a resource and rendered verbatim in the Trace\n * Viewer's request panel. Redacting only the streams (the previous behaviour)\n * left this copy readable.\n *\n * Streams are always redacted as text. Resource entries are redacted only when\n * they are lossless UTF-8 text (see `asLosslessUtf8`), so binary resources\n * (screenshots, fonts) are never touched. Entries with no sensitive substring\n * are left as-is to avoid needless rewrites.\n */\nexport const redactTraceFile = async (filePath: string): Promise<void> => {\n const buf = readFileSync(filePath);\n const zip = await JSZip.loadAsync(buf);\n\n const rewrites: Array<Promise<void>> = [];\n zip.forEach((relPath, file) => {\n if (file.dir) return;\n rewrites.push(\n (async () => {\n if (isTextTraceEntry(relPath)) {\n const text = await file.async(\"text\");\n zip.file(relPath, redactSensitive(text));\n return;\n }\n // Any other entry is a resource. Redact it only when it is textual, and\n // only when redaction actually changes it — binary bytes are preserved.\n const bytes = await file.async(\"nodebuffer\");\n // Skip the UTF-8 round-trip for the binary bulk that cannot carry a\n // redactable value; `redactSensitive` would be a no-op on it anyway.\n if (!mightContainSensitive(bytes)) return;\n const decoded = asLosslessUtf8(bytes);\n if (decoded === undefined) return;\n const redacted = redactSensitive(decoded);\n if (redacted !== decoded) {\n zip.file(relPath, redacted);\n }\n })()\n );\n });\n await Promise.all(rewrites);\n\n const out = await zip.generateAsync({ type: \"nodebuffer\", compression: \"DEFLATE\" });\n writeFileSync(filePath, out);\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scanArtifactsForSecrets.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/scanArtifactsForSecrets.ts"],"names":[],"mappings":";;;;AAAA,2BAA+C;AAC/C,0DAA0B;AAC1B,mDAA6B;AAC7B,qDAAiE;AACjE,uDAAmD;AAanD;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,OAA8B,EAAiB,EAAE,CACjF,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAEtE,MAAM,OAAO,GAAG,KAAK,EAAE,OAAe,EAAE,OAA8B,EAAqC,EAAE;IAC3G,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,SAAS,CAAC,IAAA,iBAAY,EAAC,OAAO,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAuC,EAAE,CAAC;IACvD,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;QAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAA6B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QACtC,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;YAC1E,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACI,MAAM,uBAAuB,GAAG,KAAK,EAC1C,GAAW,EACX,OAA8B,EACK,EAAE;IACrC,MAAM,IAAI,GAA6B,EAAE,CAAC;IAE1C,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QACpD,4EAA4E;QAC5E,2EAA2E;QAC3E,yDAAyD;QACzD,KAAK,MAAM,MAAM,IAAI,IAAA,gBAAW,EAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACnE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrB,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC7C,SAAS;YACX,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,IAAA,iBAAY,EAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC9D,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AA/BW,QAAA,uBAAuB,2BA+BlC;AAEF;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnD;;;;GAIG;AACH,MAAM,eAAe,GAAG,CAAC,GAAW,EAAsB,EAAE;IAC1D,IAAI,GAAG,CAAC,MAAM,GAAG,yBAAyB,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAClE,CAAC,CAAC;AAeF,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAoD,EAAE;IAC7F,MAAM,KAAK,GAAqD,EAAE,CAAC;IACnE,KAAK,MAAM,GAAG,IAAI,gCAAc,EAAE,CAAC;QACjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,sBAAK,GAAG,GAAG,GAAG,sBAAK,YAAY,sBAAK,QAAQ,sBAAK,EAAE,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;YAC5G,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,yBAAQ,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,GAAG,cAAc,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;YAC1E,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,yBAAQ,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,8EAA8E;IAC9E,KAAK,MAAM,KAAK,IAAI,IAAA,6BAAY,EAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,KAAK,KAAK,yBAAQ,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACI,MAAM,iCAAiC,GAAG,KAAK,EAAE,GAAW,EAAuC,EAAE;IAC1G,MAAM,IAAI,GAA+B,EAAE,CAAC;IAE5C,MAAM,iBAAiB,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QACjE,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,SAAS,CAAC,IAAA,iBAAY,EAAC,OAAO,CAAC,CAAC,CAAC;QACzD,MAAM,OAAO,GAAuC,EAAE,CAAC;QACvD,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAChC,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;YAC7D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,SAAS;YACX,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QACpD,KAAK,MAAM,MAAM,IAAI,IAAA,gBAAW,EAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACnE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrB,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;gBAC9B,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,eAAe,CAAC,IAAA,iBAAY,EAAC,IAAI,CAAC,CAAC,CAAC;YACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,SAAS;YACX,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAhDW,QAAA,iCAAiC,qCAgD5C","sourcesContent":["import { readdirSync, readFileSync } from \"fs\";\nimport JSZip from \"jszip\";\nimport * as path from \"path\";\nimport { cookieValues, QUOTE, REDACTED } from \"./redactPatterns\";\nimport { SENSITIVE_KEYS } from \"./redactSensitive\";\n\n/**\n * A cleartext-secret occurrence found while scanning produced test artifacts.\n * `entry` is set when the hit is inside a zip (e.g. a Playwright `trace.zip`\n * resource body); otherwise the secret was found directly in `file`.\n */\nexport interface ArtifactSecretHit {\n readonly file: string;\n readonly entry?: string;\n readonly secret: string;\n}\n\n/**\n * Searches `buf`'s raw bytes for each secret. `Buffer.prototype.includes`\n * matches the UTF-8 bytes of the (ASCII) secret directly, so we never decode\n * the buffer to a string: binary artifacts (video, screenshots) simply yield no\n * hits, and an oversized artifact can't trip `Cannot create a string longer\n * than 0x1fffffe8 characters` and abort the scan.\n */\nconst findSecrets = (buf: Buffer, secrets: ReadonlyArray<string>): Array<string> =>\n secrets.filter(secret => secret.length > 0 && buf.includes(secret));\n\nconst scanZip = async (zipPath: string, secrets: ReadonlyArray<string>): Promise<Array<ArtifactSecretHit>> => {\n const zip = await JSZip.loadAsync(readFileSync(zipPath));\n const entries: Array<[string, JSZip.JSZipObject]> = [];\n zip.forEach((relPath, file) => {\n if (!file.dir) {\n entries.push([relPath, file]);\n }\n });\n\n const hits: Array<ArtifactSecretHit> = [];\n for (const [relPath, file] of entries) {\n for (const secret of findSecrets(await file.async(\"nodebuffer\"), secrets)) {\n hits.push({ file: zipPath, entry: relPath, secret });\n }\n }\n return hits;\n};\n\n/**\n * Recursively scans a produced-artifacts directory for cleartext `secrets`,\n * looking *inside* `trace.zip` archives (including their hashed `resources/*`\n * network-body entries) as well as plain files (logs, HAR, `error-context.md`,\n * screenshots, video). Intended as a regression guard: after redaction the\n * returned list must be empty for both passing and failing runs.\n *\n * Only catches secrets known verbatim (e.g. the account password from config).\n * For per-run-minted credentials whose value is not known in advance (the Okta\n * session cookie, `okta-token-storage` tokens) use\n * {@link scanArtifactsForUnredactedSecrets}.\n *\n * @param dir - Root artifacts directory to walk.\n * @param secrets - Cleartext values that must not appear anywhere.\n * @returns {Promise<Array<ArtifactSecretHit>>} Every occurrence found.\n */\nexport const scanArtifactsForSecrets = async (\n dir: string,\n secrets: ReadonlyArray<string>\n): Promise<Array<ArtifactSecretHit>> => {\n const hits: Array<ArtifactSecretHit> = [];\n\n const walk = async (current: string): Promise<void> => {\n // `withFileTypes` gives the entry type without a per-entry `statSync` — and\n // without following symlinks, so a dangling link can't throw and abort the\n // walk. Only descend directories and read regular files.\n for (const dirent of readdirSync(current, { withFileTypes: true })) {\n const full = path.join(current, dirent.name);\n if (dirent.isDirectory()) {\n await walk(full);\n continue;\n }\n if (!dirent.isFile()) {\n continue;\n }\n if (full.endsWith(\".zip\")) {\n hits.push(...(await scanZip(full, secrets)));\n continue;\n }\n for (const secret of findSecrets(readFileSync(full), secrets)) {\n hits.push({ file: full, secret });\n }\n }\n };\n\n await walk(dir);\n return hits;\n};\n\n/**\n * Text artifacts that carry redactable structure (trace streams, `error-context`,\n * HAR, logs) are small; media (video, screenshots) is large binary that would\n * waste work and risk the `Cannot create a string longer than 0x1fffffe8\n * characters` throw. Cap the bytes we decode for structural scanning.\n */\nconst MAX_STRUCTURAL_SCAN_BYTES = 32 * 1024 * 1024;\n\n/**\n * Decodes `buf` as UTF-8 only when it round-trips losslessly (so binary\n * resources are skipped) and is within the size cap. Returns `undefined`\n * otherwise.\n */\nconst asScannableText = (buf: Buffer): string | undefined => {\n if (buf.length > MAX_STRUCTURAL_SCAN_BYTES) {\n return undefined;\n }\n const text = buf.toString(\"utf8\");\n return Buffer.from(text, \"utf8\").equals(buf) ? text : undefined;\n};\n\n/**\n * A *structural* leak: a value that should have been redacted (it sits under a\n * sensitive JSON key, a URL-encoded sensitive param, or a browser cookie\n * `value`) but is not `***`. Needs no verbatim secret, so it catches\n * per-run-minted credentials whose value CI cannot know in advance.\n */\nexport interface StructuralSecretHit {\n readonly file: string;\n readonly entry?: string;\n readonly key: string;\n readonly kind: \"json\" | \"url\" | \"cookie\";\n}\n\nconst findStructuralLeaks = (text: string): Array<Pick<StructuralSecretHit, \"key\" | \"kind\">> => {\n const leaks: Array<Pick<StructuralSecretHit, \"key\" | \"kind\">> = [];\n for (const key of SENSITIVE_KEYS) {\n for (const match of text.matchAll(new RegExp(`${QUOTE}${key}${QUOTE}\\\\s*:\\\\s*${QUOTE}(.*?)${QUOTE}`, \"gi\"))) {\n if (match[1] !== REDACTED) {\n leaks.push({ key, kind: \"json\" });\n }\n }\n for (const match of text.matchAll(new RegExp(`${key}=([^&\\\\s\"]+)`, \"gi\"))) {\n if (match[1] !== REDACTED) {\n leaks.push({ key, kind: \"url\" });\n }\n }\n }\n // Cookie detection uses the same order-independent shape helper as redaction,\n // so a value-last cookie cannot be leaked by the redactor yet reported clean.\n for (const value of cookieValues(text)) {\n if (value !== REDACTED) {\n leaks.push({ key: \"value\", kind: \"cookie\" });\n }\n }\n return leaks;\n};\n\n/**\n * Recursively scans a produced-artifacts directory for *structural* leaks —\n * sensitive values that were not reduced to `***`. Unlike\n * {@link scanArtifactsForSecrets} it needs no verbatim secret, so it catches the\n * Okta session cookie and `okta-token-storage` tokens replayed on the `login()`\n * cache-hit path, whose values are minted per run. Intended as a CI guard\n * alongside the verbatim scan: the returned list must be empty after redaction.\n *\n * @param dir - Root artifacts directory to walk.\n * @returns {Promise<Array<StructuralSecretHit>>} Every unredacted sensitive value found.\n */\nexport const scanArtifactsForUnredactedSecrets = async (dir: string): Promise<Array<StructuralSecretHit>> => {\n const hits: Array<StructuralSecretHit> = [];\n\n const scanZipStructural = async (zipPath: string): Promise<void> => {\n const zip = await JSZip.loadAsync(readFileSync(zipPath));\n const entries: Array<[string, JSZip.JSZipObject]> = [];\n zip.forEach((relPath, file) => {\n if (!file.dir) {\n entries.push([relPath, file]);\n }\n });\n for (const [relPath, file] of entries) {\n const text = asScannableText(await file.async(\"nodebuffer\"));\n if (text === undefined) {\n continue;\n }\n for (const leak of findStructuralLeaks(text)) {\n hits.push({ file: zipPath, entry: relPath, ...leak });\n }\n }\n };\n\n const walk = async (current: string): Promise<void> => {\n for (const dirent of readdirSync(current, { withFileTypes: true })) {\n const full = path.join(current, dirent.name);\n if (dirent.isDirectory()) {\n await walk(full);\n continue;\n }\n if (!dirent.isFile()) {\n continue;\n }\n if (full.endsWith(\".zip\")) {\n await scanZipStructural(full);\n continue;\n }\n const text = asScannableText(readFileSync(full));\n if (text === undefined) {\n continue;\n }\n for (const leak of findStructuralLeaks(text)) {\n hits.push({ file: full, ...leak });\n }\n }\n };\n\n await walk(dir);\n return hits;\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setupPlugins.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/setupPlugins.ts"],"names":[],"mappings":";;;;AACA,mDAA6B;AAC7B,uEAA8D;AAI9D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,MAAc,EAAU,EAAE;IACxE,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,qBAAqB,GAAG,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,YAAY,GAAG,CAC1B,MAAqC,EACrC,SAAoB,EACW,EAAE;IACjC,MAAM,MAAM,GAAG,IAAA,2CAAiB,GAAE,CAAC;IAEnC,+EAA+E;IAC/E,4EAA4E;IAC5E,+DAA+D;IAC/D,0EAA0E;IAC1E,MAAM,aAAa,GACjB,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;QAClC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;QAC/C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAwB;QAC3C,UAAU;QACV,UAAU;QACV,MAAM;QACN,SAAS;KACV,CAAC;IAEF,MAAM,iBAAiB,GAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,CAAC,EAAE,eAAe,CAAC,CAAC;IAE5G,MAAM,iBAAiB,GAA+B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5G,OAAO;QACL,GAAG,MAAM;QACT,QAAQ,EAAE,CAAC,GAAG,iBAAiB,EAAE,iBAAiB,CAAC;KACpD,CAAC;AACJ,CAAC,CAAC;AAhCW,QAAA,YAAY,gBAgCvB","sourcesContent":["import type { PlaywrightTestConfig, ReporterDescription } from \"@playwright/test\";\nimport * as path from \"path\";\nimport { findWorkspaceRoot } from \"./defaultPlaywrightConfig\";\nimport type { LogsReporterOptions } from \"./logsReporter\";\nimport type { Formatter } from \"./writeFileWithPrettier\";\n\n/**\n * Resolves a Playwright `outputDir` (relative or absolute) to an absolute\n * path anchored at `nxRoot`.\n *\n * Why this exists: `defaultPlaywrightConfig` intentionally returns a relative\n * `outputDir` like `../../dist/playwright/apps/<project>/test-results` so it\n * stays portable across machines and snapshot-stable for tests. Playwright\n * itself resolves that string against `rootDir` (the directory containing\n * `playwright.config.ts`) and writes screenshots/videos/traces in the right\n * place. The custom `LogsReporter` however runs as a Playwright `Reporter`\n * and calls `mkdirSync(logsFolder, { recursive: true })` directly — Node\n * resolves the relative path against `process.cwd()`, which under\n * `nx:run-commands` is the **workspace root**. The leading `../..` then\n * walks the reporter two directories *above* the workspace, scattering log\n * files (and HARs) into a sibling repo.\n *\n * The relative path Playwright emits always has the shape\n * `<../>+<workspace-relative-subpath>`, so stripping the leading `../`s and\n * anchoring the remainder at `nxRoot` yields the same absolute directory\n * Playwright would land on — regardless of how the test runner was spawned.\n */\nconst absolutiseOutputDir = (outputDir: string, nxRoot: string): string => {\n if (path.isAbsolute(outputDir)) {\n return outputDir;\n }\n const withoutLeadingDotDots = outputDir.replace(/^(?:\\.\\.[\\\\/])+/, \"\");\n return path.resolve(nxRoot, withoutLeadingDotDots);\n};\n\n/**\n * Augments a partial Playwright config with the Trackunit logs reporter and HAR folder env setup.\n * Call this in `playwright.config.ts` after `defaultPlaywrightConfig()`.\n *\n * @param config - Partial config from `defaultPlaywrightConfig()`\n * @param formatter - Prettier formatter used by `LogsReporter` to format JSON log files\n * @returns {Partial<PlaywrightTestConfig>} A new partial config with the logs reporter merged into the reporter array\n */\nexport const setupPlugins = (\n config: Partial<PlaywrightTestConfig>,\n formatter: Formatter\n): Partial<PlaywrightTestConfig> => {\n const nxRoot = findWorkspaceRoot();\n\n // Both HARs and logs derive from the resolved `outputDir`, so every Playwright\n // artifact for a run stays inside the one `dist/playwright` tree (under the\n // per-executor partition on CI). This removes the legacy fixed\n // `dist/playwright-results` location and the `hars_folders` env override.\n const outputDirBase =\n typeof config.outputDir === \"string\"\n ? absolutiseOutputDir(config.outputDir, nxRoot)\n : path.join(nxRoot, \"dist\", \"playwright\");\n const harsFolder = path.join(outputDirBase, \"hars\");\n const logsFolder = path.join(outputDirBase, \"logs\");\n\n const reporterOptions: LogsReporterOptions = {\n logsFolder,\n harsFolder,\n nxRoot,\n formatter,\n };\n\n const logsReporterEntry: ReporterDescription = [path.resolve(__dirname, \"./logsReporter\"), reporterOptions];\n\n const existingReporters: Array<ReporterDescription> = Array.isArray(config.reporter) ? config.reporter : [];\n\n return {\n ...config,\n reporter: [...existingReporters, logsReporterEntry],\n };\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"writeFileWithPrettier.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/plugins/writeFileWithPrettier.ts"],"names":[],"mappings":";;;;AAAA,uCAAoC;AACpC,2BAAqD;AACrD,mDAA6B;AAQ7B;;;;;;;;;GASG;AACI,MAAM,qBAAqB,GAAG,KAAK,EACxC,MAAc,EACd,QAAgB,EAChB,OAAe,EACf,MAAiB,EACjB,eAAiC,EAAE,QAAQ,EAAE,OAAO,EAAE,EACvC,EAAE;IACjB,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QAC3E,eAAM,CAAC,IAAI,CAAC,0BAA0B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC;IAChC,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACxD,IAAA,kBAAa,EAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AACnD,CAAC,CAAC;AAvBW,QAAA,qBAAqB,yBAuBhC","sourcesContent":["import { logger } from \"@nx/devkit\";\nimport { WriteFileOptions, writeFileSync } from \"fs\";\nimport * as path from \"path\";\nimport { format, resolveConfig } from \"prettier\";\n\nexport type Formatter = {\n resolveConfig: typeof resolveConfig;\n format: typeof format;\n};\n\n/**\n * Writes a file with Prettier formatting applied.\n * Automatically detects parser based on file extension.\n *\n * @param nxRoot - Absolute path to the NX workspace root (used to locate `prettier.config.mjs`)\n * @param filePath - Destination file path\n * @param content - Raw file content to format and write\n * @param writer - Prettier formatter instance\n * @param writeOptions - Node.js `writeFileSync` options (default: UTF-8)\n */\nexport const writeFileWithPrettier = async (\n nxRoot: string,\n filePath: string,\n content: string,\n writer: Formatter,\n writeOptions: WriteFileOptions = { encoding: \"utf-8\" }\n): Promise<void> => {\n const prettierConfigPath = path.join(nxRoot, \"prettier.config.mjs\");\n const options = await writer.resolveConfig(prettierConfigPath).catch(error => {\n logger.info(`Prettier config error: ${String(error)}`);\n return undefined;\n });\n if (!options) {\n throw new Error(\"Could not find prettier config\");\n }\n\n if (filePath.endsWith(\"json\")) {\n options.parser = \"json\";\n } else {\n options.parser = \"typescript\";\n }\n const prettySrc = await writer.format(content, options);\n writeFileSync(filePath, prettySrc, writeOptions);\n};\n"]}
|
package/src/support.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"support.js","sourceRoot":"","sources":["../../../../../libs/iris-app-sdk/iris-app-playwright/src/support.ts"],"names":[],"mappings":";;;AAAA,8DAAoC;AACpC,8DAAoC","sourcesContent":["export * from \"./fixtures/baseTest\";\nexport * from \"./fixtures/describe\";\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Codeowner.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/utils/Codeowner.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Parses lines from a CODEOWNERS-style file into a mapping of patterns → owners.\n * Skips blank lines and comments (lines beginning with `#`).\n *\n * @param {string[]} lines - Each element is a line from your CODEOWNERS file.\n * @returns {Record<string, string>} An object whose keys are normalized path patterns and values are the owner handle.\n */\nexport const parseCodeowners = (lines: Array<string>): Record<string, string> => {\n const patterns: Record<string, string> = {};\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n const [pattern, owner] = trimmed.split(/\\s+/, 2);\n if (pattern && owner) {\n const normalizedPattern = pattern.replace(/^\\/+|\\/+$/g, \"\");\n patterns[normalizedPattern] = owner;\n }\n }\n return patterns;\n};\n\n/**\n * Converts a full team handle (potentially namespaced and with platform suffix)\n * into its \"short\" team name.\n *\n * @param {string} teamName - e.g. `\"@trackunit/backend-be\"` or `\"@trackunit/frontend-fe\"`\n * @returns {string | undefined} e.g. `\"backend\"` or `\"frontend\"`, or `undefined` if no name given.\n */\nexport const toShortTeamName = (teamName?: string): string | undefined => {\n if (!teamName) {\n return undefined;\n }\n let shortName = teamName;\n if (teamName.startsWith(\"@\")) {\n shortName = shortName.slice(shortName.indexOf(\"/\") + 1);\n }\n if (shortName.endsWith(\"-be\") || shortName.endsWith(\"-fe\")) {\n shortName = shortName.slice(0, shortName.lastIndexOf(\"-\"));\n }\n return shortName;\n};\n\n/**\n * Recursively looks up the CODEOWNER for a given file or directory path\n * by reading your workspace's CODEOWNERS file.\n *\n * @param {string} currentPath - Absolute path to the file/directory you're querying.\n * @param {string} workspaceRoot - Absolute path to your repo/workspace root.\n * @param {string} [codeownersFileName=\"TEAM_CODEOWNERS\"] - Filename to read at the root.\n * @returns {string|undefined} The owner handle or `undefined` if none found.\n */\nexport const getCodeowner = (\n currentPath: string,\n workspaceRoot: string,\n codeownersFileName = \"TEAM_CODEOWNERS\"\n): string | undefined => {\n if (!workspaceRoot) {\n return undefined;\n }\n\n const codeownersPath = path.join(workspaceRoot, codeownersFileName);\n if (!fs.existsSync(codeownersPath)) {\n return undefined;\n }\n\n const codeownersLines = fs.readFileSync(codeownersPath, \"utf8\").split(\"\\n\");\n const codeowners = parseCodeowners(codeownersLines);\n\n let relPath = path\n .relative(workspaceRoot, currentPath)\n .replace(/\\\\/g, \"/\")\n .replace(/^\\/+|\\/+$/g, \"\");\n\n let parent = path.posix.dirname(relPath);\n while (relPath !== parent) {\n const codeowner = codeowners[relPath];\n if (codeowner) {\n return codeowner;\n }\n if (parent === \".\") {\n break;\n }\n relPath = parent;\n parent = path.posix.dirname(relPath);\n }\n\n return codeowners[\".\"];\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fileNameBuilder.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/utils/fileNameBuilder.ts"],"names":[],"mappings":";;AAOA,kDAMC;AAWD,0CAGC;AA3BD;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,GAAW;IAC7C,OAAO,GAAG;SACP,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC;SACxC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAAC,QAAgB,EAAE,KAAa,EAAE,YAAqB;IACpF,MAAM,QAAQ,GAAG,GAAG,QAAQ,IAAI,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,YAAY,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;IAC7H,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC","sourcesContent":["/**\n * Sanitizes a string to be safe for use in filenames.\n * Removes special characters and normalizes whitespace and hyphens.\n *\n * @param str - The string to sanitize (e.g., test suite or test name)\n * @returns {string} Sanitized string safe for filesystem use\n */\nexport function sanitizeForFilename(str: string): string {\n return str\n .replace(/[^a-zA-Z0-9-_\\s\\[\\]\\(\\)]/g, \"\")\n .replace(/-+/g, \"-\")\n .replace(/\\s+/g, \" \")\n .replace(/^-|-$/g, \"\");\n}\n\n/**\n * Builds a standardized filename for test artifacts (e.g., screenshots, traces).\n * Combines test name, state, and attempt number into a readable filename.\n *\n * @param testName - The name of the test (will be sanitized)\n * @param state - The test state (e.g., 'passed', 'failed')\n * @param currentRetry - The current retry attempt number (0-based)\n * @returns {string} A sanitized filename in the format: \"testName (state) (attempt N)\"\n */\nexport function fileNameBuilder(testName: string, state: string, currentRetry?: number): string {\n const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : \"\"} (${state.toLowerCase()})`;\n return sanitizeForFilename(fileName);\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fileUpdater.js","sourceRoot":"","sources":["../../../../../../libs/iris-app-sdk/iris-app-playwright/src/utils/fileUpdater.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,IAAY,EAAE,OAAwC,EAAQ,EAAE;IAC3G,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEpC,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC,CAAC;AAdW,QAAA,gBAAgB,oBAc3B;AAEF;;;;;GAKG;AACI,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,IAAY,EAAQ,EAAE;IACjE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B","sourcesContent":["import { Tree } from \"@nx/devkit\";\n\n/**\n * Reads a file from an NX Tree, applies an updater function, and writes it back if changed.\n *\n * @param tree - File system tree\n * @param path - Path to source file in the Tree\n * @param updater - Function that maps the current file content to new content\n */\nexport const updateFileInTree = (tree: Tree, path: string, updater: (fileContent: string) => string): void => {\n if (!tree.exists(path)) {\n throw new Error(`File not found: ${path}`);\n }\n\n const fileContent = tree.read(path, \"utf-8\");\n if (!fileContent) {\n throw new Error(`File is empty: ${path}`);\n }\n const result = updater(fileContent);\n\n if (result !== fileContent) {\n tree.write(path, result);\n }\n};\n\n/**\n * Deletes a file from an NX Tree. No-op if the file does not exist.\n *\n * @param tree - File system tree\n * @param path - Path to source file in the Tree\n */\nexport const deleteFileInTree = (tree: Tree, path: string): void => {\n if (!tree.exists(path)) {\n return;\n }\n\n tree.delete(path);\n};\n"]}
|