# ERP-Core E2E tests (Playwright) End-to-end tests for the three phases requested first: **GRN (receiving)**, **Production runs**, and **Stock movement** (transfers + adjustments), plus one chained scenario that walks all three in sequence. Sales and Accounts are intentionally out of scope for now. ## Why Playwright, not Selenium The frontend is Next.js 16 / React 19. Playwright auto-waits for React state updates, ships trace/video capture on failure, and can drive the backend API directly (used here to seed test data), which made it a better fit than Selenium for this stack. ## Prerequisites 1. Backend running locally: `cd Backend/ERPCore && dotnet ef database update && dotnet run` (needs `ASPNETCORE_ENVIRONMENT=Development` set — see the repo's local-env notes — and a reachable Postgres instance). Defaults to `http://localhost:5224`. 2. Frontend running locally: `cd Frontend/erp-system && npm install && npm run dev`. Defaults to `http://localhost:3000` and proxies `/api/v1/*` to the backend same-origin. 3. A real login for the tests. **Auth is fronted by an external AuthHex identity provider** (`Backend/ERPCore/Controllers/AuthController.cs`) — there is no local seed for a user account, so `E2E_ADMIN_EMAIL`/`E2E_ADMIN_PASSWORD` must be a real, already-provisioned account with access to Receiving, Production, and Stock. 4. A fresh-ish database is fine: `DataSeeder` (`Backend/ERPCore/Infra/Persistence/DataSeeder.cs`) seeds the `MAIN`/`SHOP` warehouses, `PCS`/`BOX` UOMs, and a `General Goods` category that these tests rely on existing. Everything else (vendors, items, purchase orders, a production template) is created fresh per run by `support/api.ts` with unique timestamp-suffixed codes, so reruns never collide with previous data. 5. **`E2E_BASE_URL` must use `http://localhost`, not `127.0.0.1` or a LAN IP.** The session cookie is written with `Secure = true` unconditionally (`Backend/ERPCore/Infra/Auth/AuthCookieWriter.cs`); Chromium only treats plain-HTTP `localhost` as a secure-enough origin to accept and resend a `Secure` cookie, so anything else silently drops the session and every post-login request 401s. ## Setup ```bash cd Testing/e2e npm install npx playwright install --with-deps chromium cp .env.e2e.example .env.e2e # then fill in E2E_ADMIN_EMAIL / E2E_ADMIN_PASSWORD ``` ## Running ```bash npm run test:e2e # headless, all specs npm run test:e2e:ui # interactive UI mode — best for first-run locator debugging npm run test:e2e:headed # headed browser npm run report # open the last HTML report ``` The `setup` project (`specs/global.setup.ts`) logs in once through the real `/login` form — the session is an httpOnly cookie, so there's no token to inject — and saves it to `.auth/admin.json`. Every other spec's `chromium` project reuses that storage state, so individual specs don't re-authenticate. `auth.spec.ts` is the exception: it explicitly runs with no stored session so it can exercise the login form itself. ## Layout ``` Testing/e2e/ ├── playwright.config.ts ├── support/ │ ├── env.ts # reads .env.e2e, resolves the storageState path │ └── api.ts # ApiSeeder — creates vendors/items/POs/templates, reads stock on-hand ├── pages/ # Page Object Models (one file per module) └── specs/ ├── global.setup.ts ├── auth.spec.ts ├── grn.spec.ts ├── production.spec.ts ├── stock-transfers.spec.ts ├── stock-adjustments.spec.ts └── chained-flow.spec.ts # GRN -> Production -> Stock Transfer, one continuous scenario ``` ## Coverage vs. what's deferred 18 tests across auth, GRN, production runs, stock transfers/adjustments, and one chained flow. Deliberately deferred (all would need a second, multi-stage production template or custom-field scaffolding to exercise, which felt like scope creep for a first pass): - **Approve & transfer** on a non-terminal stage, and transferring a held-back remainder from an `Approved` stage — both only apply to a multi-stage graph; the seeded template is single-stage (entry == terminal) so every run here only ever exercises "Approve & receive". - **Reject intake** (pulling back delivered upstream WIP) — same reason, needs a parent→child edge. - Client-side validation edges inside `StageDrawer`: completing a stage with produced qty over the staged input, or a scrap qty with no scrap reason selected. - Adjustment reason-code → ledger-entry tagging spot-check (`GET /stock/ledger`) — the positive/negative adjustment tests verify on-hand moves correctly but don't inspect the ledger rows themselves. **Long-run item-dropdown ceiling.** The GRN/Transfer/Adjustment "new" pages load items via `itemsApi.list({ pageSize: 200 })` (a fixed page, not paginated further in the UI). Every spec run mints 2-3 new permanent items through `ApiSeeder.createItem`, and nothing deletes them. Once a dev database accumulates more than 200 active items, freshly-seeded items stop appearing in the Item combobox (and if the list sorts ascending by id, it's exactly the newest ones that fall off) — locators like `getByRole("option", { name: item.name })` will time out with no visible cause. If that starts happening, the fix is to seed one stable per-module item once and reuse it across runs instead of minting a fresh one each time (every assertion here is already delta-based, so that's a drop-in change). ## Known limitation: a real, reproducible hydration bug Every load of the GRN/Production/Stock pages throws a genuine React hydration error ("Minified React error #418" — text content mismatch between server and client render). It is **not intermittent** — it fires on every navigation — but its effect is: hydration recovery blanks the placeholder text of a random subset of that page's Select triggers for the rest of that page's life, while leaving the sibling `