@keber/qa-framework 1.10.0 → 1.12.0

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 (113) hide show
  1. package/CHANGELOG.md +63 -18
  2. package/README.md +2 -2
  3. package/docs/architecture.md +30 -26
  4. package/docs/decisions/lane-heartbeat-caller.md +86 -0
  5. package/docs/final-report.md +23 -23
  6. package/docs/folder-structure-guide.md +106 -80
  7. package/docs/installation.md +35 -7
  8. package/docs/skills-architecture.md +23 -23
  9. package/docs/spec-driven-philosophy.md +9 -9
  10. package/docs/usage-with-agent.md +83 -9
  11. package/docs/visuals/diag-01-ejecutivo-1.png +0 -0
  12. package/docs/visuals/diag-02-tactico-1.png +0 -0
  13. package/docs/visuals/diag-03-tecnico-1.png +0 -0
  14. package/docs/visuals/info-01-ejecutivo-1.png +0 -0
  15. package/docs/visuals/info-02-tactico-1.png +0 -0
  16. package/docs/visuals/info-03-tecnico-1.png +0 -0
  17. package/docs/visuals/pres-01-ejecutivo-1.png +0 -0
  18. package/docs/visuals/pres-02-tactico-1.png +0 -0
  19. package/docs/visuals/pres-03-tecnico-1.png +0 -0
  20. package/docs/visuals/qa-01-problema-1.png +0 -0
  21. package/docs/visuals/qa-02-que-es-1.png +0 -0
  22. package/docs/visuals/qa-03-pipeline-1.png +0 -0
  23. package/docs/visuals/qa-04-6-archivos-1.png +0 -0
  24. package/docs/visuals/qa-05-humano-1.png +0 -0
  25. package/docs/visuals/qa-06-agente-1.png +0 -0
  26. package/docs/visuals/qa-07-skills-arch-1.png +0 -0
  27. package/docs/visuals/qa-08-traceability-1.png +0 -0
  28. package/docs/visuals/qa-en-01-problem-1.png +0 -0
  29. package/docs/visuals/qa-en-02-what-is-1.png +0 -0
  30. package/docs/visuals/qa-en-03-pipeline-1.png +0 -0
  31. package/docs/visuals/qa-en-04-6files-1.png +0 -0
  32. package/docs/visuals/qa-en-05-human-1.png +0 -0
  33. package/docs/visuals/qa-en-06-agent-1.png +0 -0
  34. package/docs/visuals/qa-en-07-skills-arch-1.png +0 -0
  35. package/docs/visuals/qa-en-08-traceability-1.png +0 -0
  36. package/examples/module-example/suppliers/00-inventory.md +1 -1
  37. package/examples/module-example/suppliers/suppliers-create.spec.ts +8 -8
  38. package/integrations/ado-powershell/README.md +1 -1
  39. package/integrations/ado-powershell/pipelines/azure-pipeline-qa.yml +92 -25
  40. package/integrations/ado-powershell/scripts/sync-ado-titles.ps1 +2 -2
  41. package/integrations/playwright/README.md +128 -4
  42. package/package.json +9 -6
  43. package/qa-framework.config.json +24 -3
  44. package/scripts/check-forbidden-chars.js +187 -0
  45. package/scripts/check-spanish-accents.js +159 -0
  46. package/scripts/cli.js +1 -1
  47. package/scripts/generate.js +2 -2
  48. package/scripts/init.js +108 -45
  49. package/scripts/lib/claude-agents.js +78 -0
  50. package/scripts/lib/claude-commands.js +56 -0
  51. package/scripts/sync-version.js +36 -0
  52. package/scripts/upgrade.js +461 -30
  53. package/scripts/validate.js +17 -17
  54. package/skills/qa-ado-integration/SKILL.md +19 -19
  55. package/skills/qa-ado-integration/references/scripts-and-config.md +6 -6
  56. package/skills/qa-automation/SKILL.md +49 -27
  57. package/skills/qa-automation/references/config-checklist.md +4 -4
  58. package/skills/qa-automation/references/dom-inspection-template.js +7 -7
  59. package/skills/qa-automation/references/patterns.md +20 -20
  60. package/skills/qa-automation/references/pom-template.md +161 -0
  61. package/skills/qa-maintenance/SKILL.md +11 -11
  62. package/skills/qa-maintenance/references/update-rules.md +8 -8
  63. package/skills/qa-module-analysis/SKILL.md +20 -19
  64. package/skills/qa-module-analysis/references/exploration-checklist.md +4 -4
  65. package/skills/qa-module-analysis/references/output-verification.md +69 -0
  66. package/skills/qa-module-analysis/references/spec-file-formats.md +14 -14
  67. package/skills/qa-spec-generation/SKILL.md +13 -7
  68. package/skills/qa-test-cases/SKILL.md +11 -81
  69. package/skills/qa-test-cases/references/test-case-template.md +5 -5
  70. package/skills/qa-test-plan/SKILL.md +22 -86
  71. package/skills/qa-test-plan/references/plan-de-pruebas-template.md +24 -24
  72. package/skills/qa-test-stabilization/SKILL.md +59 -30
  73. package/skills/qa-test-stabilization/references/classification-protocol.md +35 -35
  74. package/templates/agent-next-steps.md +10 -10
  75. package/templates/agents/qa-analisis.md +142 -0
  76. package/templates/agents/qa-asesoria.md +48 -0
  77. package/templates/agents/qa-informe-resultados.md +234 -0
  78. package/templates/agents/qa-plan.md +131 -0
  79. package/templates/automation-scaffold/.env.example +52 -3
  80. package/templates/automation-scaffold/fixtures/auth.ts +27 -5
  81. package/templates/automation-scaffold/fixtures/base.ts +130 -0
  82. package/templates/automation-scaffold/fixtures/test-helpers.ts +1 -1
  83. package/templates/automation-scaffold/global-setup.ts +196 -37
  84. package/templates/automation-scaffold/playwright.config.ts +74 -10
  85. package/templates/automation-scaffold/scripts/global-setup-guards.js +184 -0
  86. package/templates/automation-scaffold/scripts/lane-config.js +269 -0
  87. package/templates/automation-scaffold/scripts/lane-lock.js +589 -0
  88. package/templates/defect-report.md +18 -4
  89. package/templates/execution-report.md +9 -9
  90. package/templates/integration-scaffold/.env.example +40 -0
  91. package/templates/integration-scaffold/README.md +77 -0
  92. package/templates/integration-scaffold/global-setup.ts +119 -0
  93. package/templates/integration-scaffold/package.json +15 -0
  94. package/templates/integration-scaffold/playwright.config.ts +92 -0
  95. package/templates/integration-scaffold/tests/example.spec.ts +43 -0
  96. package/templates/memory/ci-pipeline-findings.md +36 -0
  97. package/templates/memory/data-volatility-strategies.md +70 -0
  98. package/templates/memory/e2e-stabilization-patterns.md +54 -0
  99. package/templates/qa-framework.instructions.md +16 -7
  100. package/templates/qa-framework.rules.md +53 -0
  101. package/templates/qa-readme.md +26 -5
  102. package/templates/session-summary.md +2 -2
  103. package/templates/specification/00-inventory.md +3 -3
  104. package/templates/specification/01-business-rules.md +6 -6
  105. package/templates/specification/02-workflows.md +1 -1
  106. package/templates/specification/03-roles-permissions.md +1 -1
  107. package/templates/specification/04-test-data.md +8 -8
  108. package/templates/specification/05-test-scenarios.md +5 -5
  109. package/templates/test-case.md +3 -3
  110. package/templates/test-data-guidelines.md +7 -7
  111. package/templates/test-plan-sprint.md +13 -13
  112. package/templates/test-plan.md +3 -3
  113. package/skills/qa-test-plan/references/priority-and-feasibility.md +0 -159
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: qa-plan
3
+ description: Genera el Plan de Pruebas manual de un sprint (resumen ejecutivo + tabla detallada de casos de prueba trazables a items de Azure DevOps) ejecutable en el timebox del sprint. Modo por defecto para pedidos de "plan de pruebas" de un sprint; también se invoca con "modo PLAN".
4
+ tools: Read, Write, Grep, Glob
5
+ model: sonnet
6
+ ---
7
+
8
+ # Rol
9
+
10
+ Actúas como Asistente experto en Aseguramiento de la Calidad (QA) con foco práctico en planes de prueba **manuales** ejecutables en el timebox del sprint, en contextos de madurez inicial. Este agente cubre exclusivamente el modo **PLAN** (modo por defecto del flujo original).
11
+
12
+ # Audiencia y Estilo
13
+
14
+ - Público: equipo QA/dev de {{PROJECT_DISPLAY_NAME}} ({{LOCALE_LANGUAGE_LABEL}}).
15
+ - Tono: claro, directo y accionable (sin jerga innecesaria).
16
+ - Zona horaria: {{TIMEZONE}}.
17
+ - Fechas siempre en formato **{{DATE_FORMAT}}**.
18
+
19
+ # Contexto Operativo
20
+
21
+ - Sprints de {{SPRINT_DURATION_DAYS}} días con **{{MANUAL_TESTING_TIMEBOX_DAYS}} días** para ejecutar pruebas manuales.
22
+ - Procesos inmaduros: minutas/historias/criterios incompletos, evidencia parcial, ruido en transcripciones.
23
+ - Objetivo principal: **confirmar resolución** de issues/bugs/tasks del sprint y cubrir flujos críticos del área afectada.
24
+ - E2E/UI automatizadas: no prioridad, pero puedes sugerirlas brevemente si aportan.
25
+ - Riesgos locales a considerar: separador decimal (coma vs punto), cálculos monetarios, integraciones, permisos, datos maestros, impactos legales/tributarios (ajusta esta lista a los riesgos reales del dominio del proyecto).
26
+
27
+ # Entradas posibles
28
+
29
+ - Minutas o resúmenes de planificación (`<Proyecto>`, `<Sprint>`).
30
+ - Items de Azure DevOps (Issue/Bug/Task) en texto/Excel/PPT/imágenes/transcripciones.
31
+ - Imágenes de UI, descripciones de componentes e interfaces.
32
+ - Casos de prueba anteriores (XLS ADO).
33
+
34
+ # Política anti-alucinación y uso de insumos incompletos
35
+
36
+ - **Nunca inventes datos de negocio**.
37
+ - Cuando falten detalles críticos, crea el bloque **Faltantes críticos** con preguntas puntuales y sigue con un **Plan mínimo viable**, marcando **TODO:** donde falte.
38
+ - Cuando debas asumir algo, marca **Supuesto:** (fácil de remover).
39
+ - Si hay información contradictoria, prioriza lo más reciente y explícitalo.
40
+ - En la columna **Confirma**, si no hay ID, usa **"-"** y agrega el punto a **Faltantes críticos**.
41
+
42
+ # Pipeline (proceso)
43
+
44
+ 0) **Resumen estructurado** (si las entradas son ruidosas): objetivo, áreas/módulos impactados, lista preliminar de items.
45
+ 1) **Ingesta & Normalización**: Proyecto, Sprint, áreas afectadas, lista de items (ID + título).
46
+ 2) **Deduplicación & Alcance**: elimina duplicados, agrupa por área; extrae criterios de aceptación si existen.
47
+ 3) **Universo de Tests**: lista **todos** los escenarios de prueba identificables para el alcance del sprint (happy path, negativos, permisos, transiciones de estado, integraciones, edge cases), **sin filtrar aún por el timebox de {{MANUAL_TESTING_TIMEBOX_DAYS}} días**. Para cada escenario, clasifica su **factibilidad de automatización** (mismo criterio de `qa-framework`, no inventes uno nuevo):
48
+ - **Automatizable completo**: determinístico, observable en UI/API, sin dependencia de sistemas externos.
49
+ - **Automatizable parcial**: requiere mock de un sistema externo o inspección humana.
50
+ - **No automatizable**: acceso físico, no-determinístico, efectos irreversibles en QA, o `BLOCKED-PERMISSIONS`.
51
+ 4) **Tests Priorizados (timebox {{MANUAL_TESTING_TIMEBOX_DAYS}} días)**: selecciona del universo el subconjunto que entra a la Tabla de Pruebas, combinando:
52
+ - **Riesgo/valor de negocio** via P0->P3:
53
+ - **P0**: Confirmación por cada Issue/Bug/Task del sprint; camino feliz crítico; riesgo de corrupción de datos o show-stopper.
54
+ - **P1**: Smoke crítico del flujo impactado; negativos comunes; dependencias cross-módulo.
55
+ - **P2**: Regresión mínima adyacente (alto uso/alto riesgo); features secundarias.
56
+ - **P3**: Exploratoria timeboxed (1-2 charters); edge cases de baja frecuencia.
57
+ - **Factibilidad de automatización**: a igualdad de prioridad, prefiere para el set ejecutable los escenarios automatizables (reducen costo de mantención futura); no automatizables de baja prioridad son los primeros candidatos a quedar fuera.
58
+ - Lo que no se selecciona va a **Universo excluido** con motivo - nunca se descarta silenciosamente.
59
+ - **No infles el universo por inflarlo**: documentarlo es para trazabilidad/auditoría de cobertura, no para maximizar conteo. Mergea variaciones casi idénticas y usa **Confirma** para referenciar múltiples IDs.
60
+ 5) **Estrategia & Suites**: define TestSuites por área/épica impactada, en base a los Tests Priorizados.
61
+ 6) **Casos de Prueba**: trazables a items del sprint (**Confirma**).
62
+ 7) **Auto-revisión**: ejecuta el checklist de calidad.
63
+
64
+ # Priorización y Etiquetas
65
+
66
+ - La **Tabla de Pruebas** solo incluye **Tests Priorizados** - el Universo completo y el Universo excluido se documentan aparte (ver "Salida - Plan de Pruebas").
67
+ - Mantén P0->P1->P2->P3 alineado al timebox de {{MANUAL_TESTING_TIMEBOX_DAYS}} días.
68
+ - Etiquetas por caso (en el Título): **[SMOKE]**, **[REGRESIÓN]**, **[CONFIRMACIÓN]**, **[EXPLORATORIA]** (pueden coexistir con P0-P3).
69
+ - Cobertura mínima: al menos 1 **Smoke** por funcionalidad clave + **Regresión acotada** en áreas afectadas + **Confirmación** por cada item del sprint.
70
+
71
+ # Reglas de Calidad (Checklist interno)
72
+
73
+ - Cada TestCase incluye: **Área funcional**, **Título**, **Descripción** (con precondiciones y **datos mínimos**), **Steps** numerados, **Resultado Esperado** verificable, **Confirma** (ID o "-").
74
+ - El **último paso** siempre tiene **Resultado Esperado** explícito.
75
+ - Sin pasos huérfanos ni resultados vagos.
76
+ - **Deduplicación**: mergea casos idénticos; en **Confirma** puedes referenciar múltiples IDs.
77
+ - **Trazabilidad**: incluir **Matriz de Trazabilidad (ID <-> TestCases)**.
78
+
79
+ # Salida - Plan de Pruebas
80
+
81
+ 1) **Resumen del Plan** (contenido de la respuesta en chat):
82
+ - **Marco de Pruebas**: Objetivo, Alcance (incluye fuera de alcance), Entregables
83
+ - **Universo de Tests**: conteo total de escenarios identificados, breakdown por factibilidad de automatización (completo/parcial/no automatizable).
84
+ - **Plan**: Estrategia (P0-P3), TestSuites, **Datos mínimos**, Precondiciones generales, Charters (si aplica) - sobre los Tests Priorizados.
85
+ - **Supuestos & Faltantes críticos**
86
+
87
+ 2) **Plan detallado**: archivo Markdown con la **Tabla de Pruebas** (solo Tests Priorizados) y el **Universo excluido**:
88
+
89
+ | N | TestCase | Área Funcional | Título | Descripción | Steps | Resultado Esperado | Confirma | Tipo |
90
+ |---|----------|----------------|--------|-------------|-------|--------------------|----------|------|
91
+
92
+ **Convenciones obligatorias**:
93
+ - **TestCase**: dejar **en blanco** (otro proceso generara IDs, ver skill `qa-ado-integration` si se pide crear los casos en ADO).
94
+ - **Steps**: lista numerada **en una celda** con `<br>`:
95
+ `1) Precondicion...<br>2) Accion...<br>3) Verificacion...`
96
+ - **Resultado Esperado**: concreto y verificable (evitar "funciona correctamente").
97
+ - **Confirma**: usar **`Bug 17166`**, **`Issue 17168`**, **`Task 17179`**; si no aplica, **"N/A"**; si falta ID, **"-"** y mover a **Faltantes críticos**.
98
+ - **Tipo**: `Manual` | `Automatizado` | `Ambos` | `Bloqueado` - según la factibilidad de automatización determinada en la etapa de Universo de Tests (`Bloqueado` si es `BLOCKED-PERMISSIONS`/`PENDING-CODE`).
99
+ - **Etiquetas** en **Título**: [SMOKE]/[REGRESIÓN]/[CONFIRMACIÓN]/[EXPLORATORIA] + nivel P0-P3 si ayuda.
100
+ - Mantén trazabilidad: al menos un caso **P0** por item crítico del sprint.
101
+
102
+ Después de la Tabla de Pruebas, agrega la sección **Universo excluido**:
103
+
104
+ | TC | Título | Motivo de exclusión |
105
+ |----|--------|----------------------|
106
+
107
+ Motivos válidos: fuera de scope del sprint, baja probabilidad/impacto, `PENDING-CODE`, `BLOCKED-PERMISSIONS`, requiere automatización aún no lista, etc. Esta tabla es el respaldo de trazabilidad de todo lo que se generó pero no entró al timebox de {{MANUAL_TESTING_TIMEBOX_DAYS}} días.
108
+
109
+ ## Regla de entrega
110
+
111
+ - Ruta de salida: `qa/02-test-plans/sprints/Sprint-<Sprint>/Plan-de-Pruebas-<Proyecto>-Sprint-<Sprint>.md`
112
+ (`<Proyecto>` es el `project.name` de `qa/qa-framework.config.json`; `<Sprint>` es el número de sprint sin padding, p.ej. `qa/02-test-plans/sprints/Sprint-12/Plan-de-Pruebas-{{PROJECT_NAME}}-Sprint-12.md`. Sigue la misma convención que los `Plan-de-Pruebas-*.md` ya archivados en `qa/02-test-plans/historical/sprint-*/` y documentada en `qa/QA-STRUCTURE-GUIDE.md`, si existen. Si el plan cubre un solo módulo, puedes agregar el sufijo `-{modulo}` al nombre, igual que el resto del pipeline).
113
+ - Si el directorio del sprint no existe, créalo antes de escribir el archivo.
114
+ - Antes de escribir, si ya existe un archivo con ese nombre, léelo primero y confirma con el usuario si se debe sobrescribir (no lo sobrescribas silenciosamente).
115
+ - El **Resumen del Plan** (punto 1) va en tu respuesta de chat; el **Plan detallado** con la tabla completa (punto 2) es el que se escribe al archivo.
116
+ - Al terminar, tu respuesta al usuario debe indicar la **ruta relativa exacta** del archivo creado.
117
+ - Nunca reportes la tarea como completa si el archivo no fue escrito con la herramienta `Write`.
118
+
119
+ # Checklist de calidad (marcar antes de entregar)
120
+
121
+ - [ ] Cada Bug/Issue/Task crítico tiene al menos un caso **P0** con **Confirma**.
122
+ - [ ] Los **Steps** usan `<br>` y están numerados.
123
+ - [ ] No hay **Resultados Esperados** vagos.
124
+ - [ ] La Tabla de Pruebas contiene solo **Tests Priorizados**; el **Universo de Tests** completo se resumió (conteo + factibilidad) y el **Universo excluido** quedó documentado con motivo.
125
+ - [ ] Cada fila de la Tabla de Pruebas tiene columna **Tipo** (Manual/Automatizado/Ambos/Bloqueado) coherente con su factibilidad de automatización.
126
+ - [ ] **TODO** y **Faltantes críticos** están claramente indicados.
127
+ - [ ] Se respetó el foco del timebox de {{MANUAL_TESTING_TIMEBOX_DAYS}} días y se documentaron exclusiones (backlog de regresión).
128
+ - [ ] Existe **Matriz de Trazabilidad** (ID <-> TestCases).
129
+ - [ ] El archivo `Plan-de-Pruebas-<Proyecto>-Sprint-<Sprint>.md` existe físicamente en `qa/02-test-plans/sprints/Sprint-<Sprint>/`.
130
+
131
+ Si alguna condición no se cumple, la respuesta se considera incompleta.
@@ -1,5 +1,5 @@
1
1
  # -----------------------------------------------------------------------
2
- # keber/qa-framework — Environment Variables Reference
2
+ # keber/qa-framework - Environment Variables Reference
3
3
  # -----------------------------------------------------------------------
4
4
  # Copy this file to .env and fill in real values.
5
5
  # NEVER commit .env to source control.
@@ -10,6 +10,50 @@
10
10
  # -----------------------------------------------------------------------
11
11
  QA_BASE_URL=https://your-app.qa.example.com
12
12
 
13
+ # -----------------------------------------------------------------------
14
+ # Session and login resilience
15
+ # -----------------------------------------------------------------------
16
+ QA_SESSION_TTL_MS=7200000
17
+ # Optional path to a server-side logout endpoint if the app exposes one.
18
+ # QA_LOGOUT_PATH=/logout
19
+
20
+ # -----------------------------------------------------------------------
21
+ # Parallel QA lanes
22
+ # -----------------------------------------------------------------------
23
+ # A lane is a distinct QA account. Lanes exist because the app under test
24
+ # invalidates a session when the same account logs in again, so two workers
25
+ # sharing one account silently destroy each other's session.
26
+ #
27
+ # The lane table itself lives in qa-framework.config.json (parallelLanes.lanes).
28
+ # Each lane needs its own credentials here, named after that lane's "account"
29
+ # prefix. With no parallelLanes config, only the QA_USER pair below is used.
30
+
31
+ # Pin a run to a single lane. Required by global-setup unless QA_COLD_SETUP=1.
32
+ # QA_LANE_ONLY=1
33
+
34
+ # Escape hatch for an intentional relogin of EVERY lane. A cold setup can
35
+ # invalidate another agent's or CI job's running session, so it is never implicit.
36
+ # QA_COLD_SETUP=1
37
+
38
+ # Log in a specific set of lanes (comma-separated lane ids).
39
+ # QA_AUTH_USER=1
40
+
41
+ # Shared lane-lock state directory. Set this to an absolute shared path when
42
+ # running from multiple git worktrees or checkouts: with the per-checkout default
43
+ # each one gets its OWN lock state, so they never contend, every command reports
44
+ # success, and two sessions can land on the same account. The failure is silent.
45
+ # QA_LANE_LOCKS_DIR=/absolute/path/to/shared/.locks
46
+
47
+ # Minutes before a held lane is swept as stale. A lane with a recent heartbeat is
48
+ # never swept regardless of age.
49
+ # QA_LANE_STALE_MINUTES=20
50
+
51
+ # Credentials for additional lanes, one pair per lane account prefix.
52
+ # QA_USER2_EMAIL=qa-user2@example.com
53
+ # QA_USER2_PASSWORD=CHANGE_ME
54
+ # QA_USER3_EMAIL=qa-user3@example.com
55
+ # QA_USER3_PASSWORD=CHANGE_ME
56
+
13
57
  # -----------------------------------------------------------------------
14
58
  # Login Configuration
15
59
  # These selectors allow the framework to work with any login form.
@@ -42,15 +86,20 @@ QA_USER_PASSWORD=CHANGE_ME
42
86
  # QA_USER_RUT=12345678-9
43
87
 
44
88
  # -----------------------------------------------------------------------
45
- # Azure DevOps Integration (optional — leave blank to disable)
89
+ # Azure DevOps Integration (optional - leave blank to disable)
46
90
  # -----------------------------------------------------------------------
47
91
  # ADO_ORG_URL=https://dev.azure.com/your-org
48
92
  # ADO_PROJECT_NAME=YourProject
49
93
  # ADO_PLAN_ID=00000
50
- # AZURE_TOKEN=your-pat-token-here # Personal Access Token — NEVER commit
94
+ # AZURE_TOKEN=your-pat-token-here # Personal Access Token - NEVER commit
51
95
 
52
96
  # -----------------------------------------------------------------------
53
97
  # CI / Pipeline overrides
54
98
  # -----------------------------------------------------------------------
55
99
  # CI=true # Set automatically by most CI systems
56
100
  # ADO_SYNC_DISABLED=false # Set to true to skip ADO reporter in local runs
101
+
102
+ # -----------------------------------------------------------------------
103
+ # Playwright Service (optional - only needed for cloud or self-hosted grid)
104
+ # -----------------------------------------------------------------------
105
+ # PLAYWRIGHT_SERVICE_URL=wss://eastus.api.playwright.microsoft.com/accounts/<your-id>/browsers
@@ -10,11 +10,11 @@
10
10
  * await loginAs(page, 'admin');
11
11
  *
12
12
  * Alternatively, use storageState via global-setup.ts (recommended for
13
- * performance — avoids repeating the login flow on every test).
13
+ * performance - avoids repeating the login flow on every test).
14
14
  * Use loginAs() only in tests that need to validate the login flow itself
15
15
  * or switch users mid-test.
16
16
  *
17
- * Supported roles are driven by env vars — see .env.example.
17
+ * Supported roles are driven by env vars - see .env.example.
18
18
  */
19
19
 
20
20
  import { Page } from '@playwright/test';
@@ -26,7 +26,7 @@ interface LoginConfig {
26
26
  password: string;
27
27
  }
28
28
 
29
- /** Map role name → credentials from environment variables */
29
+ /** Map role name -> credentials from environment variables */
30
30
  function getCredentials(role: QARole): LoginConfig {
31
31
  switch (role) {
32
32
  case 'admin':
@@ -58,7 +58,7 @@ export async function loginAs(page: Page, role: QARole = 'default'): Promise<voi
58
58
  if (!email || !password) {
59
59
  throw new Error(
60
60
  `[qa-framework/auth] Missing credentials for role "${role}". ` +
61
- `Check your .env file — expected ${role.toUpperCase()}_EMAIL and ${role.toUpperCase()}_PASSWORD.`
61
+ `Check your .env file - expected ${role.toUpperCase()}_EMAIL and ${role.toUpperCase()}_PASSWORD.`
62
62
  );
63
63
  }
64
64
 
@@ -71,7 +71,29 @@ export async function loginAs(page: Page, role: QARole = 'default'): Promise<voi
71
71
 
72
72
  await page.goto(`${baseURL}${loginPath}`, { waitUntil: 'domcontentloaded' });
73
73
  await page.locator(emailSelector).fill(email);
74
- await page.locator(passwordSelector).fill(password);
74
+ // Trace safety: Playwright records fill() argument values in traces, and this
75
+ // project runs with trace/video 'retain-on-failure'. This helper runs for every
76
+ // test that logs in as a role, so a fill() here would leak the plaintext password
77
+ // into far more failure artifacts than the one-time global setup does. Setting the
78
+ // value through evaluate() keeps it out of the trace.
79
+ // Do not "simplify" this back to fill().
80
+ // The input/change events are required because assigning .value directly does
81
+ // not notify SPA frameworks (Blazor/Radzen bind on those events).
82
+ // evaluate() has no auto-wait, so the explicit waitFor replaces the one that
83
+ // locator().fill() performed implicitly.
84
+ await page.locator(passwordSelector).waitFor({ state: 'visible', timeout: 30_000 });
85
+ await page.evaluate(
86
+ ([selector, pwd]) => {
87
+ const input = document.querySelector(selector) as HTMLInputElement | null;
88
+ if (!input) {
89
+ throw new Error(`[qa-framework] Password input not found for selector: ${selector}`);
90
+ }
91
+ input.value = pwd;
92
+ input.dispatchEvent(new Event('input', { bubbles: true }));
93
+ input.dispatchEvent(new Event('change', { bubbles: true }));
94
+ },
95
+ [passwordSelector, password] as const
96
+ );
75
97
  await page.locator(submitSelector).click();
76
98
  await page.waitForSelector(successSelector, { timeout: 15_000 });
77
99
  }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * fixtures/base.ts
3
+ *
4
+ * Playwright test wrapper with TTL-aware session refresh.
5
+ * Import this file instead of `@playwright/test` when the app uses a
6
+ * server-side session that may expire during long runs.
7
+ */
8
+
9
+ import { test as base, devices } from '@playwright/test';
10
+ import { SESSION_TTL_MS } from '../playwright.config';
11
+ import * as dotenv from 'dotenv';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+
15
+ dotenv.config();
16
+
17
+ const authTimestamps = new Map<string, number>();
18
+
19
+ function stateFileForProject(storageState: unknown): string {
20
+ if (typeof storageState === 'string' && storageState) {
21
+ return path.resolve(__dirname, '..', storageState);
22
+ }
23
+ return path.resolve(__dirname, '../.auth/user-default.json');
24
+ }
25
+
26
+ function credentialsForState(stateFile: string): { email: string; password: string } {
27
+ if (stateFile.includes('user-2')) {
28
+ return {
29
+ email: process.env.QA_USER2_EMAIL ?? '',
30
+ password: process.env.QA_USER2_PASSWORD ?? '',
31
+ };
32
+ }
33
+
34
+ return {
35
+ email: process.env.QA_USER_EMAIL ?? '',
36
+ password: process.env.QA_USER_PASSWORD ?? '',
37
+ };
38
+ }
39
+
40
+ function lastAuthTime(stateFile: string): number {
41
+ if (!authTimestamps.has(stateFile)) {
42
+ try {
43
+ authTimestamps.set(stateFile, fs.statSync(stateFile).mtimeMs);
44
+ } catch {
45
+ authTimestamps.set(stateFile, 0);
46
+ }
47
+ }
48
+ return authTimestamps.get(stateFile) ?? 0;
49
+ }
50
+
51
+ async function reauth(browser: import('@playwright/test').Browser, stateFile: string): Promise<void> {
52
+ const { email, password } = credentialsForState(stateFile);
53
+ const baseURL = process.env.QA_BASE_URL ?? '';
54
+ const loginPath = process.env.QA_LOGIN_PATH ?? '/login';
55
+ const emailSelector = process.env.QA_LOGIN_EMAIL_SELECTOR ?? 'input[type="email"]';
56
+ const passwordSelector = process.env.QA_LOGIN_PASSWORD_SELECTOR ?? 'input[type="password"]';
57
+ const submitSelector = process.env.QA_LOGIN_SUBMIT_SELECTOR ?? 'button[type="submit"]';
58
+ const successSelector = process.env.QA_LOGIN_SUCCESS_SELECTOR ?? '.dashboard, .main-content, [data-testid="app-shell"]';
59
+
60
+ const context = await browser.newContext({ ...devices['Desktop Chrome'], baseURL });
61
+ const page = await context.newPage();
62
+
63
+ try {
64
+ await page.goto(loginPath, { waitUntil: 'load', timeout: 60_000 });
65
+ await page.locator(emailSelector).fill(email);
66
+ // Trace safety: Playwright records fill() argument values in traces, and this
67
+ // project runs with trace/video 'retain-on-failure'. This reauth path runs for
68
+ // every test whose session exceeded the TTL, so a fill() here would leak the
69
+ // plaintext password into far more failure artifacts than the one-time global
70
+ // setup does. Setting the value through evaluate() keeps it out of the trace.
71
+ // Do not "simplify" this back to fill().
72
+ // The input/change events are required because assigning .value directly does
73
+ // not notify SPA frameworks (Blazor/Radzen bind on those events).
74
+ // evaluate() has no auto-wait, so the explicit waitFor replaces the one that
75
+ // locator().fill() performed implicitly.
76
+ await page.locator(passwordSelector).waitFor({ state: 'visible', timeout: 30_000 });
77
+ await page.evaluate(
78
+ ([selector, pwd]) => {
79
+ const input = document.querySelector(selector) as HTMLInputElement | null;
80
+ if (!input) {
81
+ throw new Error(`[qa-framework] Password input not found for selector: ${selector}`);
82
+ }
83
+ input.value = pwd;
84
+ input.dispatchEvent(new Event('input', { bubbles: true }));
85
+ input.dispatchEvent(new Event('change', { bubbles: true }));
86
+ },
87
+ [passwordSelector, password] as const
88
+ );
89
+ await page.locator(submitSelector).click();
90
+ await page.waitForSelector(successSelector, { timeout: 30_000 });
91
+
92
+ const dir = path.dirname(stateFile);
93
+ if (!fs.existsSync(dir)) {
94
+ fs.mkdirSync(dir, { recursive: true });
95
+ }
96
+
97
+ await context.storageState({ path: stateFile });
98
+ authTimestamps.set(stateFile, Date.now());
99
+ console.log(`[session-refresh] storageState renewed: ${stateFile}`);
100
+ } finally {
101
+ await context.close();
102
+ }
103
+ }
104
+
105
+ export const test = base.extend<{ authStatePath: string }>({
106
+ authStatePath: async ({}, use, testInfo) => {
107
+ await use(stateFileForProject(testInfo.project.use.storageState));
108
+ },
109
+
110
+ context: async ({ browser }, use, testInfo) => {
111
+ const stateFile = stateFileForProject(testInfo.project.use.storageState);
112
+ if (Date.now() - lastAuthTime(stateFile) > SESSION_TTL_MS) {
113
+ await reauth(browser, stateFile).catch((error) => {
114
+ console.error(`[session-refresh] reauth failed: ${(error as Error).message}`);
115
+ });
116
+ }
117
+
118
+ const context = await browser.newContext({
119
+ ...devices['Desktop Chrome'],
120
+ storageState: stateFile,
121
+ baseURL: process.env.QA_BASE_URL,
122
+ });
123
+
124
+ await use(context);
125
+ await context.close();
126
+ },
127
+ });
128
+
129
+ export { expect } from '@playwright/test';
130
+ export type { Page, Browser, BrowserContext } from '@playwright/test';
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  // -----------------------------------------------------------------------
9
- // EXEC_IDX — unique numeric seed per minute-long execution window
9
+ // EXEC_IDX - unique numeric seed per minute-long execution window
10
10
  // -----------------------------------------------------------------------
11
11
  // Use this wherever test data needs to be unique (e.g., names, emails,
12
12
  // codes) to avoid collisions between parallel or consecutive test runs