Testes unitários ajudam a verificar funções isoladas, mas não conseguem confirmar sozinhos que uma aplicação inteira funciona do ponto de vista do usuário. Um botão pode estar visível e ainda não enviar o formulário, uma rota pode carregar sem os dados esperados e uma autenticação pode falhar somente quando navegador, API e banco trabalham juntos. É nesse cenário que os testes E2E com Playwright se tornam valiosos.
Neste guia, você vai aprender a instalar o Playwright, criar os primeiros testes de ponta a ponta, escolher seletores confiáveis, trabalhar com múltiplos navegadores, reutilizar autenticação, simular respostas de rede, depurar falhas e executar a suíte em integração contínua. O objetivo é construir testes úteis e sustentáveis, evitando scripts frágeis que quebram por qualquer alteração visual.
O que são testes E2E?
E2E significa end to end, ou ponta a ponta. Esse tipo de teste simula uma jornada real, normalmente abrindo o navegador, acessando páginas, preenchendo campos, clicando em elementos e verificando o resultado final. Em uma loja virtual, por exemplo, um teste pode pesquisar um produto, adicioná-lo ao carrinho e confirmar que o total foi calculado corretamente.
O teste não precisa reproduzir todos os detalhes do ambiente de produção, mas deve exercitar as partes que realmente colaboram no fluxo. Isso inclui interface, roteamento, chamadas HTTP, estado do navegador e, dependendo da estratégia, serviços de apoio.
Se você ainda está consolidando a base da linguagem, revise o que é JavaScript e o que é TypeScript. Para comparar níveis de teste, consulte também o guia de testes unitários com Jest.
Por que usar Playwright?
Playwright é uma ferramenta de automação de navegadores mantida pela Microsoft. Ela oferece um test runner próprio, suporte a Chromium, Firefox e WebKit, execução paralela, captura de screenshots, vídeos, traces e recursos para controlar rede, permissões e armazenamento do navegador.
Uma vantagem importante é o mecanismo de espera automática. Em vez de adicionar pausas fixas, o Playwright aguarda que o elemento esteja disponível, visível e pronto para receber a ação. Isso reduz falhas causadas por animações, requisições assíncronas e pequenas diferenças de desempenho entre máquinas.
A documentação oficial do Playwright apresenta os requisitos atuais, comandos de instalação e exemplos para diferentes linguagens. Neste artigo, os exemplos usam TypeScript.
Criando o projeto
Em uma pasta vazia, execute o assistente oficial:
npm init playwright@latestO assistente pergunta se você deseja usar TypeScript, qual pasta armazenará os testes e se os navegadores devem ser instalados. Ao final, normalmente são criados arquivos como playwright.config.ts, uma pasta tests e um teste de exemplo.
Para executar toda a suíte:
npx playwright testPara abrir o relatório HTML após a execução:
npx playwright show-reportDurante o desenvolvimento, o modo de interface é muito útil:
npx playwright test --uiEle permite selecionar testes, acompanhar etapas, repetir casos e inspecionar o estado da página sem depender apenas do terminal.
Configurando a aplicação testada
O arquivo playwright.config.ts centraliza opções. Uma configuração simples pode definir a URL base e iniciar automaticamente o servidor local:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
reporter: 'html',
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry'
},
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } }
]
});Com baseURL, os testes podem navegar para caminhos relativos. A opção webServer evita iniciar a aplicação manualmente em cada execução. Já trace: 'on-first-retry' grava informações detalhadas quando um teste falha e é repetido.
Escrevendo o primeiro teste
Crie o arquivo tests/login.spec.ts:
import { test, expect } from '@playwright/test';
test('usuário entra no painel', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('E-mail').fill('usuario@example.com');
await page.getByLabel('Senha').fill('senha-segura');
await page.getByRole('button', { name: 'Entrar' }).click();
await expect(page).toHaveURL(/dashboard/);
await expect(
page.getByRole('heading', { name: 'Painel' })
).toBeVisible();
});A fixture page representa uma aba do navegador. O teste abre a página, preenche campos pelo rótulo acessível, clica no botão e verifica URL e título. As asserções do Playwright também esperam automaticamente até que a condição seja atendida ou o tempo limite seja alcançado.
Escolhendo seletores confiáveis
Seletores são uma das principais causas de testes frágeis. Evite depender de classes de estilo, estruturas profundas de CSS ou posições como “o terceiro botão da lista”. Essas características mudam com facilidade e nem sempre representam o comportamento esperado pelo usuário.
Prefira locators baseados em acessibilidade:
getByRole()para botões, links, títulos e outros papéis;getByLabel()para campos associados a rótulos;getByPlaceholder()quando o placeholder é estável;getByText()para conteúdo visível específico;getByTestId()quando não existe uma alternativa semântica confiável.
A página de locators na documentação oficial explica como compor seletores e por que atributos acessíveis costumam ser mais resistentes.
Entendendo a espera automática
Um erro comum em ferramentas de automação é adicionar pausas fixas:
await page.waitForTimeout(3000);Essa abordagem deixa a suíte lenta e ainda pode falhar quando três segundos não são suficientes. Com Playwright, ações como click e fill verificam condições necessárias antes de continuar. Asserções como toBeVisible repetem a consulta até o resultado esperado.
Use esperas explícitas somente quando existe uma condição real que não pode ser expressa por uma ação ou asserção. Por exemplo, você pode aguardar uma resposta específica da API:
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/orders') && response.status() === 201
);
await page.getByRole('button', { name: 'Finalizar pedido' }).click();
await responsePromise;Organizando preparação com fixtures
Fixtures fornecem recursos preparados para cada teste. O Playwright já inclui page, context, browser e request. Você também pode criar fixtures próprias para páginas autenticadas, dados de teste ou clientes de API.
Para uma preparação simples, use hooks:
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/products');
});
test('lista produtos', async ({ page }) => {
await expect(page.getByRole('list')).toBeVisible();
});Evite compartilhar estado mutável entre testes. Cada caso deve conseguir executar sozinho e em qualquer ordem. Essa independência é essencial quando a suíte roda em paralelo.
Reutilizando autenticação
Fazer login pela interface em todos os testes aumenta o tempo e pode tornar a suíte instável. Uma estratégia melhor é autenticar uma vez, salvar o estado do navegador e reutilizá-lo.
Um projeto de configuração pode realizar o login e salvar cookies e armazenamento local:
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('autenticar usuário', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('E-mail').fill(process.env.E2E_EMAIL!);
await page.getByLabel('Senha').fill(process.env.E2E_PASSWORD!);
await page.getByRole('button', { name: 'Entrar' }).click();
await expect(page).toHaveURL(/dashboard/);
await page.context().storageState({ path: authFile });
});Não envie o arquivo de autenticação ao repositório. Ele pode conter cookies e tokens ativos. Adicione a pasta ao .gitignore e use credenciais exclusivas para testes, com permissões limitadas.
Simulando respostas de rede
Alguns fluxos dependem de serviços externos, dados difíceis de reproduzir ou situações de erro. O método page.route() permite interceptar requisições:
test('mostra mensagem quando a API falha', async ({ page }) => {
await page.route('**/api/profile', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'indisponível' })
});
});
await page.goto('/profile');
await expect(
page.getByText('Não foi possível carregar o perfil')
).toBeVisible();
});Mocks tornam cenários previsíveis, mas não devem substituir todos os testes contra integrações reais. Mantenha uma combinação: testes rápidos com respostas controladas e alguns fluxos completos que validam contratos importantes.
Testando múltiplos navegadores e dispositivos
Os projetos definidos na configuração permitem executar o mesmo teste em engines diferentes. Isso ajuda a encontrar incompatibilidades que não aparecem no navegador usado diariamente pela equipe.
Para executar apenas Chromium:
npx playwright test --project=chromiumTambém é possível usar perfis de dispositivos fornecidos pelo Playwright. Eles configuram viewport, user agent e outras características. Ainda assim, a emulação não substitui completamente testes em dispositivos reais quando o projeto depende de recursos específicos de hardware ou do sistema operacional.
Depurando testes que falham
Uma falha E2E pode envolver interface, rede, dados ou tempo. Por isso, uma mensagem isolada nem sempre explica a causa. Execute um teste em modo debug:
npx playwright test tests/login.spec.ts --debugO Playwright Inspector permite avançar passo a passo e examinar locators. Outra ferramenta importante é o Trace Viewer, que registra snapshots, ações, console e requisições. Para abrir um trace salvo:
npx playwright show-trace trace.zipConsulte o guia oficial do Trace Viewer para interpretar cada painel. Em integração contínua, preserve traces, screenshots e relatórios como artefatos do pipeline.
Page Object Model: quando usar?
O Page Object Model encapsula operações de uma tela em uma classe. Ele pode reduzir repetição, mas também pode esconder demais o comportamento se aplicado sem critério.
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly email: Locator;
readonly password: Locator;
readonly submit: Locator;
constructor(page: Page) {
this.page = page;
this.email = page.getByLabel('E-mail');
this.password = page.getByLabel('Senha');
this.submit = page.getByRole('button', { name: 'Entrar' });
}
async login(email: string, password: string) {
await this.page.goto('/login');
await this.email.fill(email);
await this.password.fill(password);
await this.submit.click();
}
}Use page objects quando várias suítes repetem operações estáveis. Para testes pequenos, funções auxiliares simples podem ser suficientes. Evite criar métodos genéricos demais, como clickButton(), que não expressam a intenção do fluxo.
Executando no GitHub Actions
Uma suíte E2E deve rodar automaticamente antes de mudanças importantes chegarem à produção. Um workflow básico instala dependências e navegadores:
name: Testes E2E
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/Guarde senhas e tokens em secrets do repositório. Nunca escreva credenciais diretamente no YAML. O artigo sobre GitHub Actions ajuda a estruturar pipelines, e o guia sobre Docker é útil quando você precisa reproduzir dependências de forma consistente.
Boas práticas para uma suíte confiável
- Teste jornadas importantes: priorize login, compra, cadastro, pagamento e permissões críticas.
- Use dados controlados: crie e limpe registros de teste de forma previsível.
- Mantenha independência: um teste não deve depender do resultado do anterior.
- Prefira seletores semânticos: papéis e rótulos refletem melhor o uso da interface.
- Evite pausas fixas: espere condições observáveis.
- Separe cenários: um teste deve ter objetivo claro e falha fácil de diagnosticar.
- Use retries com moderação: repetição pode revelar instabilidade, mas não deve esconder defeitos.
- Capture evidências: relatórios, screenshots e traces reduzem o tempo de investigação.
- Proteja credenciais: use contas próprias para teste e permissões mínimas.
- Revise acessibilidade: bons locators por papel e rótulo incentivam uma interface mais acessível.
Ao testar formulários e autenticação, combine a automação com práticas de segurança. O artigo sobre proteção de aplicações web apresenta riscos que não devem ser ignorados apenas porque o fluxo funcional passou.
Erros comuns
- Testar detalhes visuais frágeis: mudanças de CSS quebram a suíte sem alterar o comportamento.
- Reaproveitar o mesmo usuário em paralelo: testes modificam dados uns dos outros.
- Executar somente localmente: diferenças do CI ficam sem detecção.
- Mockar todas as APIs: integrações reais deixam de ser verificadas.
- Ignorar falhas intermitentes: testes instáveis reduzem a confiança da equipe.
- Acumular testes longos: casos enormes são difíceis de manter e diagnosticar.
- Salvar credenciais no código: o repositório passa a expor acesso sensível.
Conclusão
O Playwright oferece uma base completa para automatizar jornadas reais em aplicações web. Com locators semânticos, espera automática, execução em múltiplos navegadores e ferramentas de trace, a equipe consegue criar testes E2E mais confiáveis e investigar falhas com menos tentativa e erro.
Comece com poucos fluxos que representam alto risco para o negócio. Mantenha os testes independentes, controle os dados, execute no CI e preserve evidências quando houver falhas. À medida que a suíte crescer, extraia fixtures e page objects somente onde eles realmente melhorarem a leitura. Assim, os testes deixam de ser scripts frágeis e passam a funcionar como uma camada prática de proteção contra regressões.




