Pruebas E2E en Next.js: guía práctica de automatización con Playwright

En el ticket hay una etiqueta roja de «urgente»: el flujo de pago ha vuelto a fallar. En staging todo iba bien; en producción no.
La semana pasada, antes del release, probé a mano más de treinta páginas, rellené una docena de formularios y alterné entre tres navegadores — y aun así se me escapó el botón que solo aparece al hacer scroll hasta el final en móvil. Saltó el mensaje del product manager: «Los usuarios dicen que no pueden usar el cupón».
No se puede seguir probando a mano. Las pruebas manuales acaban agotando a ti y al equipo.
Miré Cypress, Selenium, Puppeteer, Playwright… al final elegí Playwright: multi-navegador y configuración más sencilla que Cypress. La primera semana pilló cinco bugs que las pruebas manuales nunca habían visto — algunos de estilo solo en Firefox, otros por condiciones de carrera en APIs async.
Al principio tropecé con bastantes cosas: reescribí la config varias veces y los casos dos veces. Ahora el flujo va fino, CI/CD automático en cada commit y muchos menos bugs en producción.
Por qué Playwright (frente a Cypress)
Quien haya usado Cypress sabe que es fácil de configurar, con buena documentación y comunidad activa. ¿Por qué no me quedé con él?
Tres puntos me frenaron:
Soporte multi-navegador débil. En Firefox y Safari Cypress siempre ha ido a medias; en la práctica casi todo corre en Chromium. Suena inocente hasta que te caes — una vez la página de pago iba perfecta en Chrome y en blanco en Safari por una propiedad CSS que Safari no soportaba. Playwright soporta de forma nativa Chromium, Firefox y WebKit; un mismo suite cubre los navegadores principales.
Velocidad. El paralelismo de Playwright es mucho mayor. Cypress ejecuta en serie: 50 casos pueden tardar más de diez minutos; con 8 workers en Playwright, cinco minutos. En CI cada minuto cuenta.
Diseño de la API. Al pasar de Cypress a Playwright me costó un poco — la cadena de Cypress escribe muy bien. Tras acostumbrarme al async/await de Playwright, encaja mejor con JavaScript moderno y con el estilo de Server Components en Next.js.
Cypress no está mal. Si solo necesitas Chrome y el equipo es nuevo en testing, es más fácil de arrancar. Sus herramientas de depuración y el time travel son excelentes.
Para mi caso, Playwright encaja mejor:
- Necesito multi-navegador
- Ya tengo experiencia en Next.js/React con
async/await - El CI debe dar feedback rápido
- Quiero probar API routes y páginas SSR
No hay herramienta perfecta; depende del contexto. Proyecto pequeño y poca experiencia: Cypress para arrancar rápido. Proyecto con cierta escala e inversión a largo plazo en automatización: Playwright.
Configuración práctica Next.js + Playwright
Instalar Playwright es muy simple, tres comandos:
npm init playwright@latest
# o con pnpm
pnpm create playwright
Durante la instalación te preguntará varias cosas; mi recomendación:
- ¿TypeScript? Yes (muy recomendable; los tipos evitan errores)
- ¿Directorio de tests? tests (el predeterminado está bien)
- ¿GitHub Actions? Yes (lo usarás en CI/CD)
Después tendrás algo así:
your-nextjs-project/
├── tests/ # directorio de casos de prueba
│ └── example.spec.ts
├── playwright.config.ts # configuración de Playwright
└── .github/
└── workflows/
└── playwright.yml # configuración CI
Lecciones de la configuración
El playwright.config.ts inicial sirve para webs genéricas; en Next.js conviene ajustarlo. Esta es la config que me ha ido mejor tras medio año:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// 测试目录
testDir: './tests',
// 全局超时:单个测试 30 秒
timeout: 30 * 1000,
// 全局期望超时:元素查找 5 秒
expect: {
timeout: 5000,
},
// 失败时重试次数(CI 环境建议开启)
retries: process.env.CI ? 2 : 0,
// 并行 worker 数量(我的机器是 8 核,所以设 4)
workers: process.env.CI ? 2 : 4,
// 测试报告
reporter: [
['html'], // 生成 HTML 报告
['list'], // 终端输出列表
process.env.CI ? ['github'] : ['list'], // CI 环境用 GitHub 格式
],
// 启动 Next.js 开发服务器
webServer: {
command: 'npm run dev',
port: 3000,
timeout: 120 * 1000, // Next.js 首次启动可能要编译,给足时间
reuseExistingServer: !process.env.CI, // 本地开发复用服务器,省时间
},
// 测试项目(多浏览器配置)
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// 移动端测试(可选)
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
],
// 全局配置
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry', // 失败时记录 trace,方便调试
screenshot: 'only-on-failure', // 失败时截图
video: 'retain-on-failure', // 失败时录屏
},
});
Trampas frecuentes
-
webServer.timeoutdebe ser largo. Empecé con 30 segundos y Next.js en frío compilando hacía timeout. Con 120 segundos va estable. -
En local,
reuseExistingServer: true. Si no, cada ejecución reinicia Next.js y es insoportable. -
No abuses de
workers. Lo puse al número de núcleos y el equipo se colgaba. Ahora uso la mitad: rápido y estable. -
Pruebas móviles opcionales. Si tu Next.js es responsive, Mobile Chrome detecta bugs propios de móvil, pero duplica el tiempo. Según necesidad.
Configurado, ejecuta el ejemplo:
npx playwright test
Si ves passed en verde, el entorno está listo. Ya puedes escribir casos reales.
Mejores prácticas de interacción con Page Object Model
Al principio metí todo en un solo archivo. Un login de más de 100 líneas con page.locator, page.fill y page.click por todas partes. Al cambiar un selector del botón, tuve que tocar una docena de archivos — desesperante.
Luego aprendí Page Object Model (POM): encapsulas las operaciones de página en una clase; los tests solo llaman métodos.
Sin POM (mal ejemplo)
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('用户登录', async ({ page }) => {
await page.goto('/login');
// 直接操作元素,代码重复
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('input[name="password"]').fill('password123');
await page.locator('button[type="submit"]').click();
await expect(page.locator('h1')).toContainText('Dashboard');
});
test('登录失败提示', async ({ page }) => {
await page.goto('/login');
// 又来一遍同样的操作...
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('input[name="password"]').fill('wrongpass');
await page.locator('button[type="submit"]').click();
await expect(page.locator('.error')).toBeVisible();
});
¿El problema? Si el diseñador cambia input[name="email"] por input[id="email"], hay que tocar todos los tests.
Con POM (recomendado)
Primero el Page Object:
// tests/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
readonly dashboardTitle: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.locator('input[name="email"]');
this.passwordInput = page.locator('input[name="password"]');
this.submitButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('.error');
this.dashboardTitle = page.locator('h1');
}
// 封装登录操作
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
// 封装导航操作
async goto() {
await this.page.goto('/login');
}
// 封装验证逻辑
async expectLoginSuccess() {
await this.dashboardTitle.waitFor();
await expect(this.dashboardTitle).toContainText('Dashboard');
}
async expectLoginError() {
await expect(this.errorMessage).toBeVisible();
}
}
Los casos quedan muy limpios:
// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
test('用户登录', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'password123');
await loginPage.expectLoginSuccess();
});
test('登录失败提示', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'wrongpass');
await loginPage.expectLoginError();
});
¿Mejor, no? Un selector, un solo archivo LoginPage.ts. Los tests se leen casi como lenguaje natural.
Estructura de directorios en un proyecto real
tests/
├── pages/ # Page Objects
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ └── CheckoutPage.ts
├── fixtures/ # datos y utilidades
│ └── testData.ts
├── auth.spec.ts # pruebas de autenticación
├── checkout.spec.ts # flujo de pago
└── dashboard.spec.ts # pruebas del dashboard
Errores que cometí y consejos
-
No sobre-encapsules. No toda página necesita Page Object. Si solo la pruebas una vez, escribe en el test directamente.
-
Nombres semánticos.
async fillLoginForm()aclara más queasync fillForm(). Tu yo del futuro te lo agradecerá. -
Esperas dentro del Page Object. Playwright espera bien solo, pero a veces hace falta
waitFor(). Ocúltalo en el Page Object. -
Datos de prueba aparte. Usuario y contraseña en
fixtures/testData.ts:
// tests/fixtures/testData.ts
export const testUsers = {
validUser: {
email: '[email protected]',
password: 'password123'
},
invalidUser: {
email: '[email protected]',
password: 'wrongpass'
}
};
Uso en tests:
import { testUsers } from './fixtures/testData';
await loginPage.login(testUsers.validUser.email, testUsers.validUser.password);
Con este patrón el mantenimiento baja mucho. Mi flujo: definir Page Object → unas líneas de test → listo.
Pruebas E2E de API routes
Las API Routes de Next.js son parte de la app; también hay que probarlas. Antes usaba Postman a mano, agotador. Ahora en Playwright, sin abrir navegador.
El objeto request envía HTTP directamente, ideal para API Routes.
Prueba básica de API
Ejemplo: listado de usuarios:
// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';
test.describe('用户 API 测试', () => {
test('GET /api/users - 获取用户列表', async ({ request }) => {
const response = await request.get('/api/users');
// 验证状态码
expect(response.status()).toBe(200);
// 验证响应格式
const users = await response.json();
expect(Array.isArray(users)).toBeTruthy();
expect(users.length).toBeGreaterThan(0);
// 验证数据结构
expect(users[0]).toHaveProperty('id');
expect(users[0]).toHaveProperty('email');
expect(users[0]).toHaveProperty('name');
});
test('POST /api/users - 创建用户', async ({ request }) => {
const newUser = {
email: '[email protected]',
name: 'Test User',
password: 'password123'
};
const response = await request.post('/api/users', {
data: newUser
});
expect(response.status()).toBe(201);
const createdUser = await response.json();
expect(createdUser.email).toBe(newUser.email);
expect(createdUser).not.toHaveProperty('password'); // 密码不应该返回
});
test('POST /api/users - 邮箱重复应返回错误', async ({ request }) => {
const duplicateUser = {
email: '[email protected]',
name: 'Duplicate User',
password: 'password123'
};
const response = await request.post('/api/users', {
data: duplicateUser
});
expect(response.status()).toBe(400);
const error = await response.json();
expect(error.message).toContain('邮箱已存在');
});
});
API con autenticación
Muchas rutas requieren login. Primero obtén el token y envíalo en cabecera:
// tests/api/auth.spec.ts
import { test, expect } from '@playwright/test';
let authToken: string;
test.describe('需要认证的 API', () => {
// 所有测试前先登录获取 token
test.beforeAll(async ({ request }) => {
const response = await request.post('/api/auth/login', {
data: {
email: '[email protected]',
password: 'password123'
}
});
const { token } = await response.json();
authToken = token;
});
test('GET /api/profile - 获取用户资料', async ({ request }) => {
const response = await request.get('/api/profile', {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
expect(response.status()).toBe(200);
const profile = await response.json();
expect(profile.email).toBe('[email protected]');
});
test('未登录访问应返回 401', async ({ request }) => {
const response = await request.get('/api/profile');
expect(response.status()).toBe(401);
});
});
Prueba mixta: página + API
Lo más potente es combinar ambos. Publicar un artículo:
// tests/posts.spec.ts
import { test, expect } from '@playwright/test';
test('发布文章完整流程', async ({ page, request }) => {
// 1. 先通过页面登录
await page.goto('/login');
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// 2. 进入文章编辑页
await page.goto('/posts/new');
await page.fill('input[name="title"]', '测试文章标题');
await page.fill('textarea[name="content"]', '这是测试内容');
await page.click('button:has-text("发布")');
// 3. 等待跳转到文章详情页
await page.waitForURL(/\/posts\/\d+/);
// 4. 通过 API 验证文章确实创建成功
const url = page.url();
const postId = url.split('/').pop();
const response = await request.get(`/api/posts/${postId}`);
expect(response.status()).toBe(200);
const post = await response.json();
expect(post.title).toBe('测试文章标题');
expect(post.content).toBe('这是测试内容');
expect(post.status).toBe('published');
});
Ventaja: validas UI y datos. Una vez encontré un bug sutil — pantalla de «publicado» pero en BD el estado era draft; la lógica de actualización estaba mal.
Recomendaciones prácticas
-
Cubre casos límite en API. Falta de parámetros, tipos incorrectos, permisos insuficientes.
-
Limpia datos de prueba. Las APIs escriben en BD; limpia en
afterAllo usa BD dedicada:
test.afterAll(async ({ request }) => {
await request.delete('/api/test/cleanup');
});
-
Mock de servicios externos. Pago, SMS, etc. — mock en test o pagas en cada ejecución.
-
Tiempo de respuesta. Puedes medir duración:
const start = Date.now();
await request.get('/api/users');
const duration = Date.now() - start;
expect(duration).toBeLessThan(1000); // 接口响应应该在 1 秒内
API + UI bien combinadas cubren ~90% de escenarios; el resto, unitarias.
Integración CI/CD con GitHub Actions
Tests escritos, siguiente paso: CI/CD. Cada push que ejecute pruebas salva releases — cuántas veces creí que iba bien y CI demostró que rompí otra cosa.
npm init playwright ya genera el workflow de GitHub Actions. La plantilla es básica; conviene ajustarla.
Configuración CI básica
Plantilla inicial .github/workflows/playwright.yml:
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Funciona, pero:
- Reinstala navegadores cada vez — lento
- Sin BD de prueba — fallan tests de API
- Informe solo descargable — poco cómodo
Configuración de producción
La que uso en la práctica, con caché, BD e informes:
name: E2E Tests
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
services:
# 测试数据库(PostgreSQL)
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
NODE_ENV: test
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run database migrations
run: npm run db:migrate
- name: Run Playwright tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30
# 如果是主分支,把报告部署到 GitHub Pages
- name: Deploy report to GitHub Pages
if: always() && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./playwright-report
Puntos clave
-
services: PostgreSQL para tests de API. MySQL →
mysql:8. -
Caché de navegadores:
actions/cacheen~/.cache/ms-playwright. Solochromiumen CI suele bastar. -
Migraciones:
npm run db:migrateantes de los tests. Enpackage.json:
{
"scripts": {
"db:migrate": "prisma migrate deploy"
}
}
- Informe en GitHub Pages: en
main, el equipo ve resultados online.
Variables de entorno
Secrets en Settings → Secrets:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_TEST_KEY }}
Depurar fallos en CI
-
Trace: con
trace: 'on-first-retry', descarga ynpx playwright show-trace trace.zip. -
Capturas y vídeo en fallos.
-
Reproducir CI en local con
act:
# 安装 act
brew install act # macOS
# 或
choco install act # Windows
# 运行 workflow
act -j test
Errores que cometí
-
Timeout razonable. 30 minutos con un test colgado desperdicia CI. Uso 60 minutos y vigilo tests lentos.
-
Pocos workers en CI. Con 2 workers va bien.
-
Pocos reintentos.
retries: 2para red inestable; si el test está mal, reintentar no ayuda.
Con CI/CD, la calidad sube. Cada PR necesita el check verde para merge.
Cobertura e informes
Tras ejecutar tests, lo importante es el resultado. El informe HTML de Playwright es muy completo.
Informe HTML
npx playwright show-report
Abre una web local con:
- Estado pass/fail por test
- Duración
- Capturas y vídeo en fallos
- Trace (reproducir paso a paso)
Trace Viewer es mi favorito: red, DOM, consola — como una máquina del tiempo para localizar el fallo.
Cobertura funcional
En E2E no es cobertura de código sino de funcionalidad. Mantengo una checklist:
## 测试覆盖清单
### 用户认证
- [x] 登录(正常流程)
- [x] 登录失败(错误密码)
- [x] 注册
- [x] 找回密码
- [ ] 第三方登录(Google)
### 商品管理
- [x] 添加商品
- [x] 编辑商品
- [x] 删除商品
- [ ] 批量导入
### 订单流程
- [x] 加购物车
- [x] 结算
- [x] 支付(模拟环境)
- [ ] 退款流程
En tests/README.md, actualizada con cada feature nueva.
Cobertura de código (opcional)
Con Istanbul o v8:
// playwright.config.ts
export default defineConfig({
use: {
// 启用代码覆盖率
trace: 'on',
// 注入覆盖率收集代码
contextOptions: {
recordVideo: {
dir: 'test-results/videos'
}
}
}
});
En E2E casi no miro cobertura de código; las unitarias cubren lógica y E2E los flujos.
Informes personalizados
Para Slack u otras notificaciones:
// my-reporter.ts
import { Reporter } from '@playwright/test/reporter';
class SlackReporter implements Reporter {
onEnd(result) {
const passed = result.suites.filter(s => s.ok).length;
const failed = result.suites.length - passed;
// 发送到 Slack
fetch('https://hooks.slack.com/services/YOUR_WEBHOOK', {
method: 'POST',
body: JSON.stringify({
text: `测试完成:${passed} 通过,${failed} 失败`
})
});
}
}
export default SlackReporter;
En la config:
// playwright.config.ts
export default defineConfig({
reporter: [
['html'],
['./my-reporter.ts']
]
});
Tendencias históricas
Playwright Test Runner visualiza traces subidos. Yo prefiero anotar pass rate y duración en CSV y graficar en Google Sheets — simple y útil.
Mi rutina con informes
- Desarrollo local: salida en terminal;
--debugsi falla:
npx playwright test --debug
-
Revisión de PR: informe HTML de CI; atención a fallos y timeouts recurrentes.
-
Revisión semanal: checklist de cobertura y casos que faltan.
Los informes no son solo números; ayudan a mejorar el proceso.
Conclusión
Hace medio año probaba a mano hasta las tres de la mañana; ahora es otro mundo.
Playwright + Next.js se siente tranquilo. Configuras una vez y sigue. Cada commit pasa por CI; cada release, más confianza. Menos bugs y menos llamadas urgentes del product manager.
Si tu Next.js sigue en pruebas manuales:
- Empieza por flujos core. Login, pago — no hace falta cubrir todo el primer día.
- Usa Page Object Model. Al principio parece extra; a largo plazo ahorra.
- Integra CI/CD. Tests que no se ejecutan solos, mejor no escribirlos.
- No persigas el 100%. Lo crítico bien probado.
Las pruebas E2E no son solo herramienta técnica: alinean al equipo en calidad, reducen fricción (los tests documentan) y hacen predecibles los releases.
Ahora salgo a las cinco y media. El tiempo que ahorré vuelve al gimnasio — la tarjeta anual casi caducaba.
Empieza a escribir tests; tu yo del futuro te lo agradecerá.
FAQ
¿Playwright o Cypress? ¿Cuál elegir?
• Playwright: pruebas multi-navegador (Chromium/Firefox/WebKit), paralelismo rápido (8 workers ~3× más rápido que Cypress), sintaxis async/await, ideal para proyectos medianos y grandes
• Cypress: orientado a Chrome, depuración con time travel muy potente, comunidad amplia, más amigable para principiantes
Si solo pruebas Chrome y el equipo tiene poca experiencia en testing, Cypress; si necesitas multi-navegador, CI rápido y experiencia con React/Next.js, Playwright.
¿Es obligatorio usar Page Object Model?
En proyectos pequeños (menos de 10 casos) o páginas que solo pruebas una vez, puedes escribir directamente en el test. Pero si:
• Varios casos operan la misma página
• Varios miembros del equipo mantienen el código de pruebas
• El proyecto evolucionará a largo plazo
POM te evitará muchos problemas. Cuando cambies un selector una vez verás su valor: sin POM hay que tocar 10+ archivos; con POM, solo uno.
¿Qué hacer si las pruebas en CI siempre hacen timeout?
• webServer.timeout demasiado corto: súbelo a 120 segundos (Next.js en frío necesita compilar)
• Demasiados workers: en CI suele bastar con 2-4
• Problema en el propio test: revisa el trace para ver si es red lenta o timeout de elementos
• Instalación lenta del navegador: usa actions/cache para cachear los binarios
Truco: en CI solo chromium; en local pruebas multi-navegador. Ahorra mucho tiempo.
¿Cómo gestionar datos de prueba? ¿Hay que limpiar la base de datos a mano cada vez?
• Base de datos de prueba dedicada: solo para tests, se vacía periódicamente, sin afectar desarrollo
• Limpieza tras cada test: llamar a una API de limpieza en test.afterAll(), pero puedes olvidar casos
• Contenedor Docker: base de datos nueva por ejecución, se destruye al terminar (más limpio pero más lento)
Yo combino el primero y el segundo: Docker en CI, base de prueba local + afterAll.
¿Hay que hacer mock de servicios de terceros en pruebas de API?
• Coste: llamadas reales a pago/SMS cobran en cada ejecución
• Velocidad: respuestas lentas de terceros ralentizan los tests
• Estabilidad: si cae un servicio externo no debería tumbar tus pruebas
Playwright permite interceptar red y simular respuestas:
await page.route('**/api/payment', route => route.fulfill({ status: 200, body: '{"success": true}' }));
También puedes devolver datos simulados en API Routes de Next.js según variables de entorno de test.
¿Qué cobertura de pruebas se considera aceptable?
Prioridad:
• Funciones core (login, pago, pedido): 100%
• Funciones frecuentes (ver productos, carrito): más del 80%
• Funciones poco frecuentes (recuperar contraseña, reembolso): más del 50%
• Funciones marginales (tema, idioma): opcional
No persigas el 100% global. En mi proyecto el flujo core está al 100% y la cobertura funcional global al 60%; ya bloquea el 90% de los bugs.
Tras escribir pruebas Playwright, ¿hace falta pruebas unitarias?
• E2E (Playwright): flujos, interacción de usuario, integración front/back; lentas pero completas
• Unitarias (Jest/Vitest): lógica de funciones, bordes, errores; rápidas pero locales
Proporción ideal: 70% unitarias, 30% E2E. Unitarias para utilidades, hooks y lógica de componentes; E2E para flujos de usuario completos.
Las unitarias localizan el fallo; las E2E confirman que la función realmente funciona. Hay que usar ambas.
15 min de lectura · Publicado el: 7 ene 2026 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Pruebas unitarias en Next.js: guía completa de configuración con Jest + React Testing Library
Configura el entorno de pruebas de Next.js 15 desde cero: configuración de Jest + React Testing Library, pruebas de Client/Server Components, hooks, técnicas de mock y resolución de problemas frecuentes, con ejemplos de código completos.
Parte 35 de 51
Siguiente
Next.js e-commerce en la práctica: guía completa de carrito y pago con Stripe
Guía paso a paso para construir un carrito de compras y sistema de pago con Zustand + Stripe: gestión de estado, creación de Checkout Session, procesamiento de pedidos con Webhook y ejemplos de código listos para usar
Parte 37 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario