La plus grande source de confusion pour les ingénieurs QA qui apprennent Playwright n'est pas les locateurs ou les assertions. C'est async et await. Si vous avez déjà vu un test qui "réussit" mais ne fait pas ce que vous pensez, voici ce qui se passe. Pareil pour un test qui échoue avec une erreur cryptique sur les Promises.
Le problème que async/await résout
JavaScript est non-bloquant par défaut. Quand vous demandez au navigateur de faire quelque chose comme naviguer, cliquer ou charger une page, JavaScript n'attend pas. Il démarre l'opération et continue immédiatement.
C'est un problème pour les tests. Vous devez naviguer vers la page avant de cliquer sur le bouton. Vous devez cliquer avant de vérifier le résultat.
Avant qu'async/await existe, la solution était les callbacks et les Promises. Ça fonctionne, mais ça produit du code enchevêtré difficile à lire et déboguer. async/await est la solution moderne : elle fait paraître et se comporter les opérations asynchrones comme des opérations séquentielles.
L'explication la plus simple
await signifie : "attends ici que cette opération se termine, puis continue."
// Sans await : n'attend pas, échoue ou se comporte de façon inattendue
test('exemple cassé', async ({ page }) => {
page.goto('https://lab.becomeqa.com'); // démarre la navigation, continue immédiatement
page.getByRole('button', { name: 'Login' }).click(); // tente de cliquer avant le chargement
});
// Avec await : attend que chaque étape se termine
test('exemple correct', async ({ page }) => {
await page.goto('https://lab.becomeqa.com'); // attendre la fin de la navigation
await page.getByRole('button', { name: 'Login' }).click(); // puis cliquer
});Le deuxième test fonctionne parce que chaque await met l'exécution en pause jusqu'à la fin de l'opération.
Ce que async signifie
async avant une fonction signifie que cette fonction peut utiliser await à l'intérieur et renverra une Promise :
// Cette fonction est async : elle utilise await
test('test de connexion', async ({ page }) => {
await page.goto('https://lab.becomeqa.com');
// ...
});Dans les tests Playwright, toutes les fonctions de test sont async automatiquement parce qu'elles impliquent toutes des opérations de navigateur. Le test runner Playwright gère ça. Il faut juste se rappeler de mettre await avant les opérations.
La règle : quand utiliser await dans Playwright
Utilisez await avant toute méthode Playwright qui interagit avec le navigateur (navigation, clics, remplissage, sélections). Faites pareil pour les méthodes qui récupèrent des informations de la page (contenu texte, valeurs d'attributs, comptages) ou qui font des assertions (expect(...).toBeVisible()).
test('démonstration de l\'utilisation de await', async ({ page }) => {
// Navigation — await
await page.goto('https://lab.becomeqa.com');
// Actions — await
await page.getByRole('button', { name: 'Login' }).click();
await page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByLabel('Password').fill('testpass123');
await page.getByRole('button', { name: 'Submit' }).click();
// Assertions — await
await expect(page.getByRole('heading', { name: 'My Travel Items' })).toBeVisible();
// Récupérer des valeurs de la page — await
const heading = await page.getByRole('heading').textContent();
console.log(heading); // 'My Travel Items'
// Vérifier la visibilité — await
const isVisible = await page.getByRole('button', { name: 'Add Item' }).isVisible();
expect(isVisible).toBe(true);
});Ce qui se passe si vous oubliez await
Les conséquences dépendent de sur quoi vous l'avez oublié.
Oublié await sur une action :// Remplit le champ et continue immédiatement — fonctionne parfois par accident
// mais échouera sous toute charge ou réseau lent
page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByRole('button', { name: 'Submit' }).click(); // peut cliquer avant la fin du remplissage// Erreur très courante : l'assertion évalue en un objet Promise
// qui est toujours truthy, donc le test réussit toujours même quand il devrait échouer
expect(page.getByText('Erreur')).toBeVisible(); // INCORRECT — await manquant, le test réussit toujours
await expect(page.getByText('Erreur')).toBeVisible(); // CORRECTCe dernier cas est particulièrement dangereux : des tests qui réussissent toujours sont pires que des tests qui échouent toujours. TypeScript aide à détecter ça. En mode strict, TypeScript vous avertit quand vous avez une Promise non attendue dans une assertion.
Stocker des valeurs avec await
Quand vous avez besoin de capturer ce que Playwright renvoie :
test('vérification des valeurs', async ({ page }) => {
await page.goto('https://lab.becomeqa.com');
// Stocker le contenu texte
const title = await page.getByRole('heading').textContent();
// title est maintenant une chaîne : 'My Travel Items'
// Stocker un comptage
const rowCount = await page.getByRole('row').count();
// rowCount est un nombre : 5
// Stocker l'état de visibilité
const isButtonVisible = await page.getByRole('button', { name: 'Add Item' }).isVisible();
// isButtonVisible est un booléen : true ou false
// Maintenant les utiliser
expect(title).toBe('My Travel Items');
expect(rowCount).toBeGreaterThan(0);
expect(isButtonVisible).toBe(true);
});Le pattern est toujours : const result = await page.someMethod().
Fonctions async en dehors des tests
Quand vous écrivez des fonctions helper ou des méthodes de Page Object qui utilisent Playwright, elles doivent aussi être async :
// Méthode de Page Object — doit être async
class LoginPage {
constructor(private page: Page) {}
async login(email: string, password: string) {
await this.page.getByRole('button', { name: 'Login' }).click();
await this.page.getByLabel('Username').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Submit' }).click();
}
}
// Quand vous appelez une méthode async, vous avez besoin de await
test('test de connexion', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('admin@becomeqa.com', 'testpass123'); // await la méthode async
});La règle : si une fonction utilise await à l'intérieur, déclarez-la async. Si vous appelez une fonction async, mettez await avant l'appel.
Opérations parallèles avec Promise.all
Parfois vous voulez que deux choses se produisent simultanément. Promise.all exécute plusieurs opérations async en parallèle et attend qu'elles soient toutes terminées :
// Attendre la navigation ET un élément spécifique simultanément
// C'est plus rapide que les attendre séquentiellement
await Promise.all([
page.waitForURL('/dashboard'),
expect(page.getByRole('heading', { name: 'My Travel Items' })).toBeVisible(),
]);Dans Playwright, l'utilisation la plus courante est d'attendre une navigation et une assertion ensemble après avoir cliqué sur quelque chose. Playwright gère la plupart de ça automatiquement via l'auto-attente, mais vous verrez Promise.all dans du code plus ancien et dans des scénarios de timing spécifiques.
Ce que les Promises sont réellement (en bref)
Quand vous voyez quelque chose comme :
const textContent = page.getByRole('heading').textContent();
// textContent est maintenant une Promise<string>, pas une stringUne Promise est un espace réservé pour une valeur qui n'existe pas encore, parce que l'opération est toujours en cours. await déballe la Promise, mettant l'exécution en pause jusqu'à ce que la valeur soit prête :
const textContent = await page.getByRole('heading').textContent();
// Maintenant textContent est une stringVous n'avez pas besoin de comprendre les Promises en profondeur pour écrire des tests Playwright. Le modèle mental "await fait attendre" suffit pour 95% du code de test. Retenez juste que chaque méthode Playwright renvoie une Promise, donc vous avez besoin de await.
TypeScript vous aide à ne pas oublier
TypeScript sait quelles méthodes renvoient des Promises. Si vous oubliez await, TypeScript vous avertit :
Type 'Promise<string>' is not assignable to type 'string'C'est l'une des raisons d'utiliser TypeScript pour les tests Playwright : le compilateur détecte les await manquants avant que vos tests ne s'exécutent.
Référence rapide
| Ce que vous faites | Pattern |
|---|---|
| Naviguer | await page.goto(url) |
| Cliquer | await element.click() |
| Remplir un champ | await element.fill('texte') |
| Vérifier la visibilité | await expect(element).toBeVisible() |
| Obtenir du texte | const text = await element.textContent() |
| Obtenir un comptage | const n = await elements.count() |
| Appeler une fonction async | await maFonctionAsync() |
| Exécuter en parallèle | await Promise.all([op1, op2]) |
FAQ
Ai-je besoin de comprendre comment fonctionne la boucle d'événements JavaScript ?Non, pas pour écrire des tests Playwright. "await fait attendre le résultat" suffit. La boucle d'événements est le mécanisme sous-jacent, mais vous n'avez pas besoin de comprendre le mécanisme pour utiliser l'outil.
Pourquoi mon test réussit-il même quand j'ai oublié await sur expect ?Parce que expect(element).toBeVisible() sans await renvoie un objet Promise (qui est truthy), et le test runner ne l'évalue pas comme une assertion. Le test ne voit aucun échec. C'est un bug silencieux. Le mode strict de TypeScript le signale.
Oui, .then() est l'ancienne syntaxe des Promises. Ça fonctionne mais produit du code plus difficile à lire :
// Ancienne syntaxe Promise
page.goto('https://lab.becomeqa.com').then(() => {
return page.getByRole('button', { name: 'Login' }).click();
}).then(() => {
// ...
});
// Équivalent avec async/await — bien plus clair
await page.goto('https://lab.becomeqa.com');
await page.getByRole('button', { name: 'Login' }).click();Utilisez async/await. Évitez les chaînes .then() dans le nouveau code de test.
Promise en TypeScript. Qu'est-ce que ça signifie ?
void signifie que la fonction ne renvoie pas de valeur significative. Elle fait juste quelque chose (comme naviguer ou cliquer). Vous avez quand même besoin de await, mais vous ne stockez pas le résultat dans une variable.
→ See also: TypeScript pour QA: Pourquoi les Types Statiques Améliorent Vos Tests | Débuter avec Playwright: Vos Premiers Tests en 30 Minutes