@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.
- package/CHANGELOG.md +63 -18
- package/README.md +2 -2
- package/docs/architecture.md +30 -26
- package/docs/decisions/lane-heartbeat-caller.md +86 -0
- package/docs/final-report.md +23 -23
- package/docs/folder-structure-guide.md +106 -80
- package/docs/installation.md +35 -7
- package/docs/skills-architecture.md +23 -23
- package/docs/spec-driven-philosophy.md +9 -9
- package/docs/usage-with-agent.md +83 -9
- package/docs/visuals/diag-01-ejecutivo-1.png +0 -0
- package/docs/visuals/diag-02-tactico-1.png +0 -0
- package/docs/visuals/diag-03-tecnico-1.png +0 -0
- package/docs/visuals/info-01-ejecutivo-1.png +0 -0
- package/docs/visuals/info-02-tactico-1.png +0 -0
- package/docs/visuals/info-03-tecnico-1.png +0 -0
- package/docs/visuals/pres-01-ejecutivo-1.png +0 -0
- package/docs/visuals/pres-02-tactico-1.png +0 -0
- package/docs/visuals/pres-03-tecnico-1.png +0 -0
- package/docs/visuals/qa-01-problema-1.png +0 -0
- package/docs/visuals/qa-02-que-es-1.png +0 -0
- package/docs/visuals/qa-03-pipeline-1.png +0 -0
- package/docs/visuals/qa-04-6-archivos-1.png +0 -0
- package/docs/visuals/qa-05-humano-1.png +0 -0
- package/docs/visuals/qa-06-agente-1.png +0 -0
- package/docs/visuals/qa-07-skills-arch-1.png +0 -0
- package/docs/visuals/qa-08-traceability-1.png +0 -0
- package/docs/visuals/qa-en-01-problem-1.png +0 -0
- package/docs/visuals/qa-en-02-what-is-1.png +0 -0
- package/docs/visuals/qa-en-03-pipeline-1.png +0 -0
- package/docs/visuals/qa-en-04-6files-1.png +0 -0
- package/docs/visuals/qa-en-05-human-1.png +0 -0
- package/docs/visuals/qa-en-06-agent-1.png +0 -0
- package/docs/visuals/qa-en-07-skills-arch-1.png +0 -0
- package/docs/visuals/qa-en-08-traceability-1.png +0 -0
- package/examples/module-example/suppliers/00-inventory.md +1 -1
- package/examples/module-example/suppliers/suppliers-create.spec.ts +8 -8
- package/integrations/ado-powershell/README.md +1 -1
- package/integrations/ado-powershell/pipelines/azure-pipeline-qa.yml +92 -25
- package/integrations/ado-powershell/scripts/sync-ado-titles.ps1 +2 -2
- package/integrations/playwright/README.md +128 -4
- package/package.json +9 -6
- package/qa-framework.config.json +24 -3
- package/scripts/check-forbidden-chars.js +187 -0
- package/scripts/check-spanish-accents.js +159 -0
- package/scripts/cli.js +1 -1
- package/scripts/generate.js +2 -2
- package/scripts/init.js +108 -45
- package/scripts/lib/claude-agents.js +78 -0
- package/scripts/lib/claude-commands.js +56 -0
- package/scripts/sync-version.js +36 -0
- package/scripts/upgrade.js +461 -30
- package/scripts/validate.js +17 -17
- package/skills/qa-ado-integration/SKILL.md +19 -19
- package/skills/qa-ado-integration/references/scripts-and-config.md +6 -6
- package/skills/qa-automation/SKILL.md +49 -27
- package/skills/qa-automation/references/config-checklist.md +4 -4
- package/skills/qa-automation/references/dom-inspection-template.js +7 -7
- package/skills/qa-automation/references/patterns.md +20 -20
- package/skills/qa-automation/references/pom-template.md +161 -0
- package/skills/qa-maintenance/SKILL.md +11 -11
- package/skills/qa-maintenance/references/update-rules.md +8 -8
- package/skills/qa-module-analysis/SKILL.md +20 -19
- package/skills/qa-module-analysis/references/exploration-checklist.md +4 -4
- package/skills/qa-module-analysis/references/output-verification.md +69 -0
- package/skills/qa-module-analysis/references/spec-file-formats.md +14 -14
- package/skills/qa-spec-generation/SKILL.md +13 -7
- package/skills/qa-test-cases/SKILL.md +11 -81
- package/skills/qa-test-cases/references/test-case-template.md +5 -5
- package/skills/qa-test-plan/SKILL.md +22 -86
- package/skills/qa-test-plan/references/plan-de-pruebas-template.md +24 -24
- package/skills/qa-test-stabilization/SKILL.md +59 -30
- package/skills/qa-test-stabilization/references/classification-protocol.md +35 -35
- package/templates/agent-next-steps.md +10 -10
- package/templates/agents/qa-analisis.md +142 -0
- package/templates/agents/qa-asesoria.md +48 -0
- package/templates/agents/qa-informe-resultados.md +234 -0
- package/templates/agents/qa-plan.md +131 -0
- package/templates/automation-scaffold/.env.example +52 -3
- package/templates/automation-scaffold/fixtures/auth.ts +27 -5
- package/templates/automation-scaffold/fixtures/base.ts +130 -0
- package/templates/automation-scaffold/fixtures/test-helpers.ts +1 -1
- package/templates/automation-scaffold/global-setup.ts +196 -37
- package/templates/automation-scaffold/playwright.config.ts +74 -10
- package/templates/automation-scaffold/scripts/global-setup-guards.js +184 -0
- package/templates/automation-scaffold/scripts/lane-config.js +269 -0
- package/templates/automation-scaffold/scripts/lane-lock.js +589 -0
- package/templates/defect-report.md +18 -4
- package/templates/execution-report.md +9 -9
- package/templates/integration-scaffold/.env.example +40 -0
- package/templates/integration-scaffold/README.md +77 -0
- package/templates/integration-scaffold/global-setup.ts +119 -0
- package/templates/integration-scaffold/package.json +15 -0
- package/templates/integration-scaffold/playwright.config.ts +92 -0
- package/templates/integration-scaffold/tests/example.spec.ts +43 -0
- package/templates/memory/ci-pipeline-findings.md +36 -0
- package/templates/memory/data-volatility-strategies.md +70 -0
- package/templates/memory/e2e-stabilization-patterns.md +54 -0
- package/templates/qa-framework.instructions.md +16 -7
- package/templates/qa-framework.rules.md +53 -0
- package/templates/qa-readme.md +26 -5
- package/templates/session-summary.md +2 -2
- package/templates/specification/00-inventory.md +3 -3
- package/templates/specification/01-business-rules.md +6 -6
- package/templates/specification/02-workflows.md +1 -1
- package/templates/specification/03-roles-permissions.md +1 -1
- package/templates/specification/04-test-data.md +8 -8
- package/templates/specification/05-test-scenarios.md +5 -5
- package/templates/test-case.md +3 -3
- package/templates/test-data-guidelines.md +7 -7
- package/templates/test-plan-sprint.md +13 -13
- package/templates/test-plan.md +3 -3
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|