Funciona na sua máquina, falha no CI e passa quando você tenta de novo. Esse padrão costuma parecer um problema de velocidade, mas muitas vezes é um problema de estado. Um teste altera um registro que outro teste reutiliza, duas execuções escrevem no mesmo arquivo ou um fixture mantém uma conta que deveria ser exclusiva.
O Playwright cria um BrowserContext isolado para cada teste. Isso protege cookies, storage e páginas. Não protege automaticamente o banco de dados, a fila, a conta de teste, o sistema de arquivos ou um estado global carregado pelo seu aplicativo.
Este artigo parte do guia de testes end-to-end com Playwright e trata de uma pergunta mais específica: como encontrar e remover o estado compartilhado que o CI expõe quando aumenta os workers.
Resposta curta
- Dê a cada teste um identificador e dados que nenhuma outra execução possa reutilizar.
- Use fixtures com escopo de worker apenas para recursos que podem ser compartilhados com segurança.
- Grave relatórios e arquivos em caminhos próprios do teste.
- Use
workers: 1e retries para confirmar uma hipótese, não para esconder a causa.
Por que o teste passa localmente e falha no CI?
Em 2026, a documentação de paralelismo do Playwright explica que arquivos de teste rodam em paralelo por padrão e que a flakiness aparece quando o estado fica fora de um teste. A diferença entre seu laptop e o CI costuma ser a quantidade de workers, a ordem das execuções e a pressão sobre serviços compartilhados.
O navegador pode estar perfeitamente isolado enquanto duas chamadas criam o mesmo pedido no backend. Uma suíte local com um worker executa essas chamadas em sequência. O CI executa os arquivos ao mesmo tempo e transforma uma dependência invisível em uma corrida observável.
Há quatro fronteiras que vale separar durante a investigação:
| Fronteira | O que o Playwright isola | O que você ainda precisa isolar |
|---|---|---|
| Navegador | cookies, storage e páginas do BrowserContext |
nada, salvo quando você cria contextos manualmente |
| Backend | nada por padrão | usuário, pedido, documento ou chave usados pelo teste |
| Worker | processo e fixtures com escopo próprio | conta ou serviço reutilizado por vários arquivos |
| Saída | diretório de artefatos do runner | nomes de arquivos escritos pelo seu código |
O diagnóstico mais útil não começa com a pergunta "qual timeout devo aumentar?". Começa com "qual estado existe fora deste teste e quem mais pode tocá-lo?". Essa pergunta reduz o espaço de investigação antes de qualquer ajuste de configuração.
O que o Playwright já isola?
Em 2026, a página de isolamento por BrowserContext descreve cada contexto como um perfil limpo, com local storage, session storage e cookies próprios. Essa garantia é suficiente para a sessão do navegador, mas não transforma um ambiente de staging compartilhado em um ambiente descartável.
Um page novo não apaga uma linha criada por outro teste. Também não muda o usuário fixo usado por toda a equipe, não limpa uma mensagem numa fila e não impede que dois workers escrevam em exports/result.csv. O nome "isolado" precisa sempre dizer qual camada está isolada.
Veja este exemplo mínimo. Ele é ilustrativo e usa uma função de preparação que pertence ao projeto real:
import { test, expect } from "@playwright/test";
test("edita somente o pedido criado por este teste", async ({ page }, testInfo) => {
const orderId = `order-${testInfo.testId}`;
await seedOrder({ id: orderId, status: "draft" });
await page.goto(`/orders/${orderId}/edit`);
await page.getByLabel("Status").selectOption("approved");
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("status")).toHaveText("Order saved");
await expect(page.getByTestId("order-status")).toHaveText("approved");
});
O valor do exemplo não está no prefixo order-. Está em derivar o dado de testInfo.testId, executar o cenário contra esse dado e verificar uma consequência que pertence ao mesmo pedido. O guia de paralelismo do Playwright usa esse tipo de identificador para evitar que testes que editam o mesmo registro disputem entre si.
Como criar dados únicos por teste?
Em 2026, as boas práticas do Playwright recomendam que cada teste controle seus próprios dados quando trabalha com banco. A forma mais rápida de aplicar isso costuma ser preparar o cenário por API ou por uma função de seed, antes de abrir a tela. Criar a pré-condição clicando na interface aumenta o tempo e mistura o teste que prepara com o teste que verifica.
Comece listando os recursos que o caso toca: conta, pedido, cliente, arquivo, fila e feature flag. Para cada recurso, escolha uma destas estratégias:
- Gere um identificador baseado em
testInfo.testIdquando o recurso pertence a um único teste. - Gere um identificador baseado em
workerInfo.workerIndexquando um fixture pode ser compartilhado com segurança por todos os testes daquele worker. - Use um ambiente descartável quando o teste modifica uma dependência que não pode ser particionada.
- Use um mock para uma dependência externa que não é o objeto da prova, como explicado em como mockar APIs no Playwright sem falsos positivos.
Não use dados únicos somente no nome. Confirme que a aplicação realmente filtra por esse identificador e que o seed falha quando o recurso não pode ser criado. Um teste que gera order-abc, mas continua lendo "o último pedido", ainda depende de estado compartilhado.
Quando um fixture por worker é seguro?
Em 2026, a documentação de fixtures do Playwright define fixtures com escopo de worker como recursos criados uma vez para aquele processo. Isso é útil para um servidor local ou uma conta exclusiva do worker. É perigoso para uma conta global que todos os workers reutilizam.
Um fixture por worker precisa responder a três perguntas: qual índice identifica o worker, quais testes podem usar o recurso ao mesmo tempo e como o recurso será removido quando o worker terminar? Se uma alteração feita por um teste muda o que outro teste espera, o recurso não é compartilhável, mesmo que a criação seja cara.
import { test as base } from "@playwright/test";
type Account = { id: string; username: string };
export const test = base.extend<{}, { account: Account }>({
account: [async ({}, use, workerInfo) => {
const username = `ci-user-${workerInfo.workerIndex}`;
const account = await createAccount({ username });
await use(account);
await deleteAccount(account.id);
}, { scope: "worker" }],
});
O código é ilustrativo: createAccount e deleteAccount precisam existir no seu ambiente de teste. O ponto é a fronteira. Cada worker recebe uma conta própria, enquanto cada teste ainda deve evitar alterar dados que outro teste do mesmo worker precise ler. Quando isso não for possível, reduza o escopo para o teste.
Não transforme beforeAll em depósito de estado mutável. Preparar uma configuração imutável uma vez pode ser correto. Criar um pedido, alterar seu status e esperar que todos os testes o encontrem no mesmo ponto é uma dependência de ordem.
Como evitar conflitos em arquivos e artefatos?
Em 2026, o guia de paralelismo do Playwright recomenda testInfo.outputPath() para produzir caminhos exclusivos por teste. O mesmo princípio vale para exportações, downloads, snapshots auxiliares e arquivos temporários criados pela aplicação durante o caso.
import { test, expect } from "@playwright/test";
import fs from "node:fs/promises";
test("exporta o relatório deste pedido", async ({ page }, testInfo) => {
const output = testInfo.outputPath("report.csv");
await page.getByRole("button", { name: "Export" }).click();
await expect(page.getByRole("status")).toHaveText("Export ready");
await fs.writeFile(output, "order_id,status\nabc,approved\n", "utf8");
});
Esse exemplo escreve o arquivo diretamente para deixar o caminho visível. Em um teste real, você pode usar o diretório de download do contexto, uma função de exportação da aplicação ou o artefato produzido pelo CI. A regra permanece: dois testes não devem poder sobrescrever o mesmo caminho e depois discutir qual conteúdo foi encontrado.
Também revise variáveis globais no código de teste. Um array de resultados no escopo do módulo, um cliente singleton com cache mutável ou um token salvo em arquivo pode atravessar casos que parecem ter páginas independentes.
Por que workers: 1 e retry não são a correção?
Em 2026, o Playwright documenta o modo serial, mas recomenda testes isolados em vez de fazer uma suíte depender de ordem. Definir workers: 1 pode confirmar que a falha nasce de concorrência. Não prova que o teste está correto, porque o CI continuará expondo o defeito quando a configuração voltar a paralelizar.
O mesmo vale para retry. O guia de retries classifica como flaky o teste que falha na primeira tentativa e passa depois. Repetir é uma forma de coletar sinal. É uma forma ruim de declarar que o comportamento está confiável.
Use esta sequência de diagnóstico:
- Rode o caso isolado no mesmo commit e com os mesmos dados.
- Rode o caso repetidamente com
--workers=1. - Rode o mesmo conjunto com dois ou mais workers.
- Mude somente a estratégia de dados e repita a execução.
- Compare a primeira tentativa, não apenas o resultado após retry.
Se a falha desaparece com um worker, procure estado externo antes de aumentar timeouts. Se ela aparece também isolada, investigue espera, locator, ambiente, rede ou uma expectativa errada. O guia de asserções recomenda matchers web-first que aguardam a condição esperada; page.waitForTimeout() é documentado como inadequado para estabilizar testes de produção.
Como provar a causa de uma falha instável?
Em 2026, o Playwright recomenda preservar artefatos de execução e usar assertions que esperam estados observáveis. A prova de uma correção precisa mostrar o que mudou na fronteira de estado e qual execução teria falhado antes. Um novo passe verde, sozinho, não explica a causa.
Registre pelo menos:
- commit, arquivo e nome completo do teste;
- worker e tentativa que executaram o caso;
- identificadores dos dados criados, sem incluir credenciais;
- trace, console, rede e screenshot quando o caso falhar;
- diferença entre a primeira tentativa e o retry;
- comando usado para repetir com um worker e com paralelismo.
Se a execução depende de um serviço externo, não use a estabilidade do serviço como prova da sua aplicação. A recomendação de boas práticas do Playwright é testar o que você controla e garantir respostas de terceiros quando eles não fazem parte do objeto do teste.
Uma correção merece entrar na suíte quando muda uma fronteira que você consegue nomear. "Aumentamos o timeout e passou" descreve um resultado. "Cada teste passou a criar seu próprio pedido e o primeiro retry deixou de ser necessário" descreve uma hipótese verificável. Só a segunda frase ajuda a manter a suíte confiável.
Checklist para revisar testes Playwright no CI
Antes de colocar um teste em paralelo, responda:
- O teste cria ou identifica os dados que precisa, ou lê um registro deixado por outro caso?
- O usuário, token, conta e feature flag pertencem ao teste ou ao worker?
- O teste pode alterar o estado que outro teste consulta?
- O mock de API está cobrindo uma dependência externa ou escondendo o contrato que deveria ser verificado?
- O arquivo, download, snapshot ou exportação tem caminho próprio?
- Existe um singleton ou variável de módulo com estado mutável?
workers: 1está sendo usado como diagnóstico temporário ou ficou como solução permanente?- Um retry gera a marca de flakiness quando a primeira tentativa falha?
- O relatório guarda evidência suficiente para separar bug, teste, dado e ambiente?
- O teste espera uma condição observável ou dorme por uma quantidade fixa de tempo?
Se duas respostas apontarem para o mesmo recurso compartilhado, corrija essa fronteira antes de reescrever o locator. Se todas as dependências estão isoladas e a falha continua, aí sim vale investigar sincronização, infraestrutura ou uma regra do produto.
Perguntas frequentes
O BrowserContext já não isola todos os testes?
Ele isola cookies, storage, páginas e outros dados do navegador. Não isola automaticamente um pedido no backend, uma conta fixa, uma fila, um arquivo ou um singleton do seu código. O teste precisa criar ou particionar cada recurso externo que possa ser lido ou alterado por outra execução.
Posso deixar todos os testes com workers: 1?
Pode ser uma escolha temporária enquanto a equipe localiza uma corrida. A documentação do Playwright recomenda testes independentes, não uma suíte que depende de execução serial. Se o modo com um worker elimina a falha, trate isso como evidência de uma dependência de estado ou ordem e continue a investigação.
Um fixture por worker é melhor que um fixture por teste?
Não por padrão. O escopo de worker reduz custo quando o recurso é caro e pode ser compartilhado sem mutação. O escopo de teste é mais seguro para dados que mudam durante o cenário. Escolha pelo isolamento necessário, não apenas pelo tempo de preparação.
Devo aumentar o retry quando um teste falha no CI?
Use retry para preservar evidência e identificar flakiness, não para converter a falha em aprovação silenciosa. Um teste que passa somente depois da repetição continua instável. Primeiro verifique dados, contas, arquivos, chamadas externas, espera e concorrência. Depois decida se um retry limitado tem valor operacional.
Conclusão
Testes Playwright no CI ficam mais confiáveis quando cada camada tem um dono claro. O BrowserContext cuida da sessão do navegador. Seu código precisa cuidar dos registros, contas, arquivos, filas e efeitos globais que ficam fora dele.
Comece pelo caso que falha e escreva quais dados ele realmente precisa. Torne esses dados únicos, use fixtures por worker somente para recursos seguros, prefira asserções que aguardam condições reais e preserve a primeira tentativa. Paralelismo e retry são ferramentas de diagnóstico. A correção é remover a dependência invisível.
Fontes consultadas
- Playwright, "Parallelism", consultado em 2026-08-18
- Playwright, "Isolation", consultado em 2026-08-18
- Playwright, "Fixtures", consultado em 2026-08-18
- Playwright, "Best Practices", consultado em 2026-08-18
- Playwright, "Assertions", consultado em 2026-08-18
- Playwright, "Retries", consultado em 2026-08-18
- Playwright, "Page API: waitForTimeout", consultado em 2026-08-18