From 94b6515a031dfcd8ae4383c886c842ceba6aee50 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 10 Sep 2026 14:04:03 +0200 Subject: [PATCH] Entorno para test E2E --- .gitignore | 6 ++++ apps/server/.env.e2e.example | 29 ++++++++++++++++ apps/web/.env.e2e.example | 2 ++ package.json | 4 +++ playwright.config.ts | 40 +++++++++++++++++++++++ pnpm-lock.yaml | 28 ++++++++++++++++ tests/e2e/.gitkeep | 0 tests/e2e/README.md | 29 ++++++++++++++++ tests/e2e/support/.gitkeep | 0 tests/e2e/support/ensure-e2e-database.mjs | 13 ++++++++ 10 files changed, 151 insertions(+) create mode 100644 apps/server/.env.e2e.example create mode 100644 apps/web/.env.e2e.example create mode 100644 playwright.config.ts create mode 100644 tests/e2e/.gitkeep create mode 100644 tests/e2e/README.md create mode 100644 tests/e2e/support/.gitkeep create mode 100644 tests/e2e/support/ensure-e2e-database.mjs diff --git a/.gitignore b/.gitignore index 1213275f..c2bc4412 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,9 @@ dist-ssr server/dist public/dist out + +playwright-report/ +test-results/ +blob-report/ +apps/server/.env.e2e +apps/web/.env.e2e diff --git a/apps/server/.env.e2e.example b/apps/server/.env.e2e.example new file mode 100644 index 00000000..5616358b --- /dev/null +++ b/apps/server/.env.e2e.example @@ -0,0 +1,29 @@ +NODE_ENV=test +PORT=3002 +API_BASE_PATH=/api/v1 +FRONTEND_URL=http://127.0.0.1:5173 +TRUST_PROXY=0 +TIMEZONE=Europe/Madrid +DB_DIALECT=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_NAME=uecko_erp_e2e +DB_USER=replace-with-e2e-user +DB_PASS=replace-with-e2e-password +DB_LOGGING=false +DB_SSL=false +DB_SYNC_MODE=none +WARMUP_TIMEOUT_MS=30000 +WARMUP_STRICT=false +JWT_SECRET=replace-with-a-dedicated-e2e-secret-of-at-least-32-characters +JWT_ACCESS_EXPIRATION=15m +JWT_REFRESH_EXPIRATION=7d +TEMPLATES_PATH=./storage/e2e/templates +DOCUMENTS_PATH=./storage/e2e/documents +SIGNED_DOCUMENTS_PATH=./storage/e2e/signed-documents +FASTREPORT_BIN=replace-with-fastreport-binary-path +SIGNING_SERVICE_URL=http://127.0.0.1:3003 +SIGNING_SERVICE_METHOD=POST +SIGNING_SERVICE_TIMEOUT_MS=5000 +SIGNING_SERVICE_MAX_RETRIES=0 +COMPANY_CERTIFICATES_PATH=./storage/e2e/certificates diff --git a/apps/web/.env.e2e.example b/apps/web/.env.e2e.example new file mode 100644 index 00000000..9e41d99f --- /dev/null +++ b/apps/web/.env.e2e.example @@ -0,0 +1,2 @@ +VITE_API_SERVER_URL=http://127.0.0.1:3002/api/v1 +VITE_FORCE_INITIAL_ONBOARDING=false diff --git a/package.json b/package.json index 9ad83b3e..25d40bbd 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,15 @@ "check": "biome check .", "check:write": "biome check --write --unsafe .", "test:frontend": "node --experimental-test-module-mocks --import tsx --test", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", "test:unit": "node --import tsx --test", "typecheck": "tsc -p tsconfig.json --noEmit" }, "devDependencies": { "@biomejs/biome": "2.4.11", + "@playwright/test": "^1.63.0", "@repo/typescript-config": "workspace:*", "@tailwindcss/postcss": "^4.2.2", "@typescript-eslint/eslint-plugin": "^8.58.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..669a8a00 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,40 @@ +import { defineConfig, devices } from "@playwright/test"; + +const localBaseUrl = "http://127.0.0.1:5173"; +const baseURL = process.env.E2E_BASE_URL ?? localBaseUrl; +const startLocalStack = !process.env.E2E_BASE_URL; + +export default defineConfig({ + testDir: "./tests/e2e", + timeout: 30_000, + expect: { timeout: 5_000 }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + reporter: [["html", { open: "never" }]], + use: { + baseURL, + testIdAttribute: "data-testid", + trace: "on-first-retry", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: startLocalStack + ? [ + { + command: + "node tests/e2e/support/ensure-e2e-database.mjs && cd apps/server && node --env-file=.env.e2e --import=tsx src/index.ts", + url: "http://127.0.0.1:3002/api/v1/__ready", + timeout: 120_000, + reuseExistingServer: !process.env.CI, + }, + { + command: "cd apps/web && pnpm dev -- --mode e2e --port 5173", + url: localBaseUrl, + timeout: 120_000, + reuseExistingServer: !process.env.CI, + }, + ] + : undefined, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa5ae177..fb6327cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@biomejs/biome': specifier: 2.4.11 version: 2.4.11 + '@playwright/test': + specifier: ^1.63.0 + version: 1.63.0 '@repo/typescript-config': specifier: workspace:* version: link:packages/typescript-config @@ -3074,6 +3077,11 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@playwright/test@1.63.0': + resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==} + engines: {node: '>=20'} + hasBin: true + '@radix-ui/number@1.1.3': resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} @@ -6952,6 +6960,16 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + plop@4.0.5: resolution: {integrity: sha512-pJz6oWC9LyBp5mBrRp8AUV2RNiuGW+t/HOs4zwN+b/3YxoObZOOFvjn1mJMpAeKi2pbXADMFOOVQVTVXEdDHDw==} engines: {node: '>=18'} @@ -9238,6 +9256,10 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6 + '@playwright/test@1.63.0': + dependencies: + playwright: 1.63.0 + '@radix-ui/number@1.1.3': {} '@radix-ui/primitive@1.1.3': {} @@ -13054,6 +13076,12 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + plop@4.0.5(@types/node@25.9.1): dependencies: '@types/liftoff': 4.0.3 diff --git a/tests/e2e/.gitkeep b/tests/e2e/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/README.md b/tests/e2e/README.md new file mode 100644 index 00000000..c5db5d17 --- /dev/null +++ b/tests/e2e/README.md @@ -0,0 +1,29 @@ +# Infraestructura E2E + +Suite de Playwright para navegador, frontend, backend y MariaDB E2E. Aún no contiene pruebas funcionales. + +## Estructura + +El primer nivel debe coincidir exactamente con el módulo propietario en `modules`: por ejemplo, `identity` usará `tests/e2e/identity/login.spec.ts` y facturas de clientes `tests/e2e/customer-invoices/`. No usar `auth`, `invoices` ni `settings`. `support/` es exclusivamente infraestructura transversal. + +## Variables y base de datos + +Copia `apps/server/.env.e2e.example` a `apps/server/.env.e2e` y completa una MariaDB exclusiva E2E; `DB_NAME` nunca debe ser la base de desarrollo. Copia `apps/web/.env.e2e.example` a `apps/web/.env.e2e`. + +El backend arranca con `node --env-file=.env.e2e`, que prevalece sobre `.env`. La plantilla usa `DB_SYNC_MODE=none`; no hay reset, seed ni operación destructiva en esta fase. Las futuras operaciones destructivas deberán rechazar cualquier base que no sea E2E. + +`E2E_BASE_URL` permite usar un stack E2E ya iniciado y evita levantar procesos locales. Futuras credenciales: `E2E_USER_EMAIL` y `E2E_USER_PASSWORD`, nunca versionadas. + +## Ejecución + +```bash +pnpm test:e2e +pnpm test:e2e:ui +pnpm test:e2e:headed +``` + +Localmente se espera backend en `http://127.0.0.1:3002/api/v1/__ready` y Vite en `http://127.0.0.1:5173`. + +## Selectores + +Prioridad: `id` técnico estable, `data-testid`, atributo técnico como `name` y CSS estable solo excepcionalmente. No usar textos, labels o `aria-label` traducibles, Tailwind visual, `nth-child` ni DOM incidental. Playwright configura `data-testid`. diff --git a/tests/e2e/support/.gitkeep b/tests/e2e/support/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/support/ensure-e2e-database.mjs b/tests/e2e/support/ensure-e2e-database.mjs new file mode 100644 index 00000000..35e9dc55 --- /dev/null +++ b/tests/e2e/support/ensure-e2e-database.mjs @@ -0,0 +1,13 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const envFile = resolve("apps/server/.env.e2e"); +const databaseName = readFileSync(envFile, "utf8") + .split(/\r?\n/) + .find((line) => line.startsWith("DB_NAME=")) + ?.slice("DB_NAME=".length) + .trim(); + +if (databaseName !== "uecko_erp_e2e") { + throw new Error("E2E requires DB_NAME=uecko_erp_e2e in apps/server/.env.e2e."); +}