# @trackunit/iris-app-playwright

A Playwright-based E2E testing utilities library for Trackunit's Iris platform. This package provides reusable fixtures, setup utilities, and configuration helpers, enabling teams to write end-to-end tests with Playwright.

This library is exposed publicly for use in the Trackunit [Iris App SDK](https://www.npmjs.com/package/@trackunit/iris-app).

For more info and a full guide on Iris App SDK Development, please visit our [Developer Hub](https://developers.trackunit.com/).

## Installation

```bash
npm install @trackunit/iris-app-playwright --save-dev
```

## Peer Dependencies

```bash
npm install @playwright/test @nx/playwright --save-dev
```

## Quickstart

### 1. Scaffold with the generator

```bash
nx generate @trackunit/iris-app-playwright:playwright-configuration --project my-app
```

This creates:

- `playwright.config.ts` wired to `createNxPreset` + `defaultPlaywrightConfig` + `setupPlugins`
- `playwright/tsconfig.json`
- `playwright/support/fixtures.ts` — re-exports `test`, `expect`, and `describe`
- `playwright/fixtures/auth.json` — name-only account fixture (fill in the username)
- `playwright/tests/app.spec.ts` — sample login spec
- `e2e` and `e2e-ui` targets in `project.json`

### 2. Fill in the account fixture

`auth.json` is **name-only** — no password is committed. Put the E2E account's
identifier in it:

```json
// playwright/fixtures/auth.json
{
  "username": "your-test-user@example.com"
}
```

At `login()` time the password is resolved by account name — the
`E2E_PW_<SLUG>` environment variable first (set by the CircleCI context in CI),
otherwise from Vault on a developer machine (needs `vault login` on the VPN,
`E2E_VAULT_ENDPOINT` / `E2E_VAULT_PATH`, and the optional `node-vault` peer).
A full `{ "username", "password" }` fixture, or explicit `login({ credentials })`,
are also honoured.

### 3. Write a test

```typescript
import { describe, expect, test } from "../support/fixtures";

describe("My Feature", () => {
  test("user can log in", async ({ page, login, irisApp }) => {
    await login();

    const app = irisApp();
    await expect(app.getByTestId("app-content")).toBeVisible();
  });
});
```

### 4. Run tests

```bash
# Against dev environment (headless)
yarn nx run my-app:e2e --configuration=dev

# Interactive UI mode
yarn nx run my-app:e2e-ui --configuration=local
```

> **Outside the devcontainer:** Playwright browser binaries are not bundled. Run
> `yarn playwright install` once after `yarn install` to download them. The
> repo's devcontainer handles this automatically via `ensure-playwright.sh`.

## Fixtures

All fixtures are available on the `test` object exported from `@trackunit/iris-app-playwright/support`.

### `login`

Authenticates a user against the Trackunit platform.

```typescript
login(options?: LoginOptions): Promise<void>
```

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.

#### Per-worker storage state cache

The fixture maintains a worker-scoped, in-memory cache of authenticated `storageState` snapshots, keyed by `(baseURL, username)`. Within a single Playwright worker:

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.
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.

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.

You don't have to do anything to opt in — the cache is wired up automatically.

### `irisApp`

Returns a `FrameLocator` for an Iris app iframe.

```typescript
irisApp(options?: IrisAppOptions): FrameLocator
```

Defaults to `iframe[data-testid="app-iframe"]`. Pass `testId` to target a different iframe.

### `storybookPreview`

Returns a `FrameLocator` for the Storybook preview iframe.

```typescript
storybookPreview(options?: StorybookPreviewOptions): FrameLocator
```

Defaults to `iframe[id="storybook-preview-iframe"]`. Pass `iframeId` to target a different preview iframe.

### `getValidateFeatureFlags`

Installs a response interceptor that captures the `ValidateFeatureFlags` GraphQL response for the current page.

```typescript
getValidateFeatureFlags(): void
```

Call this before navigating to ensure the response is captured from the first load.

### `configCat`

Returns the boolean state of a ConfigCat feature flag.

```typescript
configCat(flag: string): Promise<boolean>
```

Waits for the `ValidateFeatureFlags` response if it hasn't been captured yet.

### `switchToLocalDevMode`

Navigates to the Iris SDK portal and enables the local dev mode toggle.

```typescript
switchToLocalDevMode(): Promise<void>
```

## HAR recording and logs

Failed tests automatically:

- Write a redacted JSON log file to `<outputDir>/logs/<testTitle>_failed_<retry>.log`
- Retain a HAR file at `<outputDir>/hars/<testTitle>_failed_<retry>.har`

Passing tests have their HAR file deleted. The log file is never written for passing tests.

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).

Sensitive values (`password`, `sessionToken`, `accessToken`, `refreshToken`, `idToken`, `authorization`) are automatically redacted from log output with `***`.

## Tuning parallelism

`defaultPlaywrightConfig` accepts a `behaviorConfig` object with two parallelism knobs:

```typescript
defaultPlaywrightConfig({
  configDir: __dirname,
  behaviorConfig: {
    workers: 2,          // number or "50%" (Playwright shorthand)
    fullyParallel: false,
  },
});
```

### `workers`

Resolution order:

1. If `behaviorConfig.workers` is set, it always wins.
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.
3. Otherwise (`BASE_URL` unset or local), Playwright's own default applies — typically `Math.ceil(os.cpus().length / 2)` locally and `1` on CI.

Pass an explicit `workers` value to override the auto-cap, e.g. when running against a non-Okta environment.

### `fullyParallel`

Defaults to Playwright's behaviour (`undefined`, project-level setting wins). Set explicitly to opt files in or out of intra-file parallelism.

## Codeowner suffix

Set the `codeowner` environment variable to append a `[team-name]` suffix to all describe titles in the test report:

```bash
codeowner=team-gluon-fe yarn nx run my-app:e2e --configuration=dev
```

Use the `describe` wrapper exported from `@trackunit/iris-app-playwright/support` (not Playwright's built-in `test.describe`) to get this behaviour.

## Cypress to Playwright parity table

| Cypress API | Playwright equivalent |
|---|---|
| `cy.login("auth")` | `await login()` |
| `cy.enterIrisApp()` | `irisApp()` returns `FrameLocator` |
| `cy.enterStorybookPreview()` | `storybookPreview()` returns `FrameLocator` |
| `cy.getValidateFeatureFlags()` | `getValidateFeatureFlags()` |
| `cy.configCat("flag")` | `await configCat("flag")` |
| `cy.switchToLocalDevMode()` | `await switchToLocalDevMode()` |
| `cy.getByTestId("id")` | `page.getByTestId("id")` (built-in Playwright) |
| `setupE2E()` | Handled by `setupPlugins` in `playwright.config.ts` |
| `setupHarRecording()` | Built in to `LogsReporter` via `setupPlugins` |
| `describe("...", fn)` | `describe("...", fn)` from `@trackunit/iris-app-playwright/support` |
| `defaultCypressConfig()` | `defaultPlaywrightConfig()` |
| `setupPlugins(on, config, ...)` | `setupPlugins(config, formatter)` |
| `createNxPreset(__filename)` | `createNxPreset(__filename)` |

## Development

This library is developed by Trackunit employees. See [INTERNAL.md](./INTERNAL.md) for maintainer notes.

## Trackunit

This package was developed by Trackunit ApS.
Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.

![The Trackunit logo](https://trackunit.com/wp-content/uploads/2022/03/top-logo.svg)
