
Cuando una prueba o un scraper de Playwright necesita autenticación, casi nunca hace falta iniciar sesión desde cero en cada ejecución. Un enfoque habitual es iniciar sesión una vez, guardar las cookies y el estado del navegador con storageState y cargar ese estado en ejecuciones posteriores para reutilizar la misma sesión. Tener un archivo storageState guardado no significa que el inicio de sesión vaya a funcionar para siempre. La sesión puede caducar, el servidor puede revocarla o puede no cargarse bien desde el principio.
Si está automatizando su propia cuenta y ya tiene la sesión iniciada en el navegador que usa a diario, hay otra opción: reutilizar esa sesión de navegador existente de forma directa. ego (lite) permite que los agentes de IA trabajen en un navegador que ya tiene el estado de inicio de sesión que necesitan, y mantiene cada tarea aislada en su propio Space. Si la tarea llega a un código de verificación, un login por QR u otro paso que le requiere a usted, puede tomar el control en el navegador visible en lugar de intentar automatizar el MFA.
Sea cual sea el enfoque, el objetivo es el mismo: asegurar que su automatización tiene de verdad una sesión autenticada válida. Una ejecución que de pronto vuelve a la página de inicio de sesión, una API que responde 401 o un elemento posterior al login que nunca aparece pueden parecer tres problemas distintos: navegación, permisos o un problema de selector, cuando los tres pueden apuntar a la misma sesión rota. En las secciones siguientes usaremos un montaje local repetible para mostrar cómo guardar, reutilizar, verificar y renovar el estado de autenticación en Playwright.
Cómo se ve un inicio de sesión roto en Playwright
En la ejecución del fixture, una sesión revocada produjo los tres síntomas a la vez. Tras borrar la sesión en el servidor, un contexto que reutilizaba el estado guardado fue redirigido a la página de inicio de sesión con un parámetro next que apuntaba de nuevo al panel, una petición de sondeo al endpoint JSON autenticado devolvió 401 y la espera a la tabla del panel agotó el tiempo tras los 3 segundos configurados.
En el login publico de the-internet, la misma clase de fallo es el rebote. Abrir /secure sin una sesion viva cae en /login con el flash rojo You must login to view the secure area. Esa es la comprobacion de URL del recuadro de abajo: el navegador esta en el formulario de login, no en la pagina que esperaba el locator. Esta captura es ese rebote. No prueba que pulsar Logout borro un archivo storageState. Logout en the-internet cierra la sesion de la ventana actual; un archivo ya escrito en disco puede seguir abriendo /secure hasta que la cookie este muerta.

El tercer síntoma es el caro. Que un locator haga timeout en un elemento posterior al login no dice que el locator esté mal; dice que la página que está viendo no es la que daba por hecha. En la ejecución anterior la tabla nunca se renderizó porque el navegador estaba en el formulario de inicio de sesión. Tratarlo como un problema de selector lleva a parchear una consulta que nunca estuvo rota.
Cuatro causas de raíz detrás de los fallos de autenticación
Los fallos de autenticación se agrupan en cuatro causas, y cada una tiene un arreglo distinto. Clasificar antes de parchear es lo que evita que el arreglo sea otra instrucción wait.
1. El estado nunca se cargó
Un contexto creado sin storageState arranca sin sesión, ocurra lo que ocurra en una ejecución anterior. El mismo resultado sale de guardar el archivo en el momento equivocado, usar una ruta relativa que resuelve en otro sitio o ejecutar el paso de setup en un proyecto cuyo estado no se pasa al proyecto dependiente. La pista es que un contexto recién creado se comporta igual que el que falla: ambos están sin sesión.
2. La cookie está, pero muerta
Una cookie de sesión puede existir, llevar el dominio y los flags correctos y aun así ser rechazada por el servidor. Las cookies de sesión tienen su propia vida, y un servidor puede revocar una en cualquier momento, incluso en un redeploy, un cambio de contraseña o un idle timeout. El campo expires de la cookie es una pista, no una garantía: la sesión del servidor puede morir antes. Este es el caso que reproduce el fixture con una llamada revoke explícita.
3. Aislamiento entre contextos
Las cookies y localStorage pertenecen a un contexto de navegador, no al navegador. Guardar el estado de un contexto y esperar que lo comparta otro contexto configurado de forma distinta falla en silencio. Lo mismo vale para workers en paralelo: cada worker obtiene su propio contexto, así que cada worker necesita su propio archivo de storage o su propio paso de login. Este aislamiento es una característica. También es la razón de que una sesión que funciona en un sitio parezca desaparecer en otro.
4. Cadenas de redirección SSO
Con el inicio de sesión único, la app que quiere rara vez es el origen que sostiene la sesión. El login redirige a un proveedor de identidad en otro dominio, el proveedor pone sus propias cookies y el control vuelve a la app con un código o un token. Guardar el estado antes de que esa cadena termine captura parte de la sesión y pierde las cookies del IdP, y la siguiente ejecución reproduce la redirección. El patrón fiable es esperar a la URL final de la aplicación y a una marca autenticada antes de guardar, para que el archivo de estado lleve cada origen implicado en la cadena.
Inicie sesión una vez y guarde el estado correctamente
El flujo de iniciar sesión una vez es corto: navegar, autenticar, confirmar el estado autenticado y luego escribir storageState en un archivo. El paso de confirmación es lo que separa un archivo de estado que funciona de uno que a veces no.
import { chromium } from "playwright";
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://app.example.com/login");
await page.fill("input[name=email]", process.env.APP_EMAIL);
await page.fill("input[name=password]", process.env.APP_PASSWORD);
await page.click("button[type=submit]");
// Wait for the authenticated destination, not just for the click to land.
await page.waitForURL("**/dashboard");
// Verify before saving: an authenticated element plus a 200 from the API.
await page.waitForSelector("#revenue-table");
const probe = await context.request.get("https://app.example.com/api/summary");
if (probe.status() !== 200) throw new Error("login did not take");
await context.storageState({ path: "playwright/.auth/user.json" });
await browser.close();En la ejecución del fixture este paso tardó 119 milisegundos desde abrir la página de inicio de sesión hasta escribir el archivo de estado. El archivo tenía una cookie, el identificador de sesión que emitió el servidor, y el sondeo devolvió 200. Cifras así son propias del fixture, pero la forma de la comprobación es lo que se transfiere: verifique la URL de destino, verifique un elemento autenticado visible, verifique un endpoint autenticado y solo entonces persista.
La ejecucion headed de abajo uso the-internet.herokuapp.com, no el dashboard del fixture. Los 119 milisegundos se quedan con el fixture. La captura muestra la misma forma de guardado en un login publico: /secure tras autenticar, Logout visible y Claude verificando /tmp/pw-auth.json.

En las suites de pruebas, la misma idea se expresa con un proyecto de setup que produce el archivo y proyectos dependientes que lo consumen, el patrón que documenta la guía oficial de autenticación. Para scripts y scrapers, la secuencia anterior es todo el flujo. En ambos casos el archivo es una credencial: contiene cookies de sesión vivas, así que trátelo con el mismo cuidado que una contraseña y déjelo fuera del control de versiones.
Reutilizar el estado guardado en pruebas y scrapers
Reutilizar es un cambio de una línea al crear el contexto. En un script, pase el archivo al contexto. En una suite de Playwright, configúrelo por proyecto o por archivo de prueba para que cada worker arranque desde el mismo estado con sesión iniciada.
// Script: create the context from the saved state.
const context = await browser.newContext({
storageState: "playwright/.auth/user.json",
});
// Test: use the state for this file (or configure it per project).
test.use({ storageState: "playwright/.auth/user.json" });En la ejecución del fixture, un contexto nuevo creado a partir del archivo guardado llegó al panel protegido en 6 milisegundos sin paso de login, y el sondeo autenticado devolvió 200 con la carga esperada. El número interesante no son los 6 milisegundos; es que nada del flujo cambió salvo de dónde salió el estado del contexto.
La captura de reutilizacion es el mismo sitio publico. Un nuevo contexto headed cargo /tmp/pw-auth.json, salto el formulario de login y abrio /secure con Logout ya en la pagina. Los 6 milisegundos siguen siendo el tiempo del fixture.

Conviene saber qué lleva el archivo de estado y qué no. Contiene cookies de cada origen que tocó el contexto y entradas de localStorage por origen. No contiene IndexedDB, session storage ni estado de service workers. Las aplicaciones que guardan su token en IndexedDB necesitan por tanto un paso extra de captura o un nuevo inicio de sesión; esperar que storageState las cubra es una fuente habitual de fallos fantasma. Las cookies mismas se rigen por dominio, ruta y flags descritos en la guía de cookies de MDN y la referencia de Set-Cookie.
Detectar una sesión caducada o revocada
El detector más barato es una petición autenticada hecha antes del trabajo caro. La mayoría de las aplicaciones tienen un endpoint pequeño que responde con el usuario actual, un recuento o un objeto de configuración, y su código de estado es el código de estado de la sesión. Una petición cuesta milisegundos; descubrir el fallo a mitad de un scrape cuesta toda la ejecución.
async function sessionIsAlive(context) {
const probe = await context.request.get(
"https://app.example.com/api/summary",
{ failOnStatusCode: false },
);
return probe.status() === 200;
}
if (!(await sessionIsAlive(context))) {
await loginAndSaveState(context);
}No se fíe solo del campo expires de la cookie. Dice cuándo el navegador debe dejar de enviarla, que no es lo mismo que cuándo el servidor deja de aceptarla. La revocación en el servidor es invisible hasta que pregunta. La llamada revoke del fixture es exactamente esa situación: la cookie seguía presente y bien formada, y el servidor respondió 401.
Reautenticar de forma automática
La reautenticación automática es una máquina de estados pequeña: detectar, volver a iniciar sesión, refrescar el estado guardado, repetir una vez el paso fallido y verificar. El tope importa. Un flujo que se reautentica en silencio en bucle puede tapar una contraseña incorrecta, un bloqueo de cuenta o una página de login que cambió, y encima genera tráfico mientras lo hace.
async function withSession(page, context, task) {
if (!(await sessionIsAlive(context))) {
await loginAndSaveState(context, page); // refresh file after login
}
try {
return await task();
} catch (error) {
if (!(await sessionIsAlive(context))) {
await loginAndSaveState(context, page); // one retry, then fail loudly
return await task();
}
throw error;
}
}En el fixture, el detector vio el 401, el nuevo inicio de sesión y la verificación terminaron en 99 milisegundos, y el archivo de estado renovado volvió a contener exactamente una cookie de sesión. La ejecución terminó en el panel protegido con la tabla visible y tres filas renderizadas. Una reautenticación que acaba sin esas tres confirmaciones no está terminada.
Si varios workers comparten un archivo de estado, todos pueden notar la caducidad al mismo tiempo y competir por volver a iniciar sesión. El remedio es coordinación single-flight: el primer worker refresca el archivo y los demás esperan, o cada worker refresca su propia copia. La mecánica es la misma que los problemas de persistencia de sesión que aparecen cuando varios agentes de IA se ejecutan contra el mismo perfil de navegador.
Varias cuentas y ejecuciones en paralelo
El aislamiento es por contexto, así que la regla es un archivo de storage por cuenta o rol, un contexto por worker y ninguna sesión mutable compartida. En el fixture, dos inicios de sesión en paralelo produjeron dos cookies de sesión distintas, ambos sondeos devolvieron 200 en el mismo milisegundo y ningún contexto pudo ver la sesión del otro. Esa es la propiedad que hay que preservar a propósito, porque el modo de fallo cuando se rompe es sutil: dos cuentas se pisan el estado y un test pasa para el usuario equivocado.
La misma regla aparece en un navegador real. Dos Spaces son dos contextos: uno puede quedar idle en una pestana nueva mientras el otro mantiene una sesion de Airbnb iniciada, sin compartir cookies como haria una sola ventana headed. El punto es el aislamiento. La vista general solo permite ver ambos a la vez.

Comprobar que la autenticación surtió efecto
La verificación son tres comprobaciones, y saltarse cualquiera es cómo un flujo se queda roto en silencio. La primera es positiva: un elemento que solo existe después del login está presente. La segunda es negativa: el formulario de inicio de sesión está ausente, así que no se acepta una página que por casualidad contiene ambos. La tercera es una comprobación de datos que fallaría en una página obsoleta o en caché, como un valor que debe cambiar entre ejecuciones.
await page.waitForSelector("#revenue-table"); // positive
await expect(page.locator("#login-form")).toHaveCount(0); // negative
const probe = await context.request.get("/api/summary"); // data
assert.equal(probe.status(), 200);
assert.ok((await probe.json()).q3Revenue);Las tres pasaron en la ejecución de reutilización del fixture: la tabla era visible, el recuento del formulario de inicio de sesión era cero y la carga JSON contenía el campo esperado. Las tres juntas tardan unos milisegundos y convierten la diferencia entre una sesión que funciona y una rota de una inferencia en un hecho. Más técnica de depuración para las ejecuciones que aún se portan mal está documentada en la guía de depuración de Playwright, y la página de buenas prácticas cubre los hábitos de alrededor, incluido qué esperas merece la pena escribir.
Cuando la sesión pertenece a su navegador real
Un archivo storageState de Playwright es algo que usted posee. Es la herramienta correcta cuando la cuenta es un fixture, la contraseña está en secretos de CI y nadie está sentado en la máquina. Es la herramienta incorrecta cuando el inicio de sesión es suyo: SSO, una llave de hardware, un aviso push, una sesión que ya mantiene caliente en Chrome a diario. En ese caso el trabajo no es reconstruir el login. Es tomar prestado el navegador que ya lo tiene.
ego (lite) es ese préstamo. Un agente de IA puede abrir una pestaña con sesión iniciada que usted ya usa, seguir trabajando en un Space que no le roba el ratón y detenerse cuando un segundo factor o una confirmación de pago le necesita. La sesión nunca se convierte en un archivo JSON en disco. Ese es el punto: un inicio de sesión personal debe quedarse en el navegador, no viajar por una ruta storageState que CI más tarde commitearía por accidente.

Separe los dos instrumentos. Las cuentas de prueba desatendidas se quedan en storageState, como describe la guía de autenticación de Playwright. Los paneles personales se quedan en un navegador visible. ego (lite) no pulsa MFA, no confirma pagos y no recoge credenciales; esas paradas están documentadas en los documentos de Space y el inicio rápido. La versión actual del producto es 0.5.0.32 (changelog, 2026-09-12). Vuelva a comprobar el changelog y el repositorio de GitHub antes de citar un build más nuevo.
FAQ
¿Por qué Playwright sigue cayendo en la página de inicio de sesión después de guardar storageState?
Playwright sigue cayendo en la página de inicio de sesión después de storageState cuando el archivo se guardó demasiado pronto, el contexto nunca lo cargó o el servidor revocó la cookie. Compruebe primero las cookies del archivo y luego las opciones del contexto. Si ambas se ven bien, trátelo como una sesión muerta, no como un error de navegación.
¿Las cookies de sesión sobreviven a storageState?
Sí. El archivo registra cookies tengan o no caducidad, junto con localStorage por origen. Lo que no sobrevive es lo que el navegador guarda fuera de esa estructura: IndexedDB, session storage, caché y service workers.
¿Cuánto dura un inicio de sesión de Playwright guardado?
Mientras el servidor siga aceptando la sesión, una decisión que toma el servidor y puede revertir en cualquier momento. La fecha de caducidad de la cookie es un techo, no una promesa. Trate cualquier archivo de estado longevo como obsoleto hasta que un sondeo diga lo contrario.
¿Pueden los workers en paralelo compartir un archivo storageState?
Pueden leerlo, y el contexto de cada worker quedará aislado. El problema aparece cuando un worker refresca el archivo tras un nuevo inicio de sesión mientras otro está a mitad de ejecución. O dé a cada worker su propia copia, o serialice el refresco para que solo ocurra un login a la vez.
¿Cómo me autentico con OAuth o SSO en Playwright?
Complete la cadena de redirección entera una vez, en un contexto que conserve las cookies del proveedor de identidad, y guarde el estado solo cuando se alcance la URL final de la aplicación. Si el proveedor exige un paso interactivo, hágalo una vez a mano en la misma ejecución y reutilice después el estado resultante.
¿Cómo hay que tratar el MFA y los códigos de un solo uso?
No automatizando el segundo factor con un código almacenado. El patrón honesto es un paso humano de una sola vez cuyo resultado es la sesión guardada, o una sesión que ya estaba autenticada en un navegador suyo. Guardar secretos OTP o códigos SMS junto a un archivo de storage convierte un control de seguridad en un pasivo.
¿Es seguro hacer commit de storageState al control de versiones?
No. El archivo contiene cookies de sesión vivas. Déjelo en el directorio de trabajo, ignórelo en git y, en CI, inyéctelo desde un almacén de secretos. Si alguna vez se ha hecho commit, trate la sesión como filtrada y revóquela.
¿Cómo pruebo a propósito que mi automatización maneja una sesión caducada?
Añada un gancho de test que invalide la sesión en el servidor, como hace el endpoint revoke del fixture, y ejecute contra él la ruta de reutilización. Afirme la redirección, el 401 del sondeo y la recuperación. Ese único test cubre el camino de código que, si no, solo se dispara en producción.
¿Y si la app guarda su token en IndexedDB?
storageState no lo transportará, así que use una de dos vías: capturar el token con un script de página tras el login y volver a inyectarlo al reutilizar, o autenticarse en cada ejecución con la API que emite el token. La primera es más rápida; la segunda está menos acoplada a los internos de la app.
¿Debería cada ejecución reautenticarse en lugar de reutilizar el estado?
Para CI con una cuenta de prueba desechable, reautenticar en cada ejecución es el valor por defecto más seguro. Para un job que corre a menudo contra una sesión estable, reutilizar más un sondeo y un nuevo login con tope sale más barato y es más fácil de razonar. Ambos son legítimos; la reutilización sin verificar no lo es.
¿Por qué el inicio de sesión funciona en local y falla en CI?
Suele ser porque el archivo de estado falta o es más viejo en CI, el reloj difiere o la página de inicio de sesión sirve una variante distinta a una IP nueva. Reproduzca con el mismo ajuste headless, el mismo viewport y el mismo archivo de storage, y sondee la sesión antes de que corra la suite. El fallo suele ser ambiental, no una diferencia de código.
¿Puede un archivo storageState servir a dos cuentas?
No, y los intentos de fusionarlos suelen quedarse con la cookie que se escribió en último lugar. Use un archivo por cuenta o rol, nómbrelo según la identidad a la que pertenece y pase el correcto a cada contexto.