mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 07:08:18 +00:00
Muluhabt ERP modules
This commit is contained in:
156
e2e-hr-finance/playwright.config.ts
Normal file
156
e2e-hr-finance/playwright.config.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { PERSONAS } from "./fixtures/personas";
|
||||
|
||||
/**
|
||||
* Playwright end-to-end suite for the HR and Finance modules.
|
||||
*
|
||||
* Scope and doctrine are set by docs/hr-finance-ui-e2e-matrix.md (Phase 0,
|
||||
* approved 2026-08-24): every scenario asserts the DOM, the network response
|
||||
* AND the database row, and fails if the three disagree.
|
||||
*
|
||||
* ── Isolation ────────────────────────────────────────────────────────────────
|
||||
* Runs against `smart_office_e2e` on its OWN ports, so a run can never disturb
|
||||
* the dev stack a human is using, nor write into `smart_office_prod` (a
|
||||
* production replica). Both are load-bearing:
|
||||
*
|
||||
* | service | dev | e2e |
|
||||
* | hr-api | 3005 | 3105 |
|
||||
* | finance-api | 3004 | 3104 |
|
||||
* | hr-web | 5185 | 5285 |
|
||||
* | finance-web | 5186 | 5286 |
|
||||
*
|
||||
* `DB_NAME`, `PORT` and `CORS_ORIGINS` all override the app's own `.env`
|
||||
* (verified), and Vite reads `PORT`/`VITE_*` from the process environment — so
|
||||
* no config file is edited to run the suite.
|
||||
*
|
||||
* CORS_ORIGINS is not optional: without it the API rejects the browser at the
|
||||
* preflight and login fails with a bare "Network Error" that names nothing.
|
||||
*/
|
||||
const HR_API = process.env.HR_API_URL ?? "http://localhost:3105";
|
||||
const FINANCE_API = process.env.FINANCE_API_URL ?? "http://localhost:3104";
|
||||
const HR_WEB = process.env.HR_WEB_URL ?? "http://localhost:5285";
|
||||
const FINANCE_WEB = process.env.FINANCE_WEB_URL ?? "http://localhost:5286";
|
||||
const DB_NAME = process.env.DB_NAME ?? "smart_office_e2e";
|
||||
|
||||
const STORAGE = path.join(__dirname, "fixtures", "storage");
|
||||
|
||||
const webEnv = {
|
||||
GITHUB_PACKAGE_TOKEN: process.env.GITHUB_PACKAGE_TOKEN ?? "dummy",
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
testDir: path.join(__dirname, "specs"),
|
||||
// Shared seeded database, and several scenarios move state that cannot be
|
||||
// moved back (a posted journal, an approved payroll run). Serialize, as the
|
||||
// passenger suite does, so assertions stay deterministic.
|
||||
fullyParallel: false,
|
||||
workers: 1,
|
||||
retries: 0,
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 10_000 },
|
||||
globalSetup: path.join(__dirname, "global-setup.ts"),
|
||||
reporter: [
|
||||
["list"],
|
||||
[
|
||||
"html",
|
||||
{
|
||||
outputFolder: path.join(__dirname, "..", "e2e-hr-finance-report"),
|
||||
open: "never",
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm --filter @edr/hr-api start",
|
||||
url: `${HR_API}/api-docs`,
|
||||
timeout: 180_000,
|
||||
reuseExistingServer: true,
|
||||
env: { ...webEnv, PORT: "3105", DB_NAME, CORS_ORIGINS: HR_WEB },
|
||||
},
|
||||
{
|
||||
command: "pnpm --filter @edr/finance-api start",
|
||||
url: `${FINANCE_API}/api-docs`,
|
||||
timeout: 180_000,
|
||||
reuseExistingServer: true,
|
||||
env: { ...webEnv, PORT: "3104", DB_NAME, CORS_ORIGINS: FINANCE_WEB },
|
||||
},
|
||||
{
|
||||
command: "pnpm --filter @edr/hr-web dev",
|
||||
url: HR_WEB,
|
||||
timeout: 120_000,
|
||||
reuseExistingServer: true,
|
||||
env: {
|
||||
...webEnv,
|
||||
PORT: "5285",
|
||||
VITE_HR_API_URL: HR_API,
|
||||
// hr-api serves POST /api/v1/auth/login itself (it embeds IamModule).
|
||||
// The app's own .env points login at passenger-api :4000 on the belief
|
||||
// that it does not — that belief is wrong, and the suite does not
|
||||
// inherit the coupling.
|
||||
VITE_AUTH_API_URL: HR_API,
|
||||
VITE_AUTH_BASE_PATH: "/api/v1",
|
||||
VITE_CLIENT_APP: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "pnpm --filter @edr/finance-web dev",
|
||||
url: FINANCE_WEB,
|
||||
timeout: 120_000,
|
||||
reuseExistingServer: true,
|
||||
env: {
|
||||
...webEnv,
|
||||
PORT: "5286",
|
||||
VITE_FINANCE_API_URL: FINANCE_API,
|
||||
VITE_AUTH_API_URL: FINANCE_API,
|
||||
VITE_AUTH_BASE_PATH: "/api/v1",
|
||||
VITE_CLIENT_APP: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
use: {
|
||||
trace: "retain-on-failure",
|
||||
screenshot: "only-on-failure",
|
||||
actionTimeout: 15_000,
|
||||
launchOptions: { slowMo: Number(process.env.SLOWMO ?? 0) },
|
||||
},
|
||||
|
||||
/**
|
||||
* ONE PROJECT PER APP, not per persona.
|
||||
*
|
||||
* Per-persona projects were the first design and were wrong: a project's
|
||||
* `testMatch` selects files, so every HR spec ran once under each of the four
|
||||
* HR personas. A gating scenario written for `hr-employee` then also ran as
|
||||
* `hr-manager` and failed — not because the gate was broken, but because it
|
||||
* was asserted against the wrong role. Worse, the inverse would pass silently.
|
||||
*
|
||||
* The persona is a property of the SCENARIO, so each `describe` declares it:
|
||||
*
|
||||
* test.use({ storageState: storageFor("hr-employee") })
|
||||
*
|
||||
* The project only supplies what is genuinely per-app: the baseURL.
|
||||
*/
|
||||
projects: [
|
||||
{
|
||||
name: "hr",
|
||||
testMatch: /specs\/hr\/.*\.spec\.ts/,
|
||||
use: { ...devices["Desktop Chrome"], baseURL: HR_WEB },
|
||||
},
|
||||
{
|
||||
name: "finance",
|
||||
testMatch: /specs\/finance\/.*\.spec\.ts/,
|
||||
use: { ...devices["Desktop Chrome"], baseURL: FINANCE_WEB },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/** Path to a persona's storageState — every describe block names its own. */
|
||||
export const storageFor = (personaKey: string): string => {
|
||||
if (!PERSONAS.some((p) => p.key === personaKey)) {
|
||||
throw new Error(`Unknown persona "${personaKey}"`);
|
||||
}
|
||||
return path.join(STORAGE, `${personaKey}.json`);
|
||||
};
|
||||
Reference in New Issue
Block a user