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:
awaitsó pode ser usado dentro de funçõesasync- Funções
asyncsempre retornam uma Promise - Se uma função
asyncretorna 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.
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 perfilSe 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;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.