@trackunit/iris-app-playwright 0.2.56 → 0.2.58

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/README.md CHANGED
@@ -31,20 +31,29 @@ This creates:
31
31
  - `playwright.config.ts` wired to `createNxPreset` + `defaultPlaywrightConfig` + `setupPlugins`
32
32
  - `playwright/tsconfig.json`
33
33
  - `playwright/support/fixtures.ts` — re-exports `test`, `expect`, and `describe`
34
- - `playwright/fixtures/auth.json` — fill in credentials
34
+ - `playwright/fixtures/auth.json` — name-only account fixture (fill in the username)
35
35
  - `playwright/tests/app.spec.ts` — sample login spec
36
36
  - `e2e` and `e2e-ui` targets in `project.json`
37
37
 
38
- ### 2. Fill in credentials
38
+ ### 2. Fill in the account fixture
39
+
40
+ `auth.json` is **name-only** — no password is committed. Put the E2E account's
41
+ identifier in it:
39
42
 
40
43
  ```json
41
44
  // playwright/fixtures/auth.json
42
45
  {
43
- "username": "your-test-user@example.com",
44
- "password": "your-test-password"
46
+ "username": "your-test-user@example.com"
45
47
  }
46
48
  ```
47
49
 
50
+ At `login()` time the password is resolved by account name — the
51
+ `E2E_PW_<SLUG>` environment variable first (set by the CircleCI context in CI),
52
+ otherwise from Vault on a developer machine (needs `vault login` on the VPN,
53
+ `E2E_VAULT_ENDPOINT` / `E2E_VAULT_PATH`, and the optional `node-vault` peer).
54
+ A full `{ "username", "password" }` fixture, or explicit `login({ credentials })`,
55
+ are also honoured.
56
+
48
57
  ### 3. Write a test
49
58
 
50
59
  ```typescript
@@ -86,7 +95,7 @@ Authenticates a user against the Trackunit platform.
86
95
  login(options?: LoginOptions): Promise<void>
87
96
  ```
88
97
 
89
- Reads credentials from `playwright/fixtures/auth.json` by default, or from `PLAYWRIGHT_USERNAME` / `PLAYWRIGHT_PASSWORD` environment variables. Navigates to `/auth/manager-classic` and waits for the host layout to be visible.
98
+ Reads the account from `playwright/fixtures/auth.json` by default. When the fixture is name-only, the password is resolved at run time — env-first (`E2E_PW_<SLUG>`, the CircleCI context in CI) then Vault locally. A full `{ username, password }` fixture or an explicit `login({ credentials })` are also honoured. Resolution happens inside the fixture (no Playwright `globalSetup`), so it works in `e2e-ui` mode too. Navigates to `/auth/manager-classic` and waits for the host layout to be visible.
90
99
 
91
100
  #### Per-worker storage state cache
92
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/iris-app-playwright",
3
- "version": "0.2.56",
3
+ "version": "0.2.58",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "generators": "./generators.json",
@@ -1,4 +1,3 @@
1
- export type VaultKvVersion = 1 | 2;
2
1
  /**
3
2
  * Minimal structural view of the `node-vault` client's KV read: it returns
4
3
  * `{ data: ... }` (KV v1) or `{ data: { data: ... } }` (KV v2). Kept as an
@@ -19,6 +18,13 @@ export interface ResolveE2EPasswordOptions {
19
18
  */
20
19
  createVault?: (env: NodeJS.ProcessEnv) => Promise<VaultReader | undefined>;
21
20
  }
21
+ /**
22
+ * Clears the in-memory resolved-password cache. The cache is a module-level
23
+ * singleton that lives for the whole process, so a resolved value would
24
+ * otherwise leak across calls (and across test cases). Call this between tests
25
+ * to keep each `resolveE2EPassword` call independent.
26
+ */
27
+ export declare const resetE2EPasswordCache: () => void;
22
28
  /**
23
29
  * Resolves an account's E2E password by name.
24
30
  *
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveE2EPassword = void 0;
3
+ exports.resolveE2EPassword = exports.resetE2EPasswordCache = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs_1 = require("fs");
6
6
  const os = tslib_1.__importStar(require("os"));
@@ -8,14 +8,6 @@ const path = tslib_1.__importStar(require("path"));
8
8
  const redactSensitive_1 = require("../plugins/redactSensitive");
9
9
  const guards_1 = require("./guards");
10
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
11
  /**
20
12
  * Reads a field from a node-vault KV read result. KV v2 nests the fields under
21
13
  * `data.data`; KV v1 puts them directly on `data`. Narrows structurally.
@@ -66,13 +58,74 @@ const defaultCreateVault = async (env) => {
66
58
  return fail("E2E credentials: install the optional `node-vault` peer dependency for local Vault lookup, " +
67
59
  "or set the E2E_PW_<SLUG> env variable.");
68
60
  }
69
- const client = nodeVault.default({ endpoint, token, requestOptions: { strictSSL: true } });
61
+ // `timeout` bounds the whole request so an off-VPN endpoint that black-holes
62
+ // the connection fails fast (~10s) instead of hanging until the test times out.
63
+ const client = nodeVault.default({
64
+ endpoint,
65
+ token,
66
+ requestOptions: { strictSSL: true, timeout: 10000 },
67
+ });
70
68
  return { read: secretPath => client.read(secretPath) };
71
69
  };
70
+ /**
71
+ * Node/socket error codes that mean the Vault host could not be reached at all —
72
+ * as opposed to a reachable Vault answering with a 403/404. Off-VPN, the internal
73
+ * Vault hostname either fails DNS (`ENOTFOUND`/`EAI_AGAIN`) or the connection is
74
+ * refused/timed out, so these map to an actionable "connect to the VPN" hint.
75
+ */
76
+ const VAULT_UNREACHABLE_CODES = new Set([
77
+ "ENOTFOUND",
78
+ "EAI_AGAIN",
79
+ "ECONNREFUSED",
80
+ "ECONNRESET",
81
+ "ETIMEDOUT",
82
+ "ESOCKETTIMEDOUT",
83
+ "EHOSTUNREACH",
84
+ "ENETUNREACH",
85
+ ]);
86
+ /** True when a thrown Vault-read error is a transport failure (host unreachable), not an HTTP response. */
87
+ const isVaultUnreachableError = (error) => {
88
+ if (!(0, guards_1.isRecord)(error)) {
89
+ return false;
90
+ }
91
+ const code = error.code;
92
+ return typeof code === "string" && VAULT_UNREACHABLE_CODES.has(code);
93
+ };
94
+ /**
95
+ * HTTP statuses a *reachable* Vault returns when the caller's token is missing,
96
+ * invalid, or expired — i.e. "not logged in / not authenticated" (as opposed to a
97
+ * 404 for a real missing path). `node-vault` attaches the HTTP response to the
98
+ * thrown error as `error.response.statusCode`, so we narrow to it structurally
99
+ * rather than making an extra `tokenLookupSelf` round-trip.
100
+ */
101
+ const VAULT_UNAUTHENTICATED_STATUS = new Set([401, 403]);
102
+ /** Extracts the HTTP status code from a thrown `node-vault` read error, if present. */
103
+ const getVaultErrorStatus = (error) => {
104
+ if (!(0, guards_1.isRecord)(error)) {
105
+ return undefined;
106
+ }
107
+ const response = error.response;
108
+ if (!(0, guards_1.isRecord)(response)) {
109
+ return undefined;
110
+ }
111
+ const statusCode = response.statusCode;
112
+ return typeof statusCode === "number" ? statusCode : undefined;
113
+ };
72
114
  /** Throws an error whose message is routed through `redactSensitive` (defence in depth). */
73
115
  const fail = (message) => {
74
116
  throw new Error((0, redactSensitive_1.redactSensitive)(message));
75
117
  };
118
+ const passwordCache = new Map();
119
+ /**
120
+ * Clears the in-memory resolved-password cache. The cache is a module-level
121
+ * singleton that lives for the whole process, so a resolved value would
122
+ * otherwise leak across calls (and across test cases). Call this between tests
123
+ * to keep each `resolveE2EPassword` call independent.
124
+ */
125
+ const resetE2EPasswordCache = () => {
126
+ passwordCache.clear();
127
+ };
128
+ exports.resetE2EPasswordCache = resetE2EPasswordCache;
76
129
  /**
77
130
  * Resolves an account's E2E password by name.
78
131
  *
@@ -90,11 +143,16 @@ const resolveE2EPassword = async (username, options = {}) => {
90
143
  if (username === "" || username === "REPLACE_ME") {
91
144
  return fail(`E2E credentials: fixture account name is unresolved ("${username}").`);
92
145
  }
146
+ const cached = passwordCache.get(username);
147
+ if (cached !== undefined) {
148
+ return cached;
149
+ }
93
150
  const env = options.env ?? process.env;
94
151
  const key = (0, keys_1.deriveEnvKey)(username);
95
152
  // CI path: an injected env variable wins and Vault is never contacted.
96
153
  const fromEnv = env[key];
97
154
  if (fromEnv !== undefined && fromEnv !== "") {
155
+ passwordCache.set(username, fromEnv);
98
156
  return fromEnv;
99
157
  }
100
158
  // Local path: read the password from Vault by the same key.
@@ -102,22 +160,36 @@ const resolveE2EPassword = async (username, options = {}) => {
102
160
  const vault = await createVault(env);
103
161
  if (vault === undefined) {
104
162
  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.`);
163
+ `(not authenticated / not on VPN). Run \`vault login -address=${env.E2E_VAULT_ENDPOINT} -method=oidc -path=okta\` on VPN, or set the env variable.`);
106
164
  }
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;
165
+ const logicalPath = env.E2E_VAULT_PATH;
166
+ if (!logicalPath) {
167
+ return fail("E2E_VAULT_PATH is not set");
168
+ }
169
+ const readPath = logicalPath;
110
170
  let result;
111
171
  try {
112
172
  result = await vault.read(readPath);
113
173
  }
114
- catch {
115
- return fail(`E2E credentials for "${username}": Vault read of "${readPath}" failed.`);
174
+ catch (error) {
175
+ if (isVaultUnreachableError(error)) {
176
+ return fail(`E2E credentials for "${username}": Vault endpoint is unreachable ` +
177
+ `(likely not on VPN, or E2E_VAULT_ENDPOINT is misconfigured). ` +
178
+ `Connect to the VPN and retry, or set the ${key} env variable, right now vault is trying to connect to ${env.E2E_VAULT_ENDPOINT}.`);
179
+ }
180
+ const status = getVaultErrorStatus(error);
181
+ if (status !== undefined && VAULT_UNAUTHENTICATED_STATUS.has(status)) {
182
+ return fail(`E2E credentials for "${username}": Vault rejected the request (HTTP ${status}) — ` +
183
+ `your Vault token is missing or expired. ` +
184
+ `Run something like \`vault login -address=${env.E2E_VAULT_ENDPOINT} -method=oidc -path=okta\` on VPN, or set the ${key} env variable, right now vault is trying to connect to a logged in vault at ${env.E2E_VAULT_ENDPOINT}.`);
185
+ }
186
+ return fail(`E2E credentials for "${username}": Vault read of "${readPath}" failed on host ${env.E2E_VAULT_ENDPOINT}.`);
116
187
  }
117
188
  const fromVault = readVaultField(result, (0, keys_1.deriveVaultField)(username));
118
189
  if (fromVault === undefined) {
119
190
  return fail(`E2E credentials for "${username}": no field "${(0, keys_1.deriveVaultField)(username)}" at Vault path "${readPath}".`);
120
191
  }
192
+ passwordCache.set(username, fromVault);
121
193
  return fromVault;
122
194
  };
123
195
  exports.resolveE2EPassword = resolveE2EPassword;
@@ -20,7 +20,7 @@ exports.test = test_1.test.extend({
20
20
  ],
21
21
  login: async ({ page, baseURL, _loginCache }, provide) => {
22
22
  const loginFn = async (options) => {
23
- const credentials = (0, resolveCredentials_1.resolveCredentials)(options);
23
+ const credentials = await (0, resolveCredentials_1.resolveCredentials)(options);
24
24
  const cacheKey = (0, loginCacheKey_1.buildLoginCacheKey)(baseURL, credentials);
25
25
  const cached = _loginCache.get(cacheKey);
26
26
  if (cached !== undefined) {
@@ -4,13 +4,27 @@ export interface AuthCredentials {
4
4
  }
5
5
  export interface ResolveCredentialsOptions {
6
6
  credentials?: AuthCredentials;
7
- /** Path to a JSON file with `{ username, password }`. Defaults to `playwright/fixtures/auth.json` relative to cwd. */
7
+ /**
8
+ * Path to a JSON fixture identifying the account. Either name-only
9
+ * (`{ username }`, password resolved at run time) or a full
10
+ * `{ username, password }`. Defaults to `playwright/fixtures/auth.json`
11
+ * relative to cwd.
12
+ */
8
13
  fixturePath?: string;
9
14
  }
10
15
  /**
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`.
14
- * Throws if none yield a complete credential pair.
16
+ * Resolves auth credentials for a **single** account (one `login()` call logs in
17
+ * as one user), from the first source that yields a complete pair, tried in
18
+ * order:
19
+ *
20
+ * 1. explicit `options.credentials`;
21
+ * 2. a full JSON fixture (`{ username, password }`);
22
+ * 3. a **name-only** fixture (`{ username }`), whose password is resolved at
23
+ * `login()` time by {@link resolveE2EPassword} — env-first (`E2E_PW_<SLUG>`,
24
+ * set by the CircleCI context in CI) then Vault locally. No `globalSetup` is
25
+ * involved, so this works in `e2e-ui` mode too.
26
+ *
27
+ * Throws if none yield a complete pair. Async because the name-only path (step 3)
28
+ * may reach out to Vault.
15
29
  */
16
- export declare const resolveCredentials: (options?: ResolveCredentialsOptions) => AuthCredentials;
30
+ export declare const resolveCredentials: (options?: ResolveCredentialsOptions) => Promise<AuthCredentials>;
@@ -4,7 +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
+ const resolveE2EPassword_1 = require("../credentials/resolveE2EPassword");
8
8
  const isAuthCredentials = (value) => {
9
9
  if (typeof value !== "object" || value === null) {
10
10
  return false;
@@ -46,21 +46,21 @@ const readFixtureUsername = (fixturePath) => {
46
46
  return username;
47
47
  };
48
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.
49
+ * Resolves auth credentials for a **single** account (one `login()` call logs in
50
+ * as one user), from the first source that yields a complete pair, tried in
51
+ * order:
52
+ *
53
+ * 1. explicit `options.credentials`;
54
+ * 2. a full JSON fixture (`{ username, password }`);
55
+ * 3. a **name-only** fixture (`{ username }`), whose password is resolved at
56
+ * `login()` time by {@link resolveE2EPassword} — env-first (`E2E_PW_<SLUG>`,
57
+ * set by the CircleCI context in CI) then Vault locally. No `globalSetup` is
58
+ * involved, so this works in `e2e-ui` mode too.
59
+ *
60
+ * Throws if none yield a complete pair. Async because the name-only path (step 3)
61
+ * may reach out to Vault.
52
62
  */
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`.
61
- * Throws if none yield a complete credential pair.
62
- */
63
- const resolveCredentials = (options) => {
63
+ const resolveCredentials = async (options) => {
64
64
  if (options?.credentials !== undefined) {
65
65
  return options.credentials;
66
66
  }
@@ -69,22 +69,16 @@ const resolveCredentials = (options) => {
69
69
  if (fromFixture !== undefined) {
70
70
  return fromFixture;
71
71
  }
72
- // Name-only fixture: the password lives in the per-account env var.
72
+ // Name-only fixture: resolve this one account's password on demand. Throws a
73
+ // clear, account-named error when neither env var nor Vault yields a value.
73
74
  const username = readFixtureUsername(fixturePath);
74
75
  if (username !== undefined) {
75
- const password = resolvePasswordFromEnv(username);
76
- if (password !== undefined) {
77
- return { username, password };
78
- }
79
- }
80
- const envUsername = process.env.PLAYWRIGHT_USERNAME;
81
- const envPassword = process.env.PLAYWRIGHT_PASSWORD;
82
- if (envUsername !== undefined && envUsername !== "" && envPassword !== undefined && envPassword !== "") {
83
- return { username: envUsername, password: envPassword };
76
+ const password = await (0, resolveE2EPassword_1.resolveE2EPassword)(username);
77
+ return { username, password };
84
78
  }
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().`);
79
+ throw new Error(`No credentials provided to login(). Tried: options.credentials and ${fixturePath} ` +
80
+ `(a full { username, password } fixture, or a name-only { username } fixture resolved via its ` +
81
+ `E2E_PW_<slug> env var / Vault). Fill in the fixture or pass credentials to login().`);
88
82
  };
89
83
  exports.resolveCredentials = resolveCredentials;
90
84
  //# sourceMappingURL=resolveCredentials.js.map
@@ -1,4 +1,3 @@
1
1
  {
2
- "username": "REPLACE_ME",
3
- "password": "REPLACE_ME"
2
+ "username": "REPLACE_ME"
4
3
  }
@@ -2,7 +2,10 @@ import { createNxPreset, defaultPlaywrightConfig, setupPlugins } from "@trackuni
2
2
  import { format, resolveConfig } from "prettier";
3
3
 
4
4
  const nxPreset = createNxPreset(__filename);
5
- const config = defaultPlaywrightConfig({ configDir: __dirname, projectConfig: { testDir: "./playwright/tests" } });
5
+ const config = defaultPlaywrightConfig({
6
+ configDir: __dirname,
7
+ projectConfig: { testDir: "./playwright/tests" },
8
+ });
6
9
 
7
10
  export default setupPlugins(
8
11
  {
@@ -99,8 +99,9 @@ async function playwrightConfigurationGenerator(tree, options) {
99
99
  addPlaywrightTsConfigReference(tree, projectRoot);
100
100
  await (0, devkit_1.formatFiles)(tree);
101
101
  return () => {
102
- devkit_1.logger.info(`\n\n✅ 🚀 \nPlease update credentials in:
102
+ devkit_1.logger.info(`\n\n✅ 🚀 \nSet the E2E account username in:
103
103
  ${(0, devkit_1.joinPathFragments)(projectRoot, "playwright/fixtures/auth.json")}
104
+ (the password is resolved at run time from E2E_PW_<SLUG> — the CircleCI context in CI, Vault locally)
104
105
  and open this file to get started writing tests:
105
106
  ${(0, devkit_1.joinPathFragments)(projectRoot, "playwright/tests/app.spec.ts")}`);
106
107
  };
package/src/index.d.ts CHANGED
@@ -8,9 +8,6 @@ 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
11
  export * from "./credentials/keys";
15
12
  export * from "./credentials/resolveE2EPassword";
16
13
  export type { FeatureFlag, FeatureFlagFixtures } from "./fixtures/featureFlags.fixture";
package/src/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.e2eCredentialsGlobalSetup = void 0;
4
3
  const tslib_1 = require("tslib");
5
4
  tslib_1.__exportStar(require("./plugins/createLogFile"), exports);
6
5
  tslib_1.__exportStar(require("./plugins/defaultPlaywrightConfig"), exports);
@@ -12,14 +11,10 @@ tslib_1.__exportStar(require("./plugins/writeFileWithPrettier"), exports);
12
11
  tslib_1.__exportStar(require("./utils/Codeowner"), exports);
13
12
  tslib_1.__exportStar(require("./utils/fileNameBuilder"), exports);
14
13
  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; } });
14
+ // E2E credential resolution: resolve an account's password at runtime by name
15
+ // (env-var first, then Vault) inside the `login()` fixture no `globalSetup`.
16
+ // Vault access uses the optional `node-vault` peer and an env-supplied endpoint —
17
+ // never hardcoded — so any consumer can wire their own Vault. See ./credentials.
23
18
  tslib_1.__exportStar(require("./credentials/keys"), exports);
24
19
  tslib_1.__exportStar(require("./credentials/resolveE2EPassword"), exports);
25
20
  //# sourceMappingURL=index.js.map
@@ -1,19 +0,0 @@
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>;
@@ -1,69 +0,0 @@
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
@@ -1,68 +0,0 @@
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;
@@ -1,91 +0,0 @@
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