@trackunit/iris-app-playwright 0.2.57-alpha-ee69623c029.0 → 0.2.59
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 +14 -5
- package/package.json +1 -1
- package/src/credentials/resolveE2EPassword.d.ts +7 -1
- package/src/credentials/resolveE2EPassword.js +88 -16
- package/src/fixtures/login.fixture.js +1 -1
- package/src/fixtures/resolveCredentials.d.ts +20 -6
- package/src/fixtures/resolveCredentials.js +22 -28
- package/src/generators/playwright-configuration/files/root/playwright/fixtures/auth.json__tmpl__ +1 -2
- package/src/generators/playwright-configuration/files/root/playwright.config.ts__tmpl__ +4 -1
- package/src/generators/playwright-configuration/generator.js +2 -1
- package/src/index.d.ts +0 -3
- package/src/index.js +4 -9
- package/src/credentials/fixtureAccounts.d.ts +0 -19
- package/src/credentials/fixtureAccounts.js +0 -69
- package/src/credentials/globalSetup.d.ts +0 -68
- package/src/credentials/globalSetup.js +0 -91
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
|
|
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
|
|
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
|
|
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,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
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
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
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
|
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:
|
|
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 =
|
|
76
|
-
|
|
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
|
|
86
|
-
`(
|
|
87
|
-
`Fill in the fixture
|
|
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
|
|
@@ -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({
|
|
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✅ 🚀 \
|
|
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
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// peer and an env-supplied endpoint —
|
|
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
|