
Quando um teste ou scraper Playwright precisa de autenticação, em geral não há motivo para entrar do zero em toda execução. O caminho comum é entrar uma vez, salvar os cookies e o estado relacionado do navegador com storageState e carregar esse estado nas execuções seguintes para reutilizar a mesma sessão. Mas ter um arquivo storageState salvo não significa que o login vai funcionar para sempre. A sessão pode expirar, ser revogada pelo servidor ou nem carregar direito desde o começo.
Se você está automatizando a própria conta e já está autenticado no navegador do dia a dia, há outra opção: reutilizar essa sessão existente direto. O ego (lite) deixa agentes de IA trabalharem num navegador que já tem o estado de login de que precisam, mantendo cada tarefa isolada no próprio Space. Se a tarefa chegar a um código de verificação, login por QR ou outro passo que exige você, dá para assumir o controle no navegador visível em vez de tentar automatizar o MFA.
Qualquer que seja a abordagem, o objetivo é o mesmo: garantir que a automação realmente tenha uma sessão autenticada válida. Uma execução que cai de volta na página de login, uma API que responde 401 ou um elemento pós-login que nunca aparece pode parecer três problemas distintos: navegação, permissões ou um problema de seletor, quando na verdade todos podem apontar para a mesma sessão quebrada. Nas seções abaixo, usamos um ambiente local repetível para mostrar como salvar, reutilizar, verificar e atualizar o estado de autenticação no Playwright.
Como um login quebrado no Playwright se parece
Na execução do fixture, uma sessão revogada produziu os três sintomas ao mesmo tempo. Depois que a sessão no servidor foi limpa, um contexto reutilizando o estado salvo foi redirecionado à página de login com um parâmetro next apontando de volta ao dashboard, uma sonda no endpoint JSON autenticado retornou 401, e a espera pela tabela do dashboard estourou o timeout configurado de 3 segundos.
No login publico do the-internet, a mesma classe de falha e o bounce. Abrir /secure sem uma sessao viva cai em /login com o flash vermelho You must login to view the secure area. Essa e a verificacao de URL da nota abaixo: o navegador esta no formulario de login, nao na pagina que o locator esperava. Esta captura e esse bounce. Nao prova que Logout apagou um ficheiro storageState. Logout no the-internet termina a sessao da janela atual; um ficheiro ja escrito em disco ainda pode reabrir /secure ate o cookie estar morto.

O terceiro sintoma é o caro. Um locator estourando timeout num elemento pós-login não diz que o locator está errado; diz que a página à sua frente não é a que você assumiu. Na execução acima a tabela nunca renderizou porque o navegador estava parado no formulário de login. Tratar isso como problema de seletor leva a remendar uma query que nunca esteve quebrada.
Quatro causas raiz das falhas de autenticação
As falhas de autenticação se agrupam em quatro causas, e cada uma tem um conserto diferente. Classificar antes de remendar é o que impede que o conserto vire mais um wait.
1. O estado nunca carregou
Um contexto criado sem storageState começa desconectado, não importa o que aconteceu na execução anterior. O mesmo resultado vem de salvar o arquivo no momento errado, usar um caminho relativo que resolve em outro lugar, ou rodar o passo de setup num projeto cujo estado não é passado ao projeto dependente. O sinal é que um contexto novinho se comporta igual ao que falha: os dois estão desconectados.
2. O cookie está presente, mas morto
Um cookie de sessão pode existir, ter o domínio e as flags certos, e mesmo assim ser recusado pelo servidor. Cookies de sessão têm vida própria, e o servidor pode revogar um a qualquer momento, inclusive num redeploy, numa troca de senha ou num timeout de ociosidade. O campo expires do cookie é uma dica, não uma garantia: a sessão no servidor pode morrer antes. É esse o caso que o fixture reproduz com uma chamada explícita de revoke.
3. Isolamento entre contextos
Cookies e localStorage pertencem a um contexto de navegador, não ao navegador. Salvar o estado de um contexto e esperar que um contexto configurado de outro jeito o compartilhe falha em silêncio. O mesmo vale para workers em paralelo: cada worker ganha o próprio contexto, então cada um precisa do próprio arquivo de estado ou do próprio passo de login. Esse isolamento é um recurso. Também é a razão de uma sessão que funciona num lugar parecer desaparecer em outro.
4. Cadeias de redirecionamento SSO
Com single sign-on, o app que você quer raramente é a origem que segura a sessão. O login redireciona para um provedor de identidade em outro domínio, o provedor define os próprios cookies, e o controle volta ao app com um código ou um token. Salvar o estado antes de essa cadeia terminar captura só parte da sessão e deixa de fora os cookies do IdP, e a próxima execução reproduz o redirecionamento. O padrão confiável é esperar a URL final da aplicação e um marcador autenticado antes de salvar, para que o arquivo de estado carregue cada origem envolvida na cadeia.
Entre uma vez e salve o estado corretamente
O fluxo de login único é curto: navegar, autenticar, confirmar o estado autenticado e então gravar o storageState num arquivo. O passo de confirmação é o que separa um arquivo de estado que funciona de um que às vezes não funciona.
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();Na execução do fixture, este passo levou 119 milissegundos da abertura da página de login até a gravação do arquivo de estado. O arquivo tinha um cookie, o identificador de sessão que o servidor emitiu, e a sonda retornou 200. Números assim são propriedades do fixture, mas o formato da verificação é o que transfere: confirme a URL de destino, confirme um elemento autenticado visível, confirme um endpoint autenticado, e só então persista.
A execucao headed abaixo usou the-internet.herokuapp.com, nao o dashboard do fixture. Os 119 milissegundos ficam com o fixture. A captura mostra a mesma forma de salvamento num login publico: /secure apos autenticacao, Logout visivel e Claude a verificar /tmp/pw-auth.json.

Em suítes de teste, a mesma ideia aparece como um projeto de setup que gera o arquivo e projetos dependentes que o consomem, o padrão que o guia oficial de autenticação documenta. Para scripts e scrapers, a sequência acima é o fluxo inteiro. De qualquer forma, o arquivo é uma credencial: contém cookies de sessão ativos, então trate-o com o mesmo cuidado de uma senha e mantenha-o fora do controle de versão.
Reutilizar o estado salvo em testes e scrapers
Reutilizar é uma mudança de uma linha na criação do contexto. Num script, passe o arquivo para o contexto. Numa suíte Playwright, configure por projeto ou por arquivo de teste para que cada worker comece no mesmo estado autenticado.
// 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" });Na execução do fixture, um contexto novo criado a partir do arquivo salvo chegou ao dashboard protegido em 6 milissegundos sem passo de login, e a sonda autenticada retornou 200 com o payload esperado. O número interessante não são os 6 milissegundos; é que nada no fluxo mudou, a não ser de onde veio o estado do contexto.
A captura de reutilizacao e o mesmo site publico. Um novo contexto headed carregou /tmp/pw-auth.json, saltou o formulario de login e abriu /secure com Logout ja na pagina. Os 6 milissegundos continuam a ser o tempo do fixture.

Vale saber o que o arquivo de estado carrega e o que não carrega. Ele contém cookies de cada origem que o contexto tocou e entradas de localStorage por origem. Não contém IndexedDB, session storage nem estado de service worker. Aplicações que guardam o token no IndexedDB precisam, portanto, de um passo extra de captura ou de um novo login; esperar que o storageState cubra isso é uma fonte comum de falhas fantasma. Os próprios cookies são regidos por domínio, caminho e flags descritos no guia de cookies da MDN e na referência de Set-Cookie.
Detectar uma sessão expirada ou revogada
O detector mais barato é uma requisição autenticada feita antes do trabalho caro. A maioria das aplicações tem um endpoint pequeno que responde com o usuário atual, uma contagem ou um objeto de configuração, e o código de status dele é o código de status da sessão. Uma requisição custa milissegundos; descobrir a falha no meio de um scrape custa a execução inteira.
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);
}Não confie só no campo expires do cookie. Ele diz quando o navegador deve parar de enviar o cookie, o que não é o mesmo que quando o servidor para de aceitá-lo. A revogação no servidor fica invisível até você perguntar. A chamada de revoke do fixture é exatamente essa situação: o cookie ainda estava presente e bem formado, e o servidor respondeu 401.
Reautenticar automaticamente
A reautenticação automática é uma máquina de estados pequena: detectar, entrar de novo, atualizar o estado salvo, repetir o passo que falhou uma vez e verificar. O teto importa. Um fluxo que reautentica em silêncio num loop pode mascarar uma senha errada, um bloqueio de conta ou uma página de login que mudou, e ainda gera tráfego enquanto faz isso.
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;
}
}No fixture, o detector viu o 401, o novo login e a verificação terminaram em 99 milissegundos, e o arquivo de estado atualizado de novo tinha exatamente um cookie de sessão. A execução terminou no dashboard protegido com a tabela visível e três linhas renderizadas. Uma reautenticação que termina sem essas três confirmações não está concluída.
Se vários workers compartilham um arquivo de estado, todos podem notar a expiração no mesmo instante e correr para entrar de novo. O remédio é coordenação single-flight: o primeiro worker atualiza o arquivo e os outros esperam, ou cada worker atualiza a própria cópia. A mecânica é a mesma dos problemas de persistência de sessão que aparecem quando vários agentes rodam contra o mesmo perfil de navegador.
Várias contas e execuções em paralelo
O isolamento é por contexto, então a regra é um arquivo de estado por conta ou papel, um contexto por worker e nenhuma sessão mutável compartilhada. No fixture, dois logins em paralelo produziram dois cookies de sessão diferentes, as duas sondas retornaram 200 no mesmo milissegundo, e nenhum contexto via a sessão do outro. Essa é a propriedade a preservar de propósito, porque o modo de falha quando ela quebra é sutil: duas contas sobrescrevem o estado uma da outra e um teste passa para o usuário errado.
A mesma regra aparece num navegador real. Dois Spaces sao dois contextos: um pode ficar idle num novo separador enquanto o outro mantem uma sessao Airbnb autenticada, sem partilhar cookies como faria uma unica janela headed. O ponto e o isolamento. A visao geral so mostra os dois ao mesmo tempo.

Verificar se a autenticação realmente valeu
A verificação são três checagens, e pular qualquer uma é como um fluxo permanece quebrado em silêncio. A primeira é positiva: um elemento que só existe depois do login está presente. A segunda é negativa: o formulário de login está ausente, então uma página que por acaso contém os dois não é aceita. A terceira é uma checagem de dados que falharia numa página velha ou em cache, como um valor que precisa mudar entre execuções.
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);As três passaram na execução de reuso do fixture: a tabela estava visível, a contagem do formulário de login era zero, e o payload JSON continha o campo esperado. As três juntas levam poucos milissegundos e transformam a diferença entre uma sessão que funciona e uma quebrada de inferência em fato. Mais técnica de debug para as execuções que ainda se comportam mal está documentada no guia de debug do Playwright, e a página de práticas recomendadas cobre os hábitos em volta, inclusive quais esperas valem a pena escrever.
Quando a sessão pertence ao seu navegador real
Um arquivo storageState do Playwright é algo que você controla. É a ferramenta certa quando a conta é um fixture, a senha está nos secrets do CI e ninguém está na máquina. É a ferramenta errada quando o login é o seu: SSO, uma chave de hardware, um prompt push, uma sessão que você já mantém quente no Chrome do dia a dia. Nesse caso o trabalho não é reconstruir o login. É emprestar o navegador que já o tem.
O ego (lite) é esse empréstimo. Um agente de IA pode abrir um guia autenticado que você já usa, continuar trabalhando num Space que não rouba o mouse e parar quando um segundo fator ou uma confirmação de pagamento precisa de você. A sessão nunca vira um arquivo JSON no disco. Esse é o ponto: um login pessoal deve ficar no navegador, não viajar por um caminho de storageState que o CI depois commitaria por acidente.

Mantenha os dois instrumentos separados. Contas de teste sem supervisão ficam no storageState, como o guia de autenticação do Playwright descreve. Painéis pessoais ficam num navegador visível. O ego (lite) não clica em MFA, não confirma pagamentos e não coleta credenciais; essas paradas estão documentadas na documentação de Space e no início rápido. A versão atual do produto é 0.5.0.32 (changelog, 2026-09-12). Confira de novo o changelog e o repositório no GitHub antes de citar um build mais novo.
FAQ
Por que o Playwright ainda cai na página de login depois que eu salvo o storageState?
O Playwright ainda cai na página de login depois do storageState quando o arquivo foi salvo cedo demais, o contexto nunca o carregou ou o servidor revogou o cookie. Confira primeiro os cookies do arquivo, depois as opções do contexto. Se os dois parecerem certos, trate como sessão morta, não como bug de navegação.
Cookies de sessão sobrevivem ao storageState?
Sim. O arquivo registra cookies tenham ou não expiração, junto com localStorage por origem. O que não sobrevive é o que o navegador guarda fora dessa estrutura: IndexedDB, session storage, cache e service workers.
Quanto tempo dura um login salvo do Playwright?
Enquanto o servidor continuar aceitando a sessão, decisão que o servidor toma e pode reverter a qualquer momento. A data de expiração do cookie é um teto, não uma promessa. Trate qualquer arquivo de estado de longa duração como velho até uma sonda dizer o contrário.
Workers em paralelo podem compartilhar um arquivo storageState?
Eles podem lê-lo, e o contexto de cada worker ficará isolado. O problema aparece quando um worker atualiza o arquivo depois de um novo login enquanto outro está no meio da execução. Ou dê a cada worker a própria cópia, ou serialize a atualização para que só um login aconteça por vez.
Como autenticar com OAuth ou SSO no Playwright?
Complete a cadeia inteira de redirecionamento uma vez, num contexto que vai guardar os cookies do provedor de identidade, e salve o estado só depois que a URL final da aplicação for alcançada. Se o provedor exigir um passo interativo, faça-o uma vez à mão na mesma execução e reutilize o estado resultante depois.
Como lidar com MFA e códigos de uso único?
Não automatizando o segundo fator com um código armazenado. O padrão honesto é um passo humano único cujo resultado é a sessão salva, ou uma sessão que já estava autenticada num navegador seu. Guardar segredos OTP ou códigos SMS ao lado de um arquivo de estado transforma um controle de segurança em passivo.
É seguro fazer commit do storageState no controle de versão?
Não. O arquivo contém cookies de sessão ativos. Mantenha-o no diretório de trabalho, ignore-o no git e, no CI, injete-o a partir de um secret store. Se alguma vez foi commitado, trate a sessão como vazada e revogue-a.
Como testar de propósito que a automação lida com uma sessão expirada?
Adicione um hook de teste que invalida a sessão no servidor, do jeito que o endpoint de revoke do fixture faz, e rode o caminho de reuso contra isso. Faça assert do redirecionamento, do 401 da sonda e da recuperação. Esse único teste cobre o caminho de código que, de outro modo, só dispara em produção.
E se o app guardar o token no IndexedDB?
O storageState não vai carregá-lo, então use um de dois caminhos: capture o token com um script de página depois do login e reinsira-o no reuso, ou autentique dentro de cada execução usando a API que emite o token. O primeiro é mais rápido; o segundo depende menos dos internos do app.
Cada execução deveria reautenticar em vez de reutilizar o estado?
No CI com uma conta de teste descartável, reautenticar a cada execução é o padrão mais seguro. Para um job que roda com frequência contra uma sessão estável, reuso mais uma sonda e um novo login com teto sai mais barato e mais fácil de raciocinar. Os dois são legítimos; reuso sem verificação não é.
Por que o login funciona localmente e falha no CI?
Em geral porque o arquivo de estado está ausente ou mais velho no CI, o relógio difere, ou a página de login serve uma variante diferente para um IP novo. Reproduza com o mesmo ajuste headless, viewport e arquivo de estado, e sonde a sessão antes da suíte rodar. A falha costuma ser ambiental, não uma diferença de código.
Um arquivo storageState pode servir duas contas?
Não, e tentativas de mesclá-los tendem a ficar com o cookie que foi gravado por último. Use um arquivo por conta ou papel, nomeie-o pela identidade a que pertence e passe o certo a cada contexto.