Revisión de Tests Playwright

Auditoría automatizada de anti-patrones · AgroFlow SaaS

AgroFlow
12 de junio 2026 · CULTIVA IA

Resumen Ejecutivo

4.2
Score medio / 10
24
Críticos
11
Advertencias
4
Archivos auditados
⚠️
La suite tiene calidad insuficiente para producción. El patrón dominante es waitForTimeout() (12 ocurrencias) seguido de selectores CSS frágiles (8 ocurrencias) y URLs hardcodeadas (7 ocurrencias). Estos tres problemas explican los ~18 min de CI y las pruebas intermitentes. Tiempo estimado de corrección: 3–4 horas aplicando los fixes listados.
Calidad global (4.2/10)

Puntuación por archivo

Archivo Score Veredicto Issues
auth.spec.ts 2 Necesita reescritura
9 críticos 2 advertencias
dashboard.spec.ts 3 Problemas significativos
8 críticos 3 advertencias 2 sugerencias
parcelas.spec.ts 4 Anti-patrones presentes
7 críticos 4 advertencias 2 sugerencias
fitosanitarios.spec.ts 7 Bueno, mejoras menores
2 advertencias 3 sugerencias
Revisión detallada por archivo
2
auth.spec.ts
Login · Registro · Recuperación de contraseña
9 críticos 2 advertencias
🚨 Críticos
  • Líneas 12, 22, 27, 32 — Anti-patrón #1
    waitForTimeout() usado 4 veces · Causa: tests lentos y flaky en CI
    Reemplazar por await expect(locator).toBeVisible() o toHaveURL()
  • Líneas 7, 20 — Anti-patrón #6
    Variable authToken compartida entre tests · Los tests no son independientes
    Mover setup de usuario a test.beforeEach o fixture compartido
  • Líneas 9, 22, 28 — Anti-patrón #3
    URLs hardcodeadas: 'http://localhost:3000/login' · Rompen en entornos staging/prod
    Usar rutas relativas: page.goto('/login') (baseURL definida en playwright.config.ts)
  • Líneas 10–11, 18–19 — Anti-patrón #4
    Selectores CSS/ID frágiles: #email-input, .btn-primary · Rompen con refactors de CSS
    page.getByLabel('Email').fill(...) · page.getByRole('button', { name: 'Entrar' })
  • Líneas 13, 23 — Anti-patrón #2
    expect(await page.textContent(...)) — aserción no-web-first · Comprueba una vez, no reintenta
    await expect(page.getByText('Credenciales incorrectas')).toBeVisible()
  • Línea 14 — Anti-patrón #11
    page.evaluate() para leer localStorage · Código frágil y acoplado a implementación
    Usar fixture de autenticación de Playwright con storageState
  • Línea 34 — Anti-patrón #5
    page.click('.submit-btn') sin await · Race condition garantizada
    Añadir await: await page.getByRole('button', { name: 'Guardar' }).click()
  • Tests "step 1" y "step 2" — Anti-patrón #7
    Dependencia de orden de ejecución entre tests de recuperación de contraseña
    Consolidar en un único test 'should complete password recovery flow'
  • Tests líneas 6, 20 — Anti-patrón #13
    Nombres genéricos: 'login test', 'should fail with bad password'
    'should redirect to dashboard after successful login'
⚠️ Advertencias
  • Global — Anti-patrón #10
    No hay test de cuenta bloqueada ni MFA requerido — casos de error críticos para auth
  • Global — Anti-patrón #9
    Credenciales de prueba dispersas como magic strings: extraer a constante TEST_USER
3
dashboard.spec.ts
Vista principal · Métricas · Notificaciones
8 críticos 3 advertencias 2 sugerencias
🚨 Críticos
  • Líneas 8, 16, 21, 23, 27 — Anti-patrón #1
    waitForTimeout() x5 — el test más lento de la suite
    Esperar carga con await expect(page.getByRole('main')).toBeVisible()
  • Líneas 7, 14 — Anti-patrón #3
    URLs hardcodeadas: 'http://localhost:3000/dashboard'
    await page.goto('/dashboard')
  • Líneas 9, 17–19 — Anti-patrón #11
    page.evaluate() para contar elementos DOM y leer texto de métricas
    await expect(page.getByTestId('metric-hectareas')).toContainText(/\d/)
  • Líneas 7, 20, 26 — Anti-patrón #4
    Selectores CSS: .welcome-card, .refresh-btn, .nav-link[href=...]
    page.getByRole('link', { name: 'Parcelas' })
  • Línea 31 — Anti-patrón #19
    page.waitForNetworkIdle() — espera arbitraria, falsa sensación de estabilidad
    Esperar elemento concreto: await expect(page.getByRole('list', { name: 'notificaciones' })).toBeVisible()
  • Líneas 5, 12 — Anti-patrón #13
    Nombres genéricos: 'should work', 'metrics panel test'
    'should display welcome card after login' · 'should show hectareas and alerts metrics'
⚠️ Advertencias
  • Global — Anti-patrón #14
    Dashboard tiene 10+ locators sin Page Object — mantenimiento costoso
  • Global — Anti-patrón #18
    No se verifican errores de consola — fallos silenciosos de JS no detectados
  • Global — Anti-patrón #20
    No hay test.describe() agrupando los tests del dashboard
4
parcelas.spec.ts
CRUD de parcelas agrícolas
7 críticos 4 advertencias 2 sugerencias
🚨 Críticos
  • Líneas 16, 26, 31, 33, 36 — Anti-patrón #1
    waitForTimeout() x5 — incluyendo waits encadenados en el flujo de borrado
    Después de .click() del modal, esperar expect(dialog).not.toBeVisible()
  • Línea 24 — Anti-patrón #3
    URL hardcodeada con ID: 'http://localhost:3000/parcelas/123/editar'
    Crear parcela vía API en beforeEach y usar su ID dinámico
  • Líneas 25–26, 31–33 — Anti-patrón #4
    Selectores CSS: input[name="nombre"], .save-button, .parcela-row:first-child .delete-btn
    Usar getByLabel, getByRole('button'), getByRole('row').first()
  • Línea 35 — sin aserción post-delete
    Test de borrado sin verificar que la parcela desapareció de la lista
    await expect(page.getByRole('cell', { name: parcelaName })).not.toBeVisible()
  • Líneas 3–6 — Anti-patrón #12
    Anidamiento triple de test.describe() — dificulta encontrar el test en el output
    Máximo 2 niveles: describe('Parcelas') > test('should create...')
⚠️ Advertencias
  • Test 'crear parcela' — Anti-patrón #10
    No hay test de validación: ¿qué pasa con hectáreas negativas o cultivo no permitido?
  • Global — Anti-patrón #15
    Datos de parcela inlined en cada test — extraer a factory createParcelaData()
7
fitosanitarios.spec.ts
Gestión de productos fitosanitarios · Mejor archivo de la suite
2 advertencias 3 sugerencias
⚠️ Advertencias
  • Global — Anti-patrón #16
    No hay aserciones de accesibilidad — crítico para SaaS con posibles usuarios con discapacidad
    Añadir await expect(page).toHaveNoViolations() con @axe-core/playwright
  • Global — Anti-patrón #18
    Errores de consola no verificados — el formulario podría lanzar excepciones silenciosas
    Añadir en beforeEach: page.on('console', msg => { if (msg.type() === 'error') errors.push(msg) })
💡 Sugerencias
  • Anti-patrón #17
    Añadir visual regression test para la tabla de productos: await expect(page).toHaveScreenshot('fitosanitarios-table.png')
  • Anti-patrón #10
    Añadir test de producto caducado: verificar que aparece badge de alerta rojo
  • Anti-patrón #15
    Constante TEST_PRODUCT es buena práctica — extraerla a fixtures compartidos entre archivos

Plan de acción priorizado

35 fixes accionables
# Prioridad Anti-patrón Ocurrencias Impacto en CI Esfuerzo
1 P1 — Crítico Eliminar todos los waitForTimeout() 12 ocurrencias ↓ ~8 min CI 1 h
2 P1 — Crítico Reemplazar URLs hardcodeadas por rutas relativas 7 ocurrencias Desbloquea staging/prod 30 min
3 P1 — Crítico Migrar selectores CSS/ID a getByRole / getByLabel 8 ocurrencias Elimina flakiness por refactor 45 min
4 P2 — Alto Eliminar estado compartido entre tests (authToken) 2 ocurrencias Permite ejecución en paralelo 30 min
5 P2 — Alto Convertir aserciones no-web-first a expect(locator) 6 ocurrencias Elimina falsos negativos 30 min
6 P2 — Alto Refactorizar tests con dependencia de orden 1 flujo (2 tests) Seguro para ejecución paralela 20 min
7 P3 — Medio Crear Page Object para Dashboard (10+ locators) 1 archivo Mantenibilidad 45 min
8 P3 — Medio Añadir tests de casos de error en auth y parcelas 4 tests nuevos Cobertura de edge cases 1 h

Gaps de cobertura identificados

🔒
Permisos y roles Sin tests de acceso como técnico vs propietario. ¿Puede un técnico borrar parcelas?
📱
Responsive / móvil Cero tests con page.setViewportSize(). AgroFlow se usa frecuentemente en campo desde móvil.
🌐
API mocking Sin page.route() para simular errores de red en carga de parcelas o fitosanitarios.
Accesibilidad No integrado @axe-core/playwright. Solo fitosanitarios.spec.ts tiene comentario al respecto.
📋
Informes de riego Módulo sin ningún test E2E — cobertura cero para feature crítica.
📸
Visual regression Sin toHaveScreenshot() en ningún archivo. Los cambios de UI pasan desapercibidos.