Cuando un test de Playwright falla con net::ERR_CONNECTION_REFUSED, ese error te está diciendo exactamente qué capa del stack de red se rompió: TCP, lo que significa que nada estaba escuchando en ese puerto cuando el test intentó conectarse. Los cuatro pasos que sigue cada request son resolución DNS, conexión TCP, handshake TLS e intercambio HTTP, y cada capa tiene su propia firma de fallo. Este artículo cubre cada paso, los códigos de estado y los headers que aparecen en fallos reales de tests, cómo las cookies gestionan el estado de sesión en Playwright, y cómo leer el tráfico de red en el trace viewer.
Qué pasa cuando Playwright abre una página
Cuando tu test ejecuta await page.goto('https://lab.becomeqa.com'), ocurren cuatro cosas en secuencia:
1. Resolución DNS
Tu computadora le pregunta a un servidor DNS: "¿Cuál es la dirección IP de lab.becomeqa.com?"
El servidor DNS responde con algo como 104.21.8.42. Los nombres de dominio son alias legibles para humanos. La red real usa direcciones IP.
Qué falla aquí
ERR_NAME_NOT_RESOLVED significa que el DNS no encontró el dominio: el dominio no existe, tu servidor DNS está caído, o (en CI) el contenedor no puede acceder a DNS externo. En entornos CI, los hostnames internos como api.internal necesitan que la red de CI esté correctamente configurada para que DNS funcione.
2. Conexión TCP
Tu computadora abre una conexión TCP a esa dirección IP en un número de puerto: puerto 443 para HTTPS (cifrado), puerto 80 para HTTP (sin cifrar), y puertos como 3000 u 8080 para servidores de desarrollo.
Qué falla aquí
ERR_CONNECTION_REFUSED significa que nada está escuchando en ese puerto: el servidor no está corriendo o estás usando el puerto incorrecto. ERR_CONNECTION_TIMED_OUT indica que el servidor no es accesible (firewall, red incorrecta, servidor caído). En CI, esto suele significar que el servidor de la app no terminó de iniciar antes de que corriera el test.
3. Handshake TLS (solo HTTPS)
El navegador y el servidor intercambian certificados y negocian el cifrado. Esta es la "S" en HTTPS.
Qué falla aquí
ERR_CERT_AUTHORITY_INVALID indica que el certificado SSL es autofirmado o expiró, y es común en entornos de staging. ERR_SSL_PROTOCOL_ERROR indica una incompatibilidad de versión TLS o configuración incorrecta.
Playwright tiene ignoreHTTPSErrors: true en la config para saltear la validación de certificados en entornos de test:
// playwright.config.ts
use: {
ignoreHTTPSErrors: true, // para staging con certs autofirmados
}4. Request y respuesta HTTP
Ahora ocurre la transferencia real del contenido. El navegador envía un request; el servidor devuelve una respuesta.
Un request luce así (simplificado):
GET /dashboard HTTP/1.1
Host: lab.becomeqa.com
Cookie: session=abc123
Accept: text/htmlUna respuesta luce así:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4521
<!DOCTYPE html>...El código de estado te dice qué pasó.
Códigos de estado HTTP que vas a encontrar
| Código | Significado | Causa común |
|--------|-------------|-------------|
| 200 | OK | Todo funcionó |
| 201 | Created | POST exitoso, el recurso fue creado |
| 204 | No Content | Éxito sin cuerpo de respuesta (común en DELETE) |
| 301/302 | Redirect | La página se movió, el navegador sigue la redirección automáticamente |
| 400 | Bad Request | El request está mal formado: formato incorrecto, campo faltante |
| 401 | Unauthorized | Sin autenticación: hay que iniciar sesión |
| 403 | Forbidden | Autenticado pero sin permiso: rol o permisos incorrectos |
| 404 | Not Found | El recurso no existe en esa ruta |
| 422 | Unprocessable Entity | El formato es válido pero el contenido falla la validación |
| 429 | Too Many Requests | Rate limit alcanzado |
| 500 | Internal Server Error | Bug en el código del servidor |
| 502 | Bad Gateway | El servidor recibió una respuesta inválida de un servicio upstream |
| 503 | Service Unavailable | El servidor está caído o sobrecargado |
| 504 | Gateway Timeout | El servicio upstream tardó demasiado en responder |
Un 401 es un bug diferente a un 403. "No inició sesión" vs "inició sesión pero sin autorización" se ven igual en la UI (los dos redirigen al login o muestran un error), pero requieren soluciones diferentes.
Un 422 significa que el request llegó al servidor y el servidor lo entendió, pero los datos fallaron la validación. Un 400 significa que el servidor ni siquiera pudo parsear el request.
Métodos HTTP: qué significa cada uno
Cada request usa un método que describe la operación que se quiere realizar:
| Método | Uso | ¿Idempotente? |
|--------|-----|---------------|
| GET | Leer datos, obtener una página, cargar una lista | Sí. Llamarlo dos veces devuelve el mismo resultado |
| POST | Crear, enviar un formulario, agregar un registro | No. Llamarlo dos veces crea dos registros |
| PUT | Reemplazar, actualizar un recurso completo | Sí. Llamarlo dos veces tiene el mismo resultado |
| PATCH | Actualización parcial, cambiar un campo | Generalmente sí |
| DELETE | Eliminar un recurso | Sí. Eliminar dos veces tiene el mismo resultado |
Si tu test llama POST dos veces (por ejemplo, un clic en un botón se registra dos veces), deberías obtener dos registros en la base de datos. Si estás testeando la idempotencia de un endpoint PUT, llamarlo dos veces debe dejar el sistema en el mismo estado que llamarlo una vez.
Cuando aparece un bug donde "el formulario se envió dos veces", necesitas saber si fueron dos requests POST o uno. Eso es visible en la pestaña Network de DevTools.
Headers: metadatos en cada request y respuesta
Los headers transportan metadatos. No son el contenido principal, pero controlan cómo se procesan el request y la respuesta.
Headers de request que vas a ver en tests
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... ← token de autenticación
Content-Type: application/json ← "Estoy enviando JSON"
Cookie: session=abc123; csrf_token=xyz ← cookies de sesión
Accept: application/json ← "Quiero recibir JSON"Headers de respuesta que vas a ver
Content-Type: application/json; charset=utf-8 ← la respuesta es JSON
Set-Cookie: session=abc123; HttpOnly; Secure ← el servidor establece una cookie
Cache-Control: no-store ← no cachear esta respuesta
Location: /dashboard ← destino de redirección (con 302)En tests de API con Playwright
test('la API devuelve content-type JSON', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items');
expect(response.status()).toBe(200);
expect(response.headers()['content-type']).toContain('application/json');
});Cookies y sesiones: cómo funciona "estar logueado"
Cuando inicias sesión, el servidor crea una sesión y te devuelve una cookie:
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=StrictEl navegador almacena esta cookie y la envía con cada request siguiente:
Cookie: session_id=abc123El servidor lee la cookie, busca la sesión y sabe quién eres.
Por eso funciona el storageState de Playwright. Guarda las cookies de una sesión con login en un archivo, luego las carga en nuevos contextos del navegador. El servidor ve la misma cookie, asume que todavía estás logueado y saltea la autenticación.
// Guardar el estado de autenticación después del login
await page.context().storageState({ path: 'auth.json' });
// Cargarlo en los tests
use: {
storageState: 'auth.json'
}Cuando un test falla con "redirigido a la página de login", significa que la cookie de sesión expiró, no se guardó correctamente, o el archivo auth.json está desactualizado.
Leer el tráfico de red en Playwright
Playwright puede interceptar e inspeccionar cada request que hace tu test:
test('el login envía las credenciales correctas', async ({ page }) => {
const loginRequest = page.waitForRequest('**/api/auth/login');
await page.goto('/');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByLabel('Password').fill('testpass123');
await page.getByRole('button', { name: 'Submit' }).click();
const request = await loginRequest;
const body = request.postDataJSON();
expect(body.email).toBe('admin@becomeqa.com');
});Y respuestas:
test('la API de ítems devuelve la estructura correcta', async ({ page }) => {
const itemsResponse = page.waitForResponse('**/api/items');
await page.goto('/dashboard');
const response = await itemsResponse;
expect(response.status()).toBe(200);
const items = await response.json();
expect(Array.isArray(items)).toBeTruthy();
});Patrones comunes de fallo y qué significan
"net::ERR_CONNECTION_REFUSED" en CI pero no en local
El servidor de la app no está corriendo cuando arranca el test. Agrega una espera hasta que el servidor esté listo, o usa la config webServer en playwright.config.ts:
webServer: {
command: 'npm start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
}"Request failed: 401 Unauthorized"
La sesión expiró o el estado de autenticación no se cargó. Verificá que storageState está configurado, o que el setup global corre antes que los tests.
"Request failed: 429 Too Many Requests"
El test está alcanzando un rate limiter. Agregá demoras entre requests o deshabilitá el rate limiting en los entornos de test.
"Response timeout: 30000ms exceeded"
El servidor tardó más de 30 segundos en responder. Aumentá el timeout para esa operación específica, o investigá por qué el servidor es lento (consulta a base de datos, llamada a API externa, etc.).
El test pasa en local pero falla en CI con un error 500
Falta una variable de entorno en CI: URL de base de datos, clave de API, feature flag. El servidor lanza un error porque un valor de configuración requerido es undefined.
La pestaña Network en DevTools
Al depurar un fallo, abre Chrome DevTools → pestaña Network antes de ejecutar el flujo manualmente. Cada request aparece con su URL y método, el código de estado, los headers y cuerpo del request, los headers y cuerpo de la respuesta, y el timing.
Esto te dice exactamente qué envió el navegador y qué devolvió el servidor. Si la UI muestra un error pero la pestaña Network muestra un 200, el bug está en el JavaScript del frontend que interpreta la respuesta. Si la pestaña Network muestra un 500, el bug está en el backend.
El trace viewer de Playwright muestra la misma información para ejecuciones automatizadas: abre el trace, cambia a la pestaña Network, y verás cada request hecho durante el test.
Lo que todavía no necesitás saber
- WebSockets y Server-Sent Events: conexiones en tiempo real, relevantes solo para testear funcionalidades en tiempo real
- HTTP/2 y HTTP/3: versiones del protocolo; Playwright las maneja de forma transparente
- Gestión de certificados TLS: relevante para DevOps, no para el QA del día a día
- Configuración de load balancing y CDN: conocimiento de infraestructura para roles senior
- Detalles del flujo OAuth 2.0: útil cuando se testean funcionalidades de autenticación específicamente
FAQ
¿Necesito saber de redes para escribir tests de Playwright?No en profundidad. Pero entender el flujo de cuatro pasos (DNS, TCP, TLS, HTTP) te permite diagnosticar la clase de error en 30 segundos en lugar de 30 minutos. No necesitás implementar nada. Solo reconocer qué capa se rompió.
¿Debo mirar la pestaña Network o el mensaje de error del test cuando algo falla?Los dos, en ese orden. El mensaje de error del test te dice qué aserción falló. La pestaña Network (o el trace de Playwright) te dice por qué: qué devolvió realmente el servidor. Primero el mensaje de error, después la red.
¿Cuál es la diferencia entre un 401 y un 403?401 = no autenticado (no iniciaste sesión). 403 = autenticado pero sin autorización (iniciaste sesión, pero no tienes permiso para ese recurso). Los dos pueden producir un "no puedes acceder a esto" en la UI, pero son bugs distintos con soluciones distintas.
→ See also: API Testing con Playwright: Más Allá de la UI | SQL para QA: Las Consultas que Realmente Necesitas