@trackunit/iris-app-playwright 0.1.1
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 +230 -0
- package/generators.json +12 -0
- package/package.json +43 -0
- package/src/fixtures/baseTest.d.ts +9 -0
- package/src/fixtures/baseTest.js +12 -0
- package/src/fixtures/describe.d.ts +11 -0
- package/src/fixtures/describe.js +23 -0
- package/src/fixtures/featureFlags.fixture.d.ts +19 -0
- package/src/fixtures/featureFlags.fixture.js +76 -0
- package/src/fixtures/harRecording.fixture.d.ts +12 -0
- package/src/fixtures/harRecording.fixture.js +35 -0
- package/src/fixtures/hydrateStorageState.d.ts +36 -0
- package/src/fixtures/hydrateStorageState.js +43 -0
- package/src/fixtures/irisApp.fixture.d.ts +15 -0
- package/src/fixtures/irisApp.fixture.js +13 -0
- package/src/fixtures/localDevMode.fixture.d.ts +5 -0
- package/src/fixtures/localDevMode.fixture.js +17 -0
- package/src/fixtures/login.fixture.d.ts +24 -0
- package/src/fixtures/login.fixture.js +95 -0
- package/src/fixtures/loginCacheKey.d.ts +25 -0
- package/src/fixtures/loginCacheKey.js +29 -0
- package/src/fixtures/resolveCredentials.d.ts +14 -0
- package/src/fixtures/resolveCredentials.js +49 -0
- package/src/generators/playwright-configuration/files/root/playwright/fixtures/auth.json__tmpl__ +4 -0
- package/src/generators/playwright-configuration/files/root/playwright/support/fixtures.ts__tmpl__ +1 -0
- package/src/generators/playwright-configuration/files/root/playwright/tests/app.spec.ts__tmpl__ +16 -0
- package/src/generators/playwright-configuration/files/root/playwright/tsconfig.json__tmpl__ +15 -0
- package/src/generators/playwright-configuration/files/root/playwright.config.ts__tmpl__ +13 -0
- package/src/generators/playwright-configuration/generator.d.ts +18 -0
- package/src/generators/playwright-configuration/generator.js +77 -0
- package/src/generators/playwright-configuration/schema.d.ts +3 -0
- package/src/generators/playwright-configuration/schema.json +18 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +14 -0
- package/src/plugins/createLogFile.d.ts +24 -0
- package/src/plugins/createLogFile.js +51 -0
- package/src/plugins/defaultPlaywrightConfig.d.ts +68 -0
- package/src/plugins/defaultPlaywrightConfig.js +150 -0
- package/src/plugins/logsReporter.d.ts +55 -0
- package/src/plugins/logsReporter.js +119 -0
- package/src/plugins/nxPreset.d.ts +9 -0
- package/src/plugins/nxPreset.js +17 -0
- package/src/plugins/redactHar.d.ts +17 -0
- package/src/plugins/redactHar.js +138 -0
- package/src/plugins/redactSensitive.d.ts +31 -0
- package/src/plugins/redactSensitive.js +72 -0
- package/src/plugins/redactTrace.d.ts +12 -0
- package/src/plugins/redactTrace.js +48 -0
- package/src/plugins/setupPlugins.d.ts +11 -0
- package/src/plugins/setupPlugins.js +68 -0
- package/src/plugins/writeFileWithPrettier.d.ts +17 -0
- package/src/plugins/writeFileWithPrettier.js +37 -0
- package/src/support.d.ts +2 -0
- package/src/support.js +6 -0
- package/src/utils/Codeowner.d.ts +26 -0
- package/src/utils/Codeowner.js +89 -0
- package/src/utils/fileNameBuilder.d.ts +18 -0
- package/src/utils/fileNameBuilder.js +32 -0
- package/src/utils/fileUpdater.d.ts +16 -0
- package/src/utils/fileUpdater.js +38 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expect = exports.test = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const hydrateStorageState_1 = require("./hydrateStorageState");
|
|
6
|
+
const loginCacheKey_1 = require("./loginCacheKey");
|
|
7
|
+
const resolveCredentials_1 = require("./resolveCredentials");
|
|
8
|
+
/**
|
|
9
|
+
* Each worker is its own Node process, so a `Map` is sufficient — no
|
|
10
|
+
* cross-worker sharing concerns, no disk I/O, no cleanup needed. The cache
|
|
11
|
+
* dies with the worker, which is the desired behaviour: a fresh `nx run`
|
|
12
|
+
* always starts with empty caches.
|
|
13
|
+
*/
|
|
14
|
+
exports.test = test_1.test.extend({
|
|
15
|
+
_loginCache: [
|
|
16
|
+
async ({}, provide) => {
|
|
17
|
+
await provide(new Map());
|
|
18
|
+
},
|
|
19
|
+
{ scope: "worker" },
|
|
20
|
+
],
|
|
21
|
+
login: async ({ page, baseURL, _loginCache }, provide) => {
|
|
22
|
+
const loginFn = async (options) => {
|
|
23
|
+
const credentials = (0, resolveCredentials_1.resolveCredentials)(options);
|
|
24
|
+
const cacheKey = (0, loginCacheKey_1.buildLoginCacheKey)(baseURL, credentials);
|
|
25
|
+
const cached = _loginCache.get(cacheKey);
|
|
26
|
+
if (cached !== undefined) {
|
|
27
|
+
try {
|
|
28
|
+
await (0, hydrateStorageState_1.hydrateStorageState)(page.context(), cached);
|
|
29
|
+
await page.goto("/");
|
|
30
|
+
await page.locator("#host-layout-content").waitFor({ state: "visible", timeout: 15000 });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
_loginCache.delete(cacheKey);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await performOktaLogin({ page, baseURL, credentials });
|
|
38
|
+
// Capture the fully-authenticated state for reuse by later tests in
|
|
39
|
+
// this worker. Errors here would mask the actual login success, so we
|
|
40
|
+
// never let snapshot capture throw — at worst we'll re-hit Okta next
|
|
41
|
+
// time, which is the pre-cache behaviour.
|
|
42
|
+
try {
|
|
43
|
+
const snapshot = await page.context().storageState();
|
|
44
|
+
_loginCache.set(cacheKey, snapshot);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* Snapshot capture is best-effort; failing here is non-fatal. */
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
await provide(loginFn);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Performs the slow path: hit Okta's `/api/v1/authn`, redeem the
|
|
55
|
+
* `sessionToken` via the manager-classic redirect, and wait for the host
|
|
56
|
+
* layout to render. Factored out so the cache hit/miss branches read clearly.
|
|
57
|
+
*/
|
|
58
|
+
const performOktaLogin = async ({ page, baseURL, credentials, }) => {
|
|
59
|
+
const context = await test_1.request.newContext({ baseURL });
|
|
60
|
+
try {
|
|
61
|
+
const envResponse = await context.get("/env");
|
|
62
|
+
const env = await envResponse.json();
|
|
63
|
+
const domainValue = env.auth?.url;
|
|
64
|
+
if (typeof domainValue !== "string" || domainValue === "") {
|
|
65
|
+
throw new Error(`No domain found from servers /env found env: ${JSON.stringify(env)}`);
|
|
66
|
+
}
|
|
67
|
+
const domain = domainValue;
|
|
68
|
+
const authResponse = await context.post(`${domain}/api/v1/authn`, {
|
|
69
|
+
data: {
|
|
70
|
+
username: credentials.username,
|
|
71
|
+
password: credentials.password,
|
|
72
|
+
options: {
|
|
73
|
+
warnBeforePasswordExpired: true,
|
|
74
|
+
multiOptionalFactorEnroll: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
headers: { "Content-Type": "application/json" },
|
|
78
|
+
timeout: 20000,
|
|
79
|
+
});
|
|
80
|
+
if (!authResponse.ok()) {
|
|
81
|
+
const body = await authResponse.json().catch(() => ({}));
|
|
82
|
+
throw new Error(`Could not get a session token for user: ${credentials.username}, ${JSON.stringify(body)}`);
|
|
83
|
+
}
|
|
84
|
+
const { sessionToken } = await authResponse.json();
|
|
85
|
+
await page.goto(`/auth/manager-classic#session_token=${sessionToken}&userProfile=true`);
|
|
86
|
+
await page.waitForURL(url => !url.href.includes("manager-classic"), { timeout: 20000 });
|
|
87
|
+
await page.locator("#host-layout-content").waitFor({ state: "visible", timeout: 15000 });
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await context.dispose();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var test_2 = require("@playwright/test");
|
|
94
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
|
|
95
|
+
//# sourceMappingURL=login.fixture.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AuthCredentials } from "./resolveCredentials";
|
|
2
|
+
/**
|
|
3
|
+
* Builds the lookup key used by the worker-scoped login cache.
|
|
4
|
+
*
|
|
5
|
+
* The cache reuses an authenticated `storageState` snapshot across every test
|
|
6
|
+
* within a Playwright worker that logs in as the same `(baseURL, username)`
|
|
7
|
+
* pair. Two principles drive the key shape:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Username, not username+password.** Different passwords for the same
|
|
10
|
+
* user are almost always a credential rotation, not a deliberate second
|
|
11
|
+
* identity — if a test really needs different identities it will use a
|
|
12
|
+
* different `username`. Including the password would make a typo silently
|
|
13
|
+
* cache-miss instead of failing loudly, and would also serialise it into
|
|
14
|
+
* the key string. Kept out for both correctness and minor hygiene.
|
|
15
|
+
* 2. **`baseURL` is required.** Okta sessions are domain-bound (the auth
|
|
16
|
+
* cookies are set on the manager origin), so a snapshot from
|
|
17
|
+
* `https://dev.manager.trackunit.com` cannot authenticate a request to
|
|
18
|
+
* `https://stage.manager.trackunit.com`. We always partition by baseURL.
|
|
19
|
+
*
|
|
20
|
+
* `JSON.stringify` over a fixed-shape object guarantees no collision between
|
|
21
|
+
* (undefined baseURL, username "X") and (baseURL "X", undefined-equivalent
|
|
22
|
+
* username) — `null` and `"X"` serialise distinctly, and the field names
|
|
23
|
+
* anchor each value to its slot.
|
|
24
|
+
*/
|
|
25
|
+
export declare const buildLoginCacheKey: (baseURL: string | undefined, credentials: AuthCredentials) => string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildLoginCacheKey = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Builds the lookup key used by the worker-scoped login cache.
|
|
6
|
+
*
|
|
7
|
+
* The cache reuses an authenticated `storageState` snapshot across every test
|
|
8
|
+
* within a Playwright worker that logs in as the same `(baseURL, username)`
|
|
9
|
+
* pair. Two principles drive the key shape:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Username, not username+password.** Different passwords for the same
|
|
12
|
+
* user are almost always a credential rotation, not a deliberate second
|
|
13
|
+
* identity — if a test really needs different identities it will use a
|
|
14
|
+
* different `username`. Including the password would make a typo silently
|
|
15
|
+
* cache-miss instead of failing loudly, and would also serialise it into
|
|
16
|
+
* the key string. Kept out for both correctness and minor hygiene.
|
|
17
|
+
* 2. **`baseURL` is required.** Okta sessions are domain-bound (the auth
|
|
18
|
+
* cookies are set on the manager origin), so a snapshot from
|
|
19
|
+
* `https://dev.manager.trackunit.com` cannot authenticate a request to
|
|
20
|
+
* `https://stage.manager.trackunit.com`. We always partition by baseURL.
|
|
21
|
+
*
|
|
22
|
+
* `JSON.stringify` over a fixed-shape object guarantees no collision between
|
|
23
|
+
* (undefined baseURL, username "X") and (baseURL "X", undefined-equivalent
|
|
24
|
+
* username) — `null` and `"X"` serialise distinctly, and the field names
|
|
25
|
+
* anchor each value to its slot.
|
|
26
|
+
*/
|
|
27
|
+
const buildLoginCacheKey = (baseURL, credentials) => JSON.stringify({ baseURL: baseURL ?? null, username: credentials.username });
|
|
28
|
+
exports.buildLoginCacheKey = buildLoginCacheKey;
|
|
29
|
+
//# sourceMappingURL=loginCacheKey.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface AuthCredentials {
|
|
2
|
+
username: string;
|
|
3
|
+
password: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ResolveCredentialsOptions {
|
|
6
|
+
credentials?: AuthCredentials;
|
|
7
|
+
/** Path to a JSON file with `{ username, password }`. Defaults to `playwright/fixtures/auth.json` relative to cwd. */
|
|
8
|
+
fixturePath?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolves auth credentials from (in order): explicit options, JSON fixture file, env vars.
|
|
12
|
+
* Throws if none yield a complete credential pair.
|
|
13
|
+
*/
|
|
14
|
+
export declare const resolveCredentials: (options?: ResolveCredentialsOptions) => AuthCredentials;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCredentials = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const isAuthCredentials = (value) => {
|
|
8
|
+
if (typeof value !== "object" || value === null) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const record = { ...value };
|
|
12
|
+
return typeof record.username === "string" && typeof record.password === "string";
|
|
13
|
+
};
|
|
14
|
+
const readFixtureCredentials = (fixturePath) => {
|
|
15
|
+
if (!(0, fs_1.existsSync)(fixturePath)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(fixturePath, "utf-8"));
|
|
19
|
+
if (!isAuthCredentials(parsed)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (parsed.username === "REPLACE_ME" || parsed.password === "REPLACE_ME") {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Resolves auth credentials from (in order): explicit options, JSON fixture file, env vars.
|
|
29
|
+
* Throws if none yield a complete credential pair.
|
|
30
|
+
*/
|
|
31
|
+
const resolveCredentials = (options) => {
|
|
32
|
+
if (options?.credentials !== undefined) {
|
|
33
|
+
return options.credentials;
|
|
34
|
+
}
|
|
35
|
+
const fixturePath = options?.fixturePath ?? path.join(process.cwd(), "playwright", "fixtures", "auth.json");
|
|
36
|
+
const fromFixture = readFixtureCredentials(fixturePath);
|
|
37
|
+
if (fromFixture !== undefined) {
|
|
38
|
+
return fromFixture;
|
|
39
|
+
}
|
|
40
|
+
const envUsername = process.env.PLAYWRIGHT_USERNAME;
|
|
41
|
+
const envPassword = process.env.PLAYWRIGHT_PASSWORD;
|
|
42
|
+
if (envUsername !== undefined && envUsername !== "" && envPassword !== undefined && envPassword !== "") {
|
|
43
|
+
return { username: envUsername, password: envPassword };
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`No credentials provided to login(). Tried: options.credentials, ${fixturePath}, PLAYWRIGHT_USERNAME/PLAYWRIGHT_PASSWORD env vars. ` +
|
|
46
|
+
`Fill in playwright/fixtures/auth.json, set the env vars, or pass credentials to login().`);
|
|
47
|
+
};
|
|
48
|
+
exports.resolveCredentials = resolveCredentials;
|
|
49
|
+
//# sourceMappingURL=resolveCredentials.js.map
|
package/src/generators/playwright-configuration/files/root/playwright/support/fixtures.ts__tmpl__
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { describe, expect, test } from "@trackunit/iris-app-playwright/support";
|
package/src/generators/playwright-configuration/files/root/playwright/tests/app.spec.ts__tmpl__
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, test } from "../support/fixtures";
|
|
2
|
+
|
|
3
|
+
describe("Sample E2E Test", () => {
|
|
4
|
+
test("Can log in and navigate to app", async ({ page, login }) => {
|
|
5
|
+
await login();
|
|
6
|
+
|
|
7
|
+
// Example: Test your application here
|
|
8
|
+
// await expect(page.getByTestId("your-app-element")).toBeVisible();
|
|
9
|
+
|
|
10
|
+
// Example: Test Iris app integration
|
|
11
|
+
// const app = irisApp();
|
|
12
|
+
// await expect(app.getByTestId("your-test-id")).toBeVisible();
|
|
13
|
+
|
|
14
|
+
await expect(page.locator("#host-layout-content")).toBeVisible();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createNxPreset, defaultPlaywrightConfig, setupPlugins } from "@trackunit/iris-app-playwright";
|
|
2
|
+
import { format, resolveConfig } from "prettier";
|
|
3
|
+
|
|
4
|
+
const nxPreset = createNxPreset(__filename);
|
|
5
|
+
const config = defaultPlaywrightConfig({ configDir: __dirname, projectConfig: { testDir: "./playwright/tests" } });
|
|
6
|
+
|
|
7
|
+
export default setupPlugins(
|
|
8
|
+
{
|
|
9
|
+
...nxPreset,
|
|
10
|
+
...config,
|
|
11
|
+
},
|
|
12
|
+
{ format, resolveConfig }
|
|
13
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Tree } from "@nx/devkit";
|
|
2
|
+
import { PlaywrightConfigurationGeneratorSchema } from "./schema";
|
|
3
|
+
/**
|
|
4
|
+
* Generates Playwright E2E test configuration for an existing project.
|
|
5
|
+
*
|
|
6
|
+
* Sets up Playwright end-to-end testing infrastructure within an existing project,
|
|
7
|
+
* including configuration files, test targets, and template files that use the public
|
|
8
|
+
* iris-app-playwright library. Does not modify any existing Cypress configuration.
|
|
9
|
+
*
|
|
10
|
+
* @param tree - The NX virtual file system tree
|
|
11
|
+
* @param options - Configuration options for the generator
|
|
12
|
+
* @returns {Promise<() => void>} A function that logs completion message with the path to the generated test file
|
|
13
|
+
* @example
|
|
14
|
+
* ```bash
|
|
15
|
+
* nx generate @trackunit/iris-app-playwright:playwright-configuration --project my-app
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function playwrightConfigurationGenerator(tree: Tree, options: PlaywrightConfigurationGeneratorSchema): Promise<() => void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.playwrightConfigurationGenerator = playwrightConfigurationGenerator;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
/**
|
|
6
|
+
* Generates Playwright E2E test configuration for an existing project.
|
|
7
|
+
*
|
|
8
|
+
* Sets up Playwright end-to-end testing infrastructure within an existing project,
|
|
9
|
+
* including configuration files, test targets, and template files that use the public
|
|
10
|
+
* iris-app-playwright library. Does not modify any existing Cypress configuration.
|
|
11
|
+
*
|
|
12
|
+
* @param tree - The NX virtual file system tree
|
|
13
|
+
* @param options - Configuration options for the generator
|
|
14
|
+
* @returns {Promise<() => void>} A function that logs completion message with the path to the generated test file
|
|
15
|
+
* @example
|
|
16
|
+
* ```bash
|
|
17
|
+
* nx generate @trackunit/iris-app-playwright:playwright-configuration --project my-app
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
async function playwrightConfigurationGenerator(tree, options) {
|
|
21
|
+
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.project);
|
|
22
|
+
const projectRoot = projectConfig.root;
|
|
23
|
+
const playwrightConfigPath = (0, devkit_1.joinPathFragments)(projectRoot, "playwright.config.ts");
|
|
24
|
+
if (tree.exists(playwrightConfigPath)) {
|
|
25
|
+
throw new Error(`playwright.config.ts already exists at ${playwrightConfigPath}. Remove it before running this generator.`);
|
|
26
|
+
}
|
|
27
|
+
const existingProjectConfig = projectConfig;
|
|
28
|
+
// We use `nx:run-commands` (not `@nx/playwright:playwright`) because that executor
|
|
29
|
+
// does not honour an `env` option in target configurations — it forwards unknown options
|
|
30
|
+
// as Playwright CLI flags, so `BASE_URL` would never reach `process.env`.
|
|
31
|
+
// `nx:run-commands` sets the configuration's `env` on the spawned process, which is what
|
|
32
|
+
// `defaultPlaywrightConfig()` reads.
|
|
33
|
+
//
|
|
34
|
+
// `cwd` is pinned to `projectRoot` so `process.cwd()` inside Playwright matches the
|
|
35
|
+
// project directory. `login()`'s default fixture path is
|
|
36
|
+
// `path.join(process.cwd(), "playwright", "fixtures", "auth.json")`, and the generator
|
|
37
|
+
// scaffolds that file under `${projectRoot}/playwright/fixtures/auth.json` — without
|
|
38
|
+
// this `cwd`, nx:run-commands would spawn from the workspace root and `login()` would
|
|
39
|
+
// miss the scaffolded fixture (failing unless env vars are set).
|
|
40
|
+
const configurations = {
|
|
41
|
+
local: { env: { BASE_URL: "http://localhost:3000" } },
|
|
42
|
+
dev: { env: { BASE_URL: "https://dev.manager.trackunit.com" } },
|
|
43
|
+
stage: { env: { BASE_URL: "https://stage.manager.trackunit.com" } },
|
|
44
|
+
prod: { env: { BASE_URL: "https://new.manager.trackunit.com" } },
|
|
45
|
+
};
|
|
46
|
+
(0, devkit_1.updateProjectConfiguration)(tree, options.project, {
|
|
47
|
+
...existingProjectConfig,
|
|
48
|
+
targets: {
|
|
49
|
+
...existingProjectConfig.targets,
|
|
50
|
+
"e2e-pw-ui": {
|
|
51
|
+
executor: "nx:run-commands",
|
|
52
|
+
options: {
|
|
53
|
+
command: `playwright test --config=playwright.config.ts --headed --ui`,
|
|
54
|
+
cwd: projectRoot,
|
|
55
|
+
},
|
|
56
|
+
configurations,
|
|
57
|
+
},
|
|
58
|
+
"e2e-pw": {
|
|
59
|
+
executor: "nx:run-commands",
|
|
60
|
+
options: {
|
|
61
|
+
command: `playwright test --config=playwright.config.ts`,
|
|
62
|
+
cwd: projectRoot,
|
|
63
|
+
},
|
|
64
|
+
configurations,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, "files/root"), projectRoot, {
|
|
69
|
+
...options,
|
|
70
|
+
tmpl: "",
|
|
71
|
+
});
|
|
72
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
73
|
+
return () => {
|
|
74
|
+
devkit_1.logger.info(`\n\n\n✅ 🚀 Please open this file to get started\n${(0, devkit_1.joinPathFragments)(projectRoot, "playwright/tests/app.spec.ts")}`);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "PlaywrightConfiguration",
|
|
4
|
+
"title": "",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"project": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "feature name",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "argv",
|
|
12
|
+
"index": 0
|
|
13
|
+
},
|
|
14
|
+
"x-prompt": "Which project would you like to add Playwright E2E tests for?"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["project"]
|
|
18
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./plugins/createLogFile";
|
|
2
|
+
export * from "./plugins/defaultPlaywrightConfig";
|
|
3
|
+
export * from "./plugins/logsReporter";
|
|
4
|
+
export * from "./plugins/nxPreset";
|
|
5
|
+
export * from "./plugins/redactSensitive";
|
|
6
|
+
export * from "./plugins/setupPlugins";
|
|
7
|
+
export * from "./plugins/writeFileWithPrettier";
|
|
8
|
+
export * from "./utils/Codeowner";
|
|
9
|
+
export * from "./utils/fileNameBuilder";
|
|
10
|
+
export * from "./utils/fileUpdater";
|
|
11
|
+
export type { FeatureFlag, FeatureFlagFixtures } from "./fixtures/featureFlags.fixture";
|
|
12
|
+
export type { CachedStorageState } from "./fixtures/hydrateStorageState";
|
|
13
|
+
export type { IrisAppFixtures, IrisAppOptions, StorybookPreviewOptions } from "./fixtures/irisApp.fixture";
|
|
14
|
+
export type { LocalDevModeFixtures } from "./fixtures/localDevMode.fixture";
|
|
15
|
+
export type { AuthCredentials, LoginOptions, TrackunitBaseFixtures } from "./fixtures/login.fixture";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./plugins/createLogFile"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./plugins/defaultPlaywrightConfig"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./plugins/logsReporter"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./plugins/nxPreset"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./plugins/redactSensitive"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./plugins/setupPlugins"), exports);
|
|
10
|
+
tslib_1.__exportStar(require("./plugins/writeFileWithPrettier"), exports);
|
|
11
|
+
tslib_1.__exportStar(require("./utils/Codeowner"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./utils/fileNameBuilder"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./utils/fileUpdater"), exports);
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Formatter } from "./writeFileWithPrettier";
|
|
2
|
+
type Log = {
|
|
3
|
+
type: string;
|
|
4
|
+
message: string;
|
|
5
|
+
severity: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Creates structured log files for Playwright test runs.
|
|
9
|
+
* Generates separate files for all logs, errors, and network errors.
|
|
10
|
+
*
|
|
11
|
+
* Returns a promise that resolves once every file has been formatted and
|
|
12
|
+
* written. Callers (e.g. `LogsReporter.onTestEnd`) must await — or collect
|
|
13
|
+
* and later await — this promise to avoid fire-and-forget writes that
|
|
14
|
+
* Playwright may finish before Prettier settles.
|
|
15
|
+
*
|
|
16
|
+
* @param nxRoot - Absolute path to the NX workspace root
|
|
17
|
+
* @param logsPath - Directory where log files will be written
|
|
18
|
+
* @param fileNameWithoutExtension - Base filename (without extension) for the log files
|
|
19
|
+
* @param logs - Array of log entries to write
|
|
20
|
+
* @param logWriter - Prettier formatter instance for formatting JSON output
|
|
21
|
+
* @returns {Promise<void>} Resolves once all log files have been written
|
|
22
|
+
*/
|
|
23
|
+
export declare function createLogFile(nxRoot: string, logsPath: string, fileNameWithoutExtension: string, logs: Array<Log>, logWriter: Formatter): Promise<void>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogFile = createLogFile;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const writeFileWithPrettier_1 = require("./writeFileWithPrettier");
|
|
8
|
+
const isNetworkCall = (log) => {
|
|
9
|
+
return (log.type === "network:request" ||
|
|
10
|
+
log.type === "network:response" ||
|
|
11
|
+
log.type === "network:fetch" ||
|
|
12
|
+
log.type === "network:websocket");
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Creates structured log files for Playwright test runs.
|
|
16
|
+
* Generates separate files for all logs, errors, and network errors.
|
|
17
|
+
*
|
|
18
|
+
* Returns a promise that resolves once every file has been formatted and
|
|
19
|
+
* written. Callers (e.g. `LogsReporter.onTestEnd`) must await — or collect
|
|
20
|
+
* and later await — this promise to avoid fire-and-forget writes that
|
|
21
|
+
* Playwright may finish before Prettier settles.
|
|
22
|
+
*
|
|
23
|
+
* @param nxRoot - Absolute path to the NX workspace root
|
|
24
|
+
* @param logsPath - Directory where log files will be written
|
|
25
|
+
* @param fileNameWithoutExtension - Base filename (without extension) for the log files
|
|
26
|
+
* @param logs - Array of log entries to write
|
|
27
|
+
* @param logWriter - Prettier formatter instance for formatting JSON output
|
|
28
|
+
* @returns {Promise<void>} Resolves once all log files have been written
|
|
29
|
+
*/
|
|
30
|
+
async function createLogFile(nxRoot, logsPath, fileNameWithoutExtension, logs, logWriter) {
|
|
31
|
+
if (!(0, fs_1.existsSync)(logsPath)) {
|
|
32
|
+
fs_1.default.mkdirSync(logsPath, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
const logFilePath = path_1.default.join(logsPath, fileNameWithoutExtension);
|
|
35
|
+
const writes = [
|
|
36
|
+
(0, writeFileWithPrettier_1.writeFileWithPrettier)(nxRoot, logFilePath + "-all.json", JSON.stringify(logs), logWriter),
|
|
37
|
+
];
|
|
38
|
+
const errorCmds = logs.filter(log => log.severity === "error");
|
|
39
|
+
if (errorCmds.length > 0) {
|
|
40
|
+
writes.push((0, writeFileWithPrettier_1.writeFileWithPrettier)(nxRoot, logFilePath + "-errors.json", JSON.stringify(errorCmds), logWriter));
|
|
41
|
+
}
|
|
42
|
+
const networkErrorsCmds = logs.filter(log => isNetworkCall(log) &&
|
|
43
|
+
!log.message.includes("Status: 200") &&
|
|
44
|
+
log.severity !== "success" &&
|
|
45
|
+
!log.message.includes("sentry.io/api/"));
|
|
46
|
+
if (networkErrorsCmds.length > 0) {
|
|
47
|
+
writes.push((0, writeFileWithPrettier_1.writeFileWithPrettier)(nxRoot, logFilePath + "-network-errors.json", JSON.stringify(networkErrorsCmds), logWriter));
|
|
48
|
+
}
|
|
49
|
+
await Promise.all(writes);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=createLogFile.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { PlaywrightTestConfig } from "@playwright/test";
|
|
2
|
+
/**
|
|
3
|
+
* Finds the NX workspace root by walking up the directory tree looking for nx.json or workspace.json.
|
|
4
|
+
*
|
|
5
|
+
* @param startDir - Starting directory for the search (defaults to current working directory)
|
|
6
|
+
* @returns {string} Absolute path to the workspace root
|
|
7
|
+
* @throws Error if workspace root cannot be found
|
|
8
|
+
*/
|
|
9
|
+
export declare function findWorkspaceRoot(startDir?: string): string;
|
|
10
|
+
export interface E2EPluginConfig {
|
|
11
|
+
outputPath?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface E2EProjectConfig {
|
|
14
|
+
testDir?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface E2EBehaviorConfig {
|
|
17
|
+
timeout?: number;
|
|
18
|
+
retries?: number;
|
|
19
|
+
navigationTimeout?: number;
|
|
20
|
+
actionTimeout?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Override Playwright's worker count. Useful when the suite hits external,
|
|
23
|
+
* rate-limited services (Okta `/api/v1/authn` returns `E0000047` once you
|
|
24
|
+
* fan out past a handful of parallel logins against a shared environment).
|
|
25
|
+
*
|
|
26
|
+
* The `login` fixture caches one authenticated `storageState` per worker,
|
|
27
|
+
* so worker count is effectively the upper bound on parallel Okta calls
|
|
28
|
+
* per `(baseURL, username)` pair. Capping workers therefore caps Okta load.
|
|
29
|
+
*
|
|
30
|
+
* Leave unset to use Playwright's default (`Math.ceil(os.cpus().length / 2)`
|
|
31
|
+
* locally, `1` on CI).
|
|
32
|
+
*/
|
|
33
|
+
workers?: number | string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether tests within the same file should run in parallel.
|
|
36
|
+
*
|
|
37
|
+
* Defaults to Playwright's behaviour (`undefined`, i.e. the project-level
|
|
38
|
+
* setting wins, which in turn defaults to `false`). Set explicitly when a
|
|
39
|
+
* project wants file-level parallelism or wants to opt out of it.
|
|
40
|
+
*/
|
|
41
|
+
fullyParallel?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface E2EConfigOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Absolute path to the directory of the calling `playwright.config.ts`. Pass `__dirname`.
|
|
46
|
+
*
|
|
47
|
+
* Required when running under `@nx/playwright` (or any executor that forks Playwright from
|
|
48
|
+
* the workspace root) — without it, `process.cwd()` resolves to the workspace root and
|
|
49
|
+
* codeowner lookup / report paths land outside the intended project directory.
|
|
50
|
+
*
|
|
51
|
+
* When omitted, falls back to `process.cwd()` for backwards compatibility with consumers
|
|
52
|
+
* invoking Playwright directly from their project directory.
|
|
53
|
+
*/
|
|
54
|
+
configDir?: string;
|
|
55
|
+
nxRoot?: string;
|
|
56
|
+
outputDirOverride?: string;
|
|
57
|
+
projectConfig?: E2EProjectConfig;
|
|
58
|
+
behaviorConfig?: E2EBehaviorConfig;
|
|
59
|
+
pluginConfig?: E2EPluginConfig;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates default Playwright configuration for E2E testing.
|
|
63
|
+
* Returns a partial `PlaywrightTestConfig` that projects can spread and extend.
|
|
64
|
+
*
|
|
65
|
+
* @param options - Optional configuration overrides
|
|
66
|
+
* @returns {Partial<PlaywrightTestConfig>} Playwright test configuration
|
|
67
|
+
*/
|
|
68
|
+
export declare const defaultPlaywrightConfig: (options?: E2EConfigOptions) => Partial<PlaywrightTestConfig>;
|