Uma assertion expect() sem await no Playwright sempre passa, mesmo quando a condição é falsa: a assertion roda mas nunca espera a verificação terminar. Essa é a aresta mais cortante do async em código de testes.

O que é uma Promise?

Uma Promise representa um valor que ainda não está pronto: é um placeholder para algo que vai chegar no futuro.

// Uma Promise em três estados:
// 1. Pending    — aguardando o resultado
// 2. Fulfilled  — a operação teve sucesso, o valor está disponível
// 3. Rejected   — a operação falhou, o erro está disponível

const promise = fetch('/api/users');
// Neste ponto, a promise está PENDING

// Algum tempo depois, ela é:
// FULFILLED — a resposta chegou
// REJECTED  — erro de rede, erro do servidor

.then(): a forma antiga

Antes do async/await, Promises eram consumidas com .then():

fetch('/api/users')
  .then(response => response.json())
  .then(users => {
    console.log(users);  // Usa os dados
  })
  .catch(error => {
    console.error('Falhou:', error);
  });

Funciona bem para casos simples, mas fica confuso com lógica complexa. O async/await foi criado para resolver isso.

async/await: a forma moderna

async function getUsuarios() {
  try {
    const response = await fetch('/api/users');
    const usuarios = await response.json();
    return usuarios;
  } catch (error) {
    console.error('Falhou:', error);
    throw error;
  }
}

await pausa a execução até a Promise resolver. O código parece síncrono, mas roda de forma assíncrona. Regras:
  • await só pode ser usado dentro de funções async
  • Funções async sempre retornam uma Promise
  • Se uma função async retorna um valor, ele é embrulhado em uma Promise resolvida

O que acontece quando você esquece o await

Esse é o bug async mais comum no Playwright:

// BUG: esqueceu o await
test('teste de login', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', 'user@teste.com');
  
  page.click('[data-testid="submit"]');  // Sem await!
  
  // Esta assertion roda ANTES do clique terminar
  await expect(page).toHaveURL('/dashboard');  // Pode falhar de forma intermitente
});

Sem await, page.click() começa mas não espera terminar. A próxima linha roda enquanto o clique ainda está em andamento. No Playwright, isso causa testes flaky: às vezes o clique termina a tempo, às vezes não.

Correção:

await page.click('[data-testid="submit"]');

Execução paralela vs sequencial

Por padrão, await executa as coisas sequencialmente:

// Sequencial — tempo total: soma de todas as esperas
const usuario = await getUsuario(1);      // Espera o usuário
const pedidos = await getPedidos(1);      // Depois espera os pedidos
const perfil  = await getPerfil(1);       // Depois espera o perfil

Se as operações são independentes, rode em paralelo:

// Paralelo — tempo total: a espera mais longa
const [usuario, pedidos, perfil] = await Promise.all([
  getUsuario(1),
  getPedidos(1),
  getPerfil(1),
]);

Em fixtures do Playwright, esse padrão é usado para configurar múltiplas coisas de uma vez:

// Configurar dados de teste em paralelo
const [tokenAdmin, usuarioTeste] = await Promise.all([
  loginComoAdmin(request),
  criarUsuarioTeste(request),
]);

Promise.all: rodar múltiplas em paralelo

// As três requisições disparam simultaneamente
const resultados = await Promise.all([
  request.get('/api/users'),
  request.get('/api/products'),
  request.get('/api/orders'),
]);

// resultados é um array de respostas
const [respostaUsuarios, respostaProdutos, respostaPedidos] = resultados;

Importante: se QUALQUER promise em Promise.all rejeitar, tudo rejeita:

try {
  const [a, b, c] = await Promise.all([
    fetch('/api/users'),
    fetch('/api/vai-falhar-com-404'),  // Esta falha
    fetch('/api/products'),
  ]);
  // Nunca chega aqui se alguma falhar
} catch (error) {
  // Uma delas falhou
  console.error(error);
}

Promise.allSettled: aguardar todas, mesmo as que falham

const resultados = await Promise.allSettled([
  fetch('/api/users'),
  fetch('/api/pode-falhar'),
  fetch('/api/products'),
]);

// Cada resultado tem status + value ou reason
resultados.forEach(resultado => {
  if (resultado.status === 'fulfilled') {
    console.log('Sucesso:', resultado.value);
  } else {
    console.log('Falhou:', resultado.reason);
  }
});

Use allSettled quando quiser todos os resultados independente de falhas individuais.

Promise.race: o primeiro que ganhar

// Padrão de timeout com Promise.race
const fetchComTimeout = async (url, timeoutMs) => {
  const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), timeoutMs)
  );
  
  return Promise.race([fetch(url), timeout]);
};

try {
  const response = await fetchComTimeout('/api/lento', 5000);
} catch (error) {
  if (error.message === 'Timeout') {
    console.log('Requisição demorou demais');
  }
}

Tratamento de erros

try/catch com async/await

async function criarUsuario(dados: DadosUsuario) {
  try {
    const response = await request.post('/api/users', { data: dados });
    
    if (!response.ok()) {
      const body = await response.json();
      throw new Error(`Erro da API: ${body.message}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Falhou ao criar usuário:', error);
    throw error;  // Relança para que o chamador saiba que falhou
  }
}

Em testes Playwright

test('trata erro de API corretamente', async ({ page }) => {
  // Mocka a API para falhar
  await page.route('/api/users', route => route.fulfill({ status: 500 }));
  
  await page.goto('/users');
  
  await expect(page.getByTestId('error-message')).toBeVisible();
  await expect(page.getByTestId('error-message')).toContainText('Algo deu errado');
});

Bugs async comuns em testes Playwright

1. await faltando nas assertions

// BUG
expect(page.getByTestId('botao')).toBeVisible();  // Sem await — sempre passa!

// CORREÇÃO
await expect(page.getByTestId('botao')).toBeVisible();

2. await incorreto dentro de loops

// BUG — rodam em paralelo mas erros podem ficar sem tratamento
const itens = ['a', 'b', 'c'];
itens.forEach(async (item) => {
  await processarItem(item);  // Rodam em paralelo, sem await real
});

// CORREÇÃO — sequencial
for (const item of itens) {
  await processarItem(item);
}

// OU paralelo com tratamento correto
await Promise.all(itens.map(item => processarItem(item)));

3. Race conditions

// BUG — clique e navegação em corrida
await page.click('[data-testid="submit"]');
// Submit pode navegar, ou pode mostrar erro de validação
// A próxima linha pode rodar antes de sabermos qual aconteceu

// CORREÇÃO — espere explicitamente pelo que você espera
await Promise.all([
  page.waitForURL('/dashboard'),
  page.click('[data-testid="submit"]'),
]);
// Ou: aguarde a resposta
const [response] = await Promise.all([
  page.waitForResponse('/api/auth/login'),
  page.click('[data-testid="submit"]'),
]);

Async/await em Page Objects

Métodos de page object devem ser async quando aguardam algo:

class LoginPage {
  constructor(private page: Page) {}

  // Async porque navega
  async navigate(): Promise<void> {
    await this.page.goto('/login');
  }

  // Async porque executa ações
  async login(email: string, password: string): Promise<void> {
    await this.page.fill('[data-testid="email"]', email);
    await this.page.fill('[data-testid="password"]', password);
    await this.page.click('[data-testid="submit"]');
  }

  // Async porque lê da página
  async getMensagemErro(): Promise<string | null> {
    return this.page.getByTestId('error').textContent();
  }
}

// Uso — tudo com await
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@teste.com', 'SenhaValida1');
const erro = await loginPage.getMensagemErro();

Resumo

| Conceito | O que faz |

|---------|-----------|

| Promise | Placeholder para valor futuro |

| async | Marca uma função como assíncrona |

| await | Pausa até a Promise resolver |

| Promise.all() | Roda múltiplas em paralelo, aguarda todas |

| Promise.allSettled() | Como all(), mas continua mesmo em falhas |

| Promise.race() | Retorna a primeira a resolver/rejeitar |

| try/catch | Trata erros async |

A regra mais importante: sempre use await em ações e assertions do Playwright. await faltando é a causa número 1 de testes Playwright flaky. Se seu teste está falhando de forma intermitente sem razão óbvia, procure await ausente primeiro.

→ Veja também: Async/Await em Português Simples (para Testadores que se Perdem com Promises) | Testes Instáveis: Por que Acontecem e Como Eliminá-los | Tratamento de Erros JavaScript com try/catch para Engenheiros QA