@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.
Files changed (60) hide show
  1. package/README.md +230 -0
  2. package/generators.json +12 -0
  3. package/package.json +43 -0
  4. package/src/fixtures/baseTest.d.ts +9 -0
  5. package/src/fixtures/baseTest.js +12 -0
  6. package/src/fixtures/describe.d.ts +11 -0
  7. package/src/fixtures/describe.js +23 -0
  8. package/src/fixtures/featureFlags.fixture.d.ts +19 -0
  9. package/src/fixtures/featureFlags.fixture.js +76 -0
  10. package/src/fixtures/harRecording.fixture.d.ts +12 -0
  11. package/src/fixtures/harRecording.fixture.js +35 -0
  12. package/src/fixtures/hydrateStorageState.d.ts +36 -0
  13. package/src/fixtures/hydrateStorageState.js +43 -0
  14. package/src/fixtures/irisApp.fixture.d.ts +15 -0
  15. package/src/fixtures/irisApp.fixture.js +13 -0
  16. package/src/fixtures/localDevMode.fixture.d.ts +5 -0
  17. package/src/fixtures/localDevMode.fixture.js +17 -0
  18. package/src/fixtures/login.fixture.d.ts +24 -0
  19. package/src/fixtures/login.fixture.js +95 -0
  20. package/src/fixtures/loginCacheKey.d.ts +25 -0
  21. package/src/fixtures/loginCacheKey.js +29 -0
  22. package/src/fixtures/resolveCredentials.d.ts +14 -0
  23. package/src/fixtures/resolveCredentials.js +49 -0
  24. package/src/generators/playwright-configuration/files/root/playwright/fixtures/auth.json__tmpl__ +4 -0
  25. package/src/generators/playwright-configuration/files/root/playwright/support/fixtures.ts__tmpl__ +1 -0
  26. package/src/generators/playwright-configuration/files/root/playwright/tests/app.spec.ts__tmpl__ +16 -0
  27. package/src/generators/playwright-configuration/files/root/playwright/tsconfig.json__tmpl__ +15 -0
  28. package/src/generators/playwright-configuration/files/root/playwright.config.ts__tmpl__ +13 -0
  29. package/src/generators/playwright-configuration/generator.d.ts +18 -0
  30. package/src/generators/playwright-configuration/generator.js +77 -0
  31. package/src/generators/playwright-configuration/schema.d.ts +3 -0
  32. package/src/generators/playwright-configuration/schema.json +18 -0
  33. package/src/index.d.ts +15 -0
  34. package/src/index.js +14 -0
  35. package/src/plugins/createLogFile.d.ts +24 -0
  36. package/src/plugins/createLogFile.js +51 -0
  37. package/src/plugins/defaultPlaywrightConfig.d.ts +68 -0
  38. package/src/plugins/defaultPlaywrightConfig.js +150 -0
  39. package/src/plugins/logsReporter.d.ts +55 -0
  40. package/src/plugins/logsReporter.js +119 -0
  41. package/src/plugins/nxPreset.d.ts +9 -0
  42. package/src/plugins/nxPreset.js +17 -0
  43. package/src/plugins/redactHar.d.ts +17 -0
  44. package/src/plugins/redactHar.js +138 -0
  45. package/src/plugins/redactSensitive.d.ts +31 -0
  46. package/src/plugins/redactSensitive.js +72 -0
  47. package/src/plugins/redactTrace.d.ts +12 -0
  48. package/src/plugins/redactTrace.js +48 -0
  49. package/src/plugins/setupPlugins.d.ts +11 -0
  50. package/src/plugins/setupPlugins.js +68 -0
  51. package/src/plugins/writeFileWithPrettier.d.ts +17 -0
  52. package/src/plugins/writeFileWithPrettier.js +37 -0
  53. package/src/support.d.ts +2 -0
  54. package/src/support.js +6 -0
  55. package/src/utils/Codeowner.d.ts +26 -0
  56. package/src/utils/Codeowner.js +89 -0
  57. package/src/utils/fileNameBuilder.d.ts +18 -0
  58. package/src/utils/fileNameBuilder.js +32 -0
  59. package/src/utils/fileUpdater.d.ts +16 -0
  60. package/src/utils/fileUpdater.js +38 -0
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # @trackunit/iris-app-playwright
2
+
3
+ A Playwright-based E2E testing utilities library for Trackunit's Iris platform — the Playwright equivalent of `@trackunit/iris-app-e2e`. This package provides reusable fixtures, setup utilities, and configuration helpers that mirror the Cypress support library's API, enabling teams to adopt Playwright while preserving familiar patterns.
4
+
5
+ This library is exposed publicly for use in the Trackunit [Iris App SDK](https://www.npmjs.com/package/@trackunit/iris-app).
6
+
7
+ For more info and a full guide on Iris App SDK Development, please visit our [Developer Hub](https://developers.trackunit.com/).
8
+
9
+ > **See also:** [`@trackunit/iris-app-e2e`](../iris-app-e2e/README.md) — the equivalent Cypress library. Both are supported; teams opt in to Playwright on their own schedule.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @trackunit/iris-app-playwright --save-dev
15
+ ```
16
+
17
+ ## Peer Dependencies
18
+
19
+ ```bash
20
+ npm install @playwright/test @nx/playwright --save-dev
21
+ ```
22
+
23
+ ## Quickstart
24
+
25
+ ### 1. Scaffold with the generator
26
+
27
+ ```bash
28
+ nx generate @trackunit/iris-app-playwright:playwright-configuration --project my-app
29
+ ```
30
+
31
+ This creates:
32
+
33
+ - `playwright.config.ts` wired to `createNxPreset` + `defaultPlaywrightConfig` + `setupPlugins`
34
+ - `playwright/tsconfig.json`
35
+ - `playwright/support/fixtures.ts` — re-exports `test`, `expect`, and `describe`
36
+ - `playwright/fixtures/auth.json` — fill in credentials
37
+ - `playwright/tests/app.spec.ts` — sample login spec
38
+ - `e2e-pw` and `e2e-pw-ui` targets in `project.json`
39
+
40
+ ### 2. Fill in credentials
41
+
42
+ ```json
43
+ // playwright/fixtures/auth.json
44
+ {
45
+ "username": "your-test-user@example.com",
46
+ "password": "your-test-password"
47
+ }
48
+ ```
49
+
50
+ ### 3. Write a test
51
+
52
+ ```typescript
53
+ import { describe, expect, test } from "../support/fixtures";
54
+
55
+ describe("My Feature", () => {
56
+ test("user can log in", async ({ page, login, irisApp }) => {
57
+ await login();
58
+
59
+ const app = irisApp();
60
+ await expect(app.getByTestId("app-content")).toBeVisible();
61
+ });
62
+ });
63
+ ```
64
+
65
+ ### 4. Run tests
66
+
67
+ ```bash
68
+ # Against dev environment (headless)
69
+ yarn nx run my-app:e2e-pw --configuration=dev
70
+
71
+ # Interactive UI mode
72
+ yarn nx run my-app:e2e-pw-ui --configuration=local
73
+ ```
74
+
75
+ > **Outside the devcontainer:** Playwright browser binaries are not bundled. Run
76
+ > `yarn playwright install` once after `yarn install` to download them. The
77
+ > repo's devcontainer handles this automatically via `ensure-playwright.sh`.
78
+
79
+ ## Fixtures
80
+
81
+ All fixtures are available on the `test` object exported from `@trackunit/iris-app-playwright/support`.
82
+
83
+ ### `login`
84
+
85
+ Authenticates a user against the Trackunit platform.
86
+
87
+ ```typescript
88
+ login(options?: LoginOptions): Promise<void>
89
+ ```
90
+
91
+ 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.
92
+
93
+ #### Per-worker storage state cache
94
+
95
+ The fixture maintains a worker-scoped, in-memory cache of authenticated `storageState` snapshots, keyed by `(baseURL, username)`. Within a single Playwright worker:
96
+
97
+ 1. The first `login()` call performs the full Okta flow (`POST /api/v1/authn` → `sessionToken` → `/auth/manager-classic` redirect) and captures the resulting cookies + localStorage.
98
+ 2. Every subsequent `login()` call for the same `(baseURL, username)` hydrates those cookies/localStorage onto the current browser context and navigates straight to `/` — no Okta call.
99
+
100
+ This is what lets large suites run safely against shared environments (`dev`, `stage`) without tripping Okta's `E0000047` rate limit, which fires once you fan out past a handful of parallel logins. Workers are independent processes, so each worker pays the slow path exactly once per identity.
101
+
102
+ You don't have to do anything to opt in — the cache is wired up automatically.
103
+
104
+ ### `irisApp`
105
+
106
+ Returns a `FrameLocator` for an Iris app iframe.
107
+
108
+ ```typescript
109
+ irisApp(options?: IrisAppOptions): FrameLocator
110
+ ```
111
+
112
+ Defaults to `iframe[data-testid="app-iframe"]`. Pass `testId` to target a different iframe.
113
+
114
+ ### `storybookPreview`
115
+
116
+ Returns a `FrameLocator` for the Storybook preview iframe.
117
+
118
+ ```typescript
119
+ storybookPreview(options?: StorybookPreviewOptions): FrameLocator
120
+ ```
121
+
122
+ Defaults to `iframe[id="storybook-preview-iframe"]`. Pass `iframeId` to target a different preview iframe.
123
+
124
+ ### `getValidateFeatureFlags`
125
+
126
+ Installs a response interceptor that captures the `ValidateFeatureFlags` GraphQL response for the current page.
127
+
128
+ ```typescript
129
+ getValidateFeatureFlags(): void
130
+ ```
131
+
132
+ Call this before navigating to ensure the response is captured from the first load.
133
+
134
+ ### `configCat`
135
+
136
+ Returns the boolean state of a ConfigCat feature flag.
137
+
138
+ ```typescript
139
+ configCat(flag: string): Promise<boolean>
140
+ ```
141
+
142
+ Waits for the `ValidateFeatureFlags` response if it hasn't been captured yet.
143
+
144
+ ### `switchToLocalDevMode`
145
+
146
+ Navigates to the Iris SDK portal and enables the local dev mode toggle.
147
+
148
+ ```typescript
149
+ switchToLocalDevMode(): Promise<void>
150
+ ```
151
+
152
+ ## HAR recording and logs
153
+
154
+ Failed tests automatically:
155
+
156
+ - Write a redacted JSON log file to `<outputDir>/logs/<testTitle>_failed_<retry>.log`
157
+ - Retain a HAR file at `<outputDir>/hars/<testTitle>_failed_<retry>.har`
158
+
159
+ Passing tests have their HAR file deleted. The log file is never written for passing tests.
160
+
161
+ Both the logs and HAR directories derive from the resolved `outputDir`, so every artifact for a run stays inside the one Playwright output tree (under the per-executor partition on CI).
162
+
163
+ Sensitive values (`password`, `sessionToken`, `accessToken`, `refreshToken`, `idToken`, `authorization`) are automatically redacted from log output with `***`.
164
+
165
+ ## Tuning parallelism
166
+
167
+ `defaultPlaywrightConfig` accepts a `behaviorConfig` object with two parallelism knobs:
168
+
169
+ ```typescript
170
+ defaultPlaywrightConfig({
171
+ configDir: __dirname,
172
+ behaviorConfig: {
173
+ workers: 2, // number or "50%" (Playwright shorthand)
174
+ fullyParallel: false,
175
+ },
176
+ });
177
+ ```
178
+
179
+ ### `workers`
180
+
181
+ Resolution order:
182
+
183
+ 1. If `behaviorConfig.workers` is set, it always wins.
184
+ 2. Otherwise, when `BASE_URL` (or `NX_FEATURE_BRANCH_BASE_URL`) points at a shared environment — i.e. anything other than `localhost`, `127.0.0.1`, `0.0.0.0`, `::1`, or a `*.local` / `*.localhost` host — `workers` is automatically capped at `2`. This pairs with the `login` fixture's per-worker `storageState` cache: the cache collapses repeat logins within a worker, the cap limits the initial parallel login fan-out at suite startup, and together they keep Okta's `/api/v1/authn` comfortably under the `E0000047` rate limit.
185
+ 3. Otherwise (`BASE_URL` unset or local), Playwright's own default applies — typically `Math.ceil(os.cpus().length / 2)` locally and `1` on CI.
186
+
187
+ Pass an explicit `workers` value to override the auto-cap, e.g. when running against a non-Okta environment.
188
+
189
+ ### `fullyParallel`
190
+
191
+ Defaults to Playwright's behaviour (`undefined`, project-level setting wins). Set explicitly to opt files in or out of intra-file parallelism.
192
+
193
+ ## Codeowner suffix
194
+
195
+ Set the `codeowner` environment variable to append a `[team-name]` suffix to all describe titles in the test report:
196
+
197
+ ```bash
198
+ codeowner=team-gluon-fe yarn nx run my-app:e2e-pw --configuration=dev
199
+ ```
200
+
201
+ Use the `describe` wrapper exported from `@trackunit/iris-app-playwright/support` (not Playwright's built-in `test.describe`) to get this behaviour.
202
+
203
+ ## Cypress to Playwright parity table
204
+
205
+ | Cypress API | Playwright equivalent |
206
+ |---|---|
207
+ | `cy.login("auth")` | `await login()` |
208
+ | `cy.enterIrisApp()` | `irisApp()` returns `FrameLocator` |
209
+ | `cy.enterStorybookPreview()` | `storybookPreview()` returns `FrameLocator` |
210
+ | `cy.getValidateFeatureFlags()` | `getValidateFeatureFlags()` |
211
+ | `cy.configCat("flag")` | `await configCat("flag")` |
212
+ | `cy.switchToLocalDevMode()` | `await switchToLocalDevMode()` |
213
+ | `cy.getByTestId("id")` | `page.getByTestId("id")` (built-in Playwright) |
214
+ | `setupE2E()` | Handled by `setupPlugins` in `playwright.config.ts` |
215
+ | `setupHarRecording()` | Built in to `LogsReporter` via `setupPlugins` |
216
+ | `describe("...", fn)` | `describe("...", fn)` from `@trackunit/iris-app-playwright/support` |
217
+ | `defaultCypressConfig()` | `defaultPlaywrightConfig()` |
218
+ | `setupPlugins(on, config, ...)` | `setupPlugins(config, formatter)` |
219
+ | `createNxPreset(__filename)` | `createNxPreset(__filename)` |
220
+
221
+ ## Development
222
+
223
+ This library is developed by Trackunit employees. See [INTERNAL.md](./INTERNAL.md) for maintainer notes.
224
+
225
+ ## Trackunit
226
+
227
+ This package was developed by Trackunit ApS.
228
+ Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.
229
+
230
+ ![The Trackunit logo](https://trackunit.com/wp-content/uploads/2022/03/top-logo.svg)
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "name": "iris-app-playwright",
4
+ "version": "1.0.0",
5
+ "generators": {
6
+ "playwright-configuration": {
7
+ "factory": "./src/generators/playwright-configuration/generator#playwrightConfigurationGenerator",
8
+ "schema": "./src/generators/playwright-configuration/schema.json",
9
+ "description": "Playwright E2E configuration setup for Iris app projects"
10
+ }
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@trackunit/iris-app-playwright",
3
+ "version": "0.1.1",
4
+ "repository": "https://github.com/Trackunit/manager",
5
+ "license": "SEE LICENSE IN LICENSE.txt",
6
+ "generators": "./generators.json",
7
+ "engines": {
8
+ "node": ">=24.x"
9
+ },
10
+ "main": "./src/index.js",
11
+ "types": "./src/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.d.ts",
15
+ "require": "./src/index.js",
16
+ "import": "./src/index.js"
17
+ },
18
+ "./support": {
19
+ "types": "./src/support.d.ts",
20
+ "require": "./src/support.js",
21
+ "import": "./src/support.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "typesVersions": {
26
+ "*": {
27
+ "support": [
28
+ "./src/support.d.ts"
29
+ ]
30
+ }
31
+ },
32
+ "dependencies": {
33
+ "@nx/devkit": "22.6.5",
34
+ "@nx/playwright": "22.6.5",
35
+ "jszip": "^3.10.1",
36
+ "prettier": "^3.4.2",
37
+ "tslib": "^2.6.2"
38
+ },
39
+ "peerDependencies": {
40
+ "@playwright/test": "^1.60.0"
41
+ },
42
+ "type": "commonjs"
43
+ }
@@ -0,0 +1,9 @@
1
+ import { expect } from "@playwright/test";
2
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
3
+ _harRecording: void;
4
+ } & import("./login.fixture").TrackunitBaseFixtures & import("./irisApp.fixture").IrisAppFixtures & import("./featureFlags.fixture").FeatureFlagFixtures & import("./localDevMode.fixture").LocalDevModeFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & import("./login.fixture").LoginCacheFixtures>;
5
+ export { expect };
6
+ export type { FeatureFlag, FeatureFlagFixtures } from "./featureFlags.fixture";
7
+ export type { IrisAppFixtures, IrisAppOptions, StorybookPreviewOptions } from "./irisApp.fixture";
8
+ export type { LocalDevModeFixtures } from "./localDevMode.fixture";
9
+ export type { AuthCredentials, LoginOptions, TrackunitBaseFixtures } from "./login.fixture";
@@ -0,0 +1,12 @@
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
+ Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
6
+ const featureFlags_fixture_1 = require("./featureFlags.fixture");
7
+ const harRecording_fixture_1 = require("./harRecording.fixture");
8
+ const irisApp_fixture_1 = require("./irisApp.fixture");
9
+ const localDevMode_fixture_1 = require("./localDevMode.fixture");
10
+ const login_fixture_1 = require("./login.fixture");
11
+ exports.test = (0, test_1.mergeTests)(harRecording_fixture_1.test, login_fixture_1.test, irisApp_fixture_1.test, featureFlags_fixture_1.test, localDevMode_fixture_1.test);
12
+ //# sourceMappingURL=baseTest.js.map
@@ -0,0 +1,11 @@
1
+ type DescribeCallback = () => void;
2
+ type DescribeFn = (title: string, callback: DescribeCallback) => void;
3
+ /**
4
+ * Wrapped `describe` that appends `[codeowner]` to the suite title when the
5
+ * `codeowner` environment variable is set. Drop-in replacement for `test.describe`.
6
+ */
7
+ export declare const describe: DescribeFn & {
8
+ only: DescribeFn;
9
+ skip: DescribeFn;
10
+ };
11
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describe = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const addOwnerToTitle = (title) => {
6
+ const codeowner = process.env.codeowner;
7
+ return codeowner ? `${title} [${codeowner}]` : title;
8
+ };
9
+ /**
10
+ * Wrapped `describe` that appends `[codeowner]` to the suite title when the
11
+ * `codeowner` environment variable is set. Drop-in replacement for `test.describe`.
12
+ */
13
+ exports.describe = Object.assign((title, callback) => {
14
+ test_1.test.describe(addOwnerToTitle(title), callback);
15
+ }, {
16
+ only: (title, callback) => {
17
+ test_1.test.describe.only(addOwnerToTitle(title), callback);
18
+ },
19
+ skip: (title, callback) => {
20
+ test_1.test.describe.skip(addOwnerToTitle(title), callback);
21
+ },
22
+ });
23
+ //# sourceMappingURL=describe.js.map
@@ -0,0 +1,19 @@
1
+ export interface FeatureFlag {
2
+ key: string;
3
+ state: boolean;
4
+ }
5
+ export interface FeatureFlagFixtures {
6
+ /**
7
+ * Call before the navigation that triggers `ValidateFeatureFlags`, `UserPermissions`,
8
+ * and `ActiveSubscription` requests to ensure the responses are captured for `configCat`.
9
+ */
10
+ getValidateFeatureFlags: () => void;
11
+ /**
12
+ * Returns the state of a ConfigCat feature flag from the captured `ValidateFeatureFlags` response.
13
+ * Waits for the response if not yet captured.
14
+ *
15
+ * @param flag - The feature flag key to look up
16
+ */
17
+ configCat: (flag: string) => Promise<boolean>;
18
+ }
19
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & FeatureFlagFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.test = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const featureFlagsCache = new WeakMap();
6
+ const featureFlagsInFlight = new WeakMap();
7
+ const listenerInstalled = new WeakSet();
8
+ const extractFlags = async (response) => {
9
+ const body = await response.json();
10
+ const flags = body?.data?.featureFlags;
11
+ return Array.isArray(flags) ? flags : undefined;
12
+ };
13
+ // Tracks the in-flight extraction promise per page so `configCat` can await it
14
+ // instead of falling through to `page.waitForResponse(...)`, which would wait
15
+ // for the *next* matching response and hang when the original already happened.
16
+ const trackExtraction = (page, response) => {
17
+ const promise = extractFlags(response)
18
+ .then(flags => {
19
+ if (flags !== undefined) {
20
+ featureFlagsCache.set(page, flags);
21
+ }
22
+ return flags;
23
+ })
24
+ .finally(() => {
25
+ if (featureFlagsInFlight.get(page) === promise) {
26
+ featureFlagsInFlight.delete(page);
27
+ }
28
+ });
29
+ featureFlagsInFlight.set(page, promise);
30
+ return promise;
31
+ };
32
+ const installFlagListener = (page) => {
33
+ if (listenerInstalled.has(page)) {
34
+ return;
35
+ }
36
+ listenerInstalled.add(page);
37
+ page.on("response", response => {
38
+ if (response.url().includes("ValidateFeatureFlags") && response.ok()) {
39
+ void trackExtraction(page, response);
40
+ }
41
+ });
42
+ };
43
+ exports.test = test_1.test.extend({
44
+ getValidateFeatureFlags: async ({ page }, provide) => {
45
+ await provide(() => {
46
+ installFlagListener(page);
47
+ });
48
+ },
49
+ configCat: async ({ page }, provide) => {
50
+ await provide(async (flag) => {
51
+ if (!featureFlagsCache.has(page)) {
52
+ installFlagListener(page);
53
+ // If the listener already saw the response, an extraction is in flight —
54
+ // await it before deciding to wait for another network response.
55
+ const inFlight = featureFlagsInFlight.get(page);
56
+ if (inFlight !== undefined) {
57
+ await inFlight;
58
+ }
59
+ if (!featureFlagsCache.has(page)) {
60
+ const response = await page.waitForResponse(r => r.url().includes("ValidateFeatureFlags") && r.ok(), { timeout: 30000 });
61
+ const parsedFlags = await extractFlags(response);
62
+ if (parsedFlags !== undefined) {
63
+ featureFlagsCache.set(page, parsedFlags);
64
+ }
65
+ }
66
+ }
67
+ const cachedFlags = featureFlagsCache.get(page);
68
+ const found = cachedFlags?.find(ff => ff.key === flag);
69
+ if (found === undefined) {
70
+ throw new Error(`Feature flag '${flag}' not found in ValidateFeatureFlags response`);
71
+ }
72
+ return found.state;
73
+ });
74
+ },
75
+ });
76
+ //# sourceMappingURL=featureFlags.fixture.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Adds Playwright HAR recording without replacing the built-in `context` fixture.
3
+ *
4
+ * `contextOptions` is merged into the default `browser.newContext()` call by Playwright's
5
+ * own `context` fixture, so trace/video/screenshot retention configured via `use` still works.
6
+ * `_harRecording` is an auto-fixture that depends on `context` so its teardown runs after the
7
+ * context closes — that's when Playwright flushes the HAR file to disk, which is when we
8
+ * can attach it as `har` for `LogsReporter` to retain (on failure) or delete (on pass).
9
+ */
10
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
11
+ _harRecording: void;
12
+ }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.test = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const fs_1 = require("fs");
6
+ const HAR_FILENAME = "test.har";
7
+ /**
8
+ * Adds Playwright HAR recording without replacing the built-in `context` fixture.
9
+ *
10
+ * `contextOptions` is merged into the default `browser.newContext()` call by Playwright's
11
+ * own `context` fixture, so trace/video/screenshot retention configured via `use` still works.
12
+ * `_harRecording` is an auto-fixture that depends on `context` so its teardown runs after the
13
+ * context closes — that's when Playwright flushes the HAR file to disk, which is when we
14
+ * can attach it as `har` for `LogsReporter` to retain (on failure) or delete (on pass).
15
+ */
16
+ exports.test = test_1.test.extend({
17
+ contextOptions: async ({ contextOptions }, provide, testInfo) => {
18
+ const harPath = testInfo.outputPath(HAR_FILENAME);
19
+ await provide({
20
+ ...contextOptions,
21
+ recordHar: { path: harPath, mode: "minimal" },
22
+ });
23
+ },
24
+ _harRecording: [
25
+ async ({ context: _context }, provide, testInfo) => {
26
+ await provide();
27
+ const harPath = testInfo.outputPath(HAR_FILENAME);
28
+ if ((0, fs_1.existsSync)(harPath)) {
29
+ await testInfo.attach("har", { path: harPath });
30
+ }
31
+ },
32
+ { auto: true },
33
+ ],
34
+ });
35
+ //# sourceMappingURL=harRecording.fixture.js.map
@@ -0,0 +1,36 @@
1
+ import type { BrowserContext } from "@playwright/test";
2
+ /**
3
+ * Subset of Playwright's `StorageState` we persist between tests within the
4
+ * same worker. We model it explicitly rather than re-exporting
5
+ * `Awaited<ReturnType<BrowserContext['storageState']>>` so the cache
6
+ * contract is greppable from spec files and Playwright API churn cannot
7
+ * silently widen what we're persisting.
8
+ */
9
+ export interface CachedStorageState {
10
+ cookies: Parameters<BrowserContext["addCookies"]>[0];
11
+ origins: Array<{
12
+ origin: string;
13
+ localStorage: Array<{
14
+ name: string;
15
+ value: string;
16
+ }>;
17
+ }>;
18
+ }
19
+ export type StorageStateHydrator = Pick<BrowserContext, "addCookies" | "addInitScript">;
20
+ /**
21
+ * Restores cookies and localStorage from a cached `storageState` snapshot
22
+ * into the supplied browser context.
23
+ *
24
+ * Cookies go through `context.addCookies` (synchronous from the page's POV
25
+ * because they're set before any navigation). LocalStorage requires an
26
+ * `addInitScript` because it can only be written by JS executing on the
27
+ * matching origin — Playwright runs the init script in every new document,
28
+ * and we gate it by `window.location.origin` so we don't smuggle localStorage
29
+ * onto unrelated origins (e.g. about:blank, OAuth IdP domains, or iframes
30
+ * pointing at iris-app subdomains).
31
+ *
32
+ * Wrapping each `setItem` in try/catch protects against contexts where
33
+ * localStorage is unavailable (private browsing modes, sandboxed iframes,
34
+ * etc.) — we never want hydration to crash the page load.
35
+ */
36
+ export declare const hydrateStorageState: (context: StorageStateHydrator, state: CachedStorageState) => Promise<void>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hydrateStorageState = void 0;
4
+ /**
5
+ * Restores cookies and localStorage from a cached `storageState` snapshot
6
+ * into the supplied browser context.
7
+ *
8
+ * Cookies go through `context.addCookies` (synchronous from the page's POV
9
+ * because they're set before any navigation). LocalStorage requires an
10
+ * `addInitScript` because it can only be written by JS executing on the
11
+ * matching origin — Playwright runs the init script in every new document,
12
+ * and we gate it by `window.location.origin` so we don't smuggle localStorage
13
+ * onto unrelated origins (e.g. about:blank, OAuth IdP domains, or iframes
14
+ * pointing at iris-app subdomains).
15
+ *
16
+ * Wrapping each `setItem` in try/catch protects against contexts where
17
+ * localStorage is unavailable (private browsing modes, sandboxed iframes,
18
+ * etc.) — we never want hydration to crash the page load.
19
+ */
20
+ const hydrateStorageState = async (context, state) => {
21
+ if (state.cookies.length > 0) {
22
+ await context.addCookies(state.cookies);
23
+ }
24
+ if (state.origins.length === 0) {
25
+ return;
26
+ }
27
+ await context.addInitScript((origins) => {
28
+ const match = origins.find(entry => entry.origin === window.location.origin);
29
+ if (match === undefined) {
30
+ return;
31
+ }
32
+ for (const { name, value } of match.localStorage) {
33
+ try {
34
+ window.localStorage.setItem(name, value);
35
+ }
36
+ catch {
37
+ /* localStorage may be unavailable (sandboxed iframe, private mode) — skip silently. */
38
+ }
39
+ }
40
+ }, state.origins);
41
+ };
42
+ exports.hydrateStorageState = hydrateStorageState;
43
+ //# sourceMappingURL=hydrateStorageState.js.map
@@ -0,0 +1,15 @@
1
+ import { type FrameLocator } from "@playwright/test";
2
+ export interface IrisAppOptions {
3
+ testId?: string;
4
+ }
5
+ export interface StorybookPreviewOptions {
6
+ /** Storybook preview iframe element id. Defaults to `storybook-preview-iframe`. */
7
+ iframeId?: string;
8
+ }
9
+ export interface IrisAppFixtures {
10
+ /** Returns a FrameLocator for the iris app iframe. Defaults to `data-testid="app-iframe"`. */
11
+ irisApp: (options?: IrisAppOptions) => FrameLocator;
12
+ /** Returns a FrameLocator for the Storybook preview iframe. Defaults to `id="storybook-preview-iframe"`. */
13
+ storybookPreview: (options?: StorybookPreviewOptions) => FrameLocator;
14
+ }
15
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & IrisAppFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.test = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ exports.test = test_1.test.extend({
6
+ irisApp: async ({ page }, provide) => {
7
+ await provide((options) => page.frameLocator(`iframe[data-testid="${options?.testId ?? "app-iframe"}"]`));
8
+ },
9
+ storybookPreview: async ({ page }, provide) => {
10
+ await provide((options) => page.frameLocator(`iframe[id="${options?.iframeId ?? "storybook-preview-iframe"}"]`));
11
+ },
12
+ });
13
+ //# sourceMappingURL=irisApp.fixture.js.map
@@ -0,0 +1,5 @@
1
+ export interface LocalDevModeFixtures {
2
+ /** Navigates to the developer portal and toggles local dev mode on if not already enabled. */
3
+ switchToLocalDevMode: () => Promise<void>;
4
+ }
5
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & LocalDevModeFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.test = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ exports.test = test_1.test.extend({
6
+ switchToLocalDevMode: async ({ page }, provide) => {
7
+ await provide(async () => {
8
+ await page.getByTestId("developerPortalNav").click();
9
+ await page.waitForURL(url => url.toString().includes("/iris-sdk-portal"), { timeout: 15000 });
10
+ const toggle = page.getByTestId("localDevModeSwitch-input");
11
+ if (!(await toggle.isChecked())) {
12
+ await page.getByTestId("localDevModeSwitch-thumb").click();
13
+ }
14
+ });
15
+ },
16
+ });
17
+ //# sourceMappingURL=localDevMode.fixture.js.map
@@ -0,0 +1,24 @@
1
+ import { type CachedStorageState } from "./hydrateStorageState";
2
+ import { type AuthCredentials, type ResolveCredentialsOptions } from "./resolveCredentials";
3
+ export type { AuthCredentials };
4
+ export type LoginOptions = ResolveCredentialsOptions;
5
+ export interface TrackunitBaseFixtures {
6
+ login: (options?: LoginOptions) => Promise<void>;
7
+ }
8
+ /**
9
+ * Worker-scoped, in-memory cache of authenticated `storageState` snapshots,
10
+ * keyed by `(baseURL, username)`. Implementation detail of the `login`
11
+ * fixture — exposed on the fixtures object so Playwright can manage its
12
+ * lifecycle, but prefixed with `_` to signal "don't touch from tests".
13
+ */
14
+ export interface LoginCacheFixtures {
15
+ _loginCache: Map<string, CachedStorageState>;
16
+ }
17
+ /**
18
+ * Each worker is its own Node process, so a `Map` is sufficient — no
19
+ * cross-worker sharing concerns, no disk I/O, no cleanup needed. The cache
20
+ * dies with the worker, which is the desired behaviour: a fresh `nx run`
21
+ * always starts with empty caches.
22
+ */
23
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & TrackunitBaseFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & LoginCacheFixtures>;
24
+ export { expect } from "@playwright/test";