test.beforeEach roda o setup antes de cada teste no seu escopo. Código de login escrito uma vez dentro de um bloco describe serve a todos os testes dentro dele sem afetar os de fora. test.beforeAll roda uma vez para o grupo inteiro, não uma vez por teste. Um token de auth que ele cria fica compartilhado entre todos os testes do arquivo, o que causa interferência quando esses testes rodam em paralelo.

O teste básico

Antes de adicionar estrutura, um teste simples tem essa cara:

import { test, expect } from '@playwright/test';

test('usuário consegue fazer login', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.click('[data-testid="submit"]');
  await expect(page).toHaveURL('/dashboard');
});

Simples. Mas se você tem 10 testes que começam com os mesmos passos de login, são 10 blocos duplicados. Quando o fluxo de login muda, você atualiza 10 lugares em vez de um.

test.beforeEach — rodar antes de cada teste

beforeEach executa código de setup antes de cada teste no seu escopo. O uso mais comum: navegar para uma página ou fazer login.

import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.click('[data-testid="submit"]');
  await page.waitForURL('/dashboard');
});

test('dashboard mostra o nome do usuário', async ({ page }) => {
  // Já autenticado e no /dashboard
  await expect(page.getByTestId('user-name')).toContainText('Test User');
});

test('usuário consegue acessar as configurações', async ({ page }) => {
  // Também já autenticado
  await page.getByTestId('settings-link').click();
  await expect(page).toHaveURL('/settings');
});

Cada teste começa com o usuário já autenticado e no dashboard. Sem duplicação.

test.afterEach — limpar após cada teste

afterEach roda após cada teste, independente de ter passado ou falhado. Use para limpeza que precisa acontecer depois de cada teste.

test.afterEach(async ({ page }, testInfo) => {
  // Captura screenshot na falha (o Playwright também faz isso via config)
  if (testInfo.status !== testInfo.expectedStatus) {
    await page.screenshot({ path: `screenshots/${testInfo.title}.png` });
  }
});

Ou para limpeza via API:

let createdUserId: number;

test.beforeEach(async ({ request }) => {
  const response = await request.post('/api/users', {
    data: { email: 'temp@test.com', password: 'Pass1' },
  });
  const body = await response.json();
  createdUserId = body.id;
});

test.afterEach(async ({ request }) => {
  // Deleta o usuário criado no setup
  await request.delete(`/api/users/${createdUserId}`);
});

test.describe — agrupar testes relacionados

test.describe cria um grupo nomeado de testes. Útil para:

  • Agrupar testes por feature ou página
  • Aplicar beforeEach/afterEach apenas a um subconjunto de testes
  • Aninhar cenários de teste relacionados
import { test, expect } from '@playwright/test';

test.describe('Página de login', () => {
  test('mostra os campos de email e senha', async ({ page }) => {
    await page.goto('/login');
    await expect(page.getByTestId('email')).toBeVisible();
    await expect(page.getByTestId('password')).toBeVisible();
  });

  test('mostra erro com senha errada', async ({ page }) => {
    await page.goto('/login');
    await page.fill('[data-testid="email"]', 'user@test.com');
    await page.fill('[data-testid="password"]', 'SenhaErrada');
    await page.click('[data-testid="submit"]');
    await expect(page.getByTestId('error-message')).toBeVisible();
  });
});

test.describe('Dashboard', () => {
  test.beforeEach(async ({ page }) => {
    // Login antes de cada teste de dashboard
    await page.goto('/login');
    await page.fill('[data-testid="email"]', 'user@test.com');
    await page.fill('[data-testid="password"]', 'ValidPass1');
    await page.click('[data-testid="submit"]');
    await page.waitForURL('/dashboard');
  });

  test('mostra o nome do usuário', async ({ page }) => {
    await expect(page.getByTestId('user-name')).toBeVisible();
  });

  test('mostra pedidos recentes', async ({ page }) => {
    await expect(page.getByTestId('orders-section')).toBeVisible();
  });
});

O beforeEach de login só se aplica ao bloco describe do Dashboard. Os testes de login não são afetados.

test.beforeAll e test.afterAll

beforeAll roda uma vez antes de todos os testes no seu escopo (não antes de cada um). afterAll roda uma vez depois de todos.

Caso de uso: setup caro que só precisa acontecer uma vez, como criar um usuário de teste via API, popular um banco de dados ou iniciar um servidor.

import { test, expect, request as playwrightRequest } from '@playwright/test';

let authToken: string;

test.beforeAll(async () => {
  // Cria o token de auth uma vez para todos os testes do arquivo
  const ctx = await playwrightRequest.newContext();
  const response = await ctx.post('/api/auth/login', {
    data: { email: 'admin@test.com', password: 'AdminPass1' },
  });
  const body = await response.json();
  authToken = body.token;
  await ctx.dispose();
});

test('admin consegue ver todos os usuários', async ({ request }) => {
  const response = await request.get('/api/admin/users', {
    headers: { Authorization: `Bearer ${authToken}` },
  });
  expect(response.status()).toBe(200);
});

test('admin consegue deletar um usuário', async ({ request }) => {
  // Também usa authToken — compartilhado entre todos os testes
});

beforeAll e afterAll rodam em um contexto compartilhado. Mudanças no estado compartilhado persistem entre testes, o que pode causar testes flaky. Para a maioria dos setups, prefira beforeEach.

Blocos describe aninhados

Você pode aninhar blocos describe para criar uma hierarquia:

test.describe('Fluxo de checkout', () => {
  test.beforeEach(async ({ page }) => {
    await loginAsUser(page);
    await addItemToCart(page, 'product-123');
  });

  test.describe('com cartão válido', () => {
    test.beforeEach(async ({ page }) => {
      await fillShippingAddress(page);
      await fillValidCard(page, '4242 4242 4242 4242');
    });

    test('conclui a compra', async ({ page }) => {
      await page.getByTestId('place-order').click();
      await expect(page.getByTestId('order-confirmation')).toBeVisible();
    });

    test('envia email de confirmação', async ({ page }) => {
      // ...
    });
  });

  test.describe('com cartão inválido', () => {
    test('mostra mensagem de erro', async ({ page }) => {
      await fillValidCard(page, '0000 0000 0000 0000');
      await page.getByTestId('place-order').click();
      await expect(page.getByTestId('payment-error')).toBeVisible();
    });
  });
});

Ordem de execução dos hooks para um teste aninhado:

  1. beforeEach externo (login + adicionar ao carrinho)
  2. beforeEach interno (preencher endereço + cartão)
  3. O próprio teste
  4. afterEach interno (se houver)
  5. afterEach externo (se houver)

test.skip e test.only

Dois modificadores úteis durante o desenvolvimento:

// Pula este teste (marca como pulado, não falhado)
test.skip('feature ainda não implementada', async ({ page }) => {
  // ...
});

// Roda apenas este teste (ignora todos os outros no arquivo)
test.only('depurando este caso específico', async ({ page }) => {
  // ...
});

Nunca faça commit de test.only na branch principal. Ele faz toda a suite de CI falhar ao rodar apenas um teste.

Skip condicional — útil para testes específicos de ambiente:

test('painel de admin', async ({ page }) => {
  test.skip(process.env.ENV === 'production', 'Pulado em produção');
  // ...
});

Como os testes são nomeados nos relatórios

O nome do teste exibido nos relatórios combina o label do describe com o label do test:

test.describe('Página de login', () => {
  test('mostra erro com senha errada', async ({ page }) => { ... });
});
// Relatório mostra: "Página de login > mostra erro com senha errada"

Escreva nomes descritivos. Quando um CI mostrar 3 falhas, você vai precisar entendê-las sem abrir o código.

Exemplo de estrutura completa de arquivo

import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('Autenticação de usuário', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.navigate();
  });

  test.describe('Credenciais válidas', () => {
    test('redireciona para o dashboard', async ({ page }) => {
      await loginPage.login('user@test.com', 'ValidPass1');
      await expect(page).toHaveURL('/dashboard');
    });

    test('define o cookie de auth', async ({ page }) => {
      await loginPage.login('user@test.com', 'ValidPass1');
      const cookies = await page.context().cookies();
      expect(cookies.some(c => c.name === 'auth_token')).toBe(true);
    });
  });

  test.describe('Credenciais inválidas', () => {
    test('senha errada mostra erro', async () => {
      await loginPage.login('user@test.com', 'SenhaErrada');
      await expect(loginPage.errorMessage).toBeVisible();
    });

    test('email vazio mostra erro de validação', async () => {
      await loginPage.login('', 'ValidPass1');
      await expect(loginPage.emailError).toContainText('required');
    });
  });
});

Resumo

HookQuando rodaUse para
test.beforeEachAntes de cada teste no escopoNavegação, login, reset de estado
test.afterEachApós cada teste no escopoLimpeza, screenshots em falhas
test.beforeAllUma vez antes de todos os testes no escopoSetup caro que pode ser compartilhado
test.afterAllUma vez após todos os testes no escopoTeardown único
test.describe(agrupamento, não é hook)Organizar testes, escopar hooks

Comece com test.beforeEach para a maioria dos setups. Adicione test.describe para agrupar testes relacionados. Use test.beforeAll apenas quando o setup for genuinamente caro e seguro de compartilhar entre testes.

→ Veja também: Fixtures Personalizados no Playwright: O Padrão que Torna os Testes Legíveis | Isolamento de Testes: Por que Cada Teste Playwright Deve Ser sem Estado | Boas Práticas de Automação de Testes que Realmente Importam