Une assertion est la partie d'un test qui teste vraiment quelque chose. Sans assertions, vous cliquez simplement sur des éléments en espérant que rien ne plante.
Comment fonctionnent les assertions Playwright
Les assertions Playwright utilisent la fonction expect() de @playwright/test. Ce n'est pas le même expect que celui de Jest. La version Playwright est asynchrone et dispose d'une logique de retry intégrée.
import { test, expect } from '@playwright/test';La différence clé par rapport à la plupart des frameworks : les assertions Playwright réessaient automatiquement. Quand vous écrivez :
await expect(page.getByText('Welcome')).toBeVisible();Playwright ne vérifie pas une seule fois. Il vérifie à répétition pendant 5 secondes maximum (le expect timeout par défaut), en attendant que la condition devienne vraie. Cela élimine le besoin d'appels manuels à waitFor dans 90 % des cas.
Si la condition ne devient jamais vraie dans le délai imparti, le test échoue avec un message clair indiquant ce qui était attendu et ce qui existait réellement.
Assertions sur les éléments (basées sur les locators)
Ces assertions vérifient les propriétés d'un élément spécifique de la page.
toBeVisible / toBeHidden
// L'élément est rendu et visible pour l'utilisateur
await expect(page.getByText('Dashboard')).toBeVisible();
// L'élément est absent, ou présent mais caché (display:none, visibility:hidden, opacity:0)
await expect(page.getByRole('dialog')).toBeHidden();toBeHidden() est vrai si l'élément n'existe pas OU s'il existe mais est invisible. Utilisez not.toBeAttached() si vous devez confirmer spécifiquement que l'élément n'est pas dans le DOM.
toHaveText / toContainText
// Correspondance exacte du texte (supprime automatiquement les espaces)
await expect(page.getByRole('heading', { level: 1 })).toHaveText('My Travel Items');
// Correspondance partielle du texte
await expect(page.getByRole('heading')).toContainText('Travel');
// Tableau : vérifier le texte de plusieurs éléments
await expect(page.getByRole('listitem')).toHaveText(['Tokyo', 'Paris', 'London']);
// Regex : correspondance par pattern
await expect(page.getByTestId('price')).toHaveText(/\$\d+\.\d{2}/);toHaveText avec un tableau correspond au texte complet de chaque élément dans l'ordre. Très utile pour vérifier des lignes de tableau ou des listes triées.
toHaveValue
Pour les éléments , , :
// Champ texte
await expect(page.getByLabel('Email')).toHaveValue('admin@becomeqa.com');
// Champ vide
await expect(page.getByLabel('Search')).toHaveValue('');
// Menu déroulant
await expect(page.getByLabel('Status')).toHaveValue('completed');toBeChecked / not.toBeChecked
Pour les cases à cocher et les boutons radio :
await expect(page.getByLabel('Se souvenir de moi')).toBeChecked();
await expect(page.getByLabel('S\'abonner à la newsletter')).not.toBeChecked();toBeEnabled / toBeDisabled
// Le bouton est cliquable
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
// Le bouton est grisé ou a l'attribut disabled
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled();Utilisez ceci pour vérifier qu'un bouton de soumission de formulaire ne devient activé qu'après le remplissage des champs obligatoires.
toBeEditable / toBeReadOnly
await expect(page.getByLabel('Username')).toBeEditable();
await expect(page.getByLabel('Created At')).toBeReadOnly();toHaveAttribute
Vérifier n'importe quel attribut HTML :
// href sur un lien
await expect(page.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/');
// data-testid
await expect(page.getByTestId('status-badge')).toHaveAttribute('data-status', 'active');
// aria-label
await expect(page.getByRole('button', { name: 'Close' })).toHaveAttribute('aria-label', 'Close dialog');
// Correspondance regex sur la valeur
await expect(page.getByRole('img')).toHaveAttribute('src', /\/images\/.+\.svg/);toHaveClass
// L'élément a cette classe CSS
await expect(page.getByTestId('alert')).toHaveClass(/error/);
// Liste de classes exacte
await expect(page.getByTestId('button')).toHaveClass('btn btn-primary active');toHaveClass vérifie si la classe est présente, pas si c'est la seule classe. Utilisez une regex pour les correspondances partielles.
toHaveCount
Pour les collections d'éléments :
// Le tableau a une ligne d'en-tête + 5 lignes de données
await expect(page.getByRole('row')).toHaveCount(6);
// Le menu déroulant a 4 options
await expect(page.getByRole('option')).toHaveCount(4);
// Pas de messages d'erreur
await expect(page.getByRole('alert')).toHaveCount(0);toHaveCSS
Vérifier les propriétés CSS calculées :
await expect(page.getByTestId('error-message')).toHaveCSS('color', 'rgb(220, 38, 38)');
await expect(page.getByRole('dialog')).toHaveCSS('display', 'flex');Utilisez les valeurs calculées (rgb(...)) et non les variables CSS ou les propriétés raccourcies.
Assertions sur la page
Ces assertions vérifient les propriétés de la page entière, pas d'un élément spécifique.
toHaveURL
// URL exacte
await expect(page).toHaveURL('https://lab.becomeqa.com/dashboard');
// Correspondance partielle avec regex
await expect(page).toHaveURL(/\/dashboard/);
// Avec baseURL configurée dans playwright.config.ts
await expect(page).toHaveURL('/dashboard');Utilisez ceci après une navigation pour confirmer que vous êtes arrivé où vous l'attendiez.
toHaveTitle
await expect(page).toHaveTitle('My Travel Items | BecomeQA Lab');
await expect(page).toHaveTitle(/BecomeQA/);toHaveScreenshot (régression visuelle)
// La première exécution crée la capture de référence
// Les exécutions suivantes comparent avec elle
await expect(page).toHaveScreenshot('dashboard.png');
// Avec options
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixels: 100, // autoriser des différences mineures de pixels
threshold: 0.2, // échelle 0-1 de tolérance de différence de couleur
});Test de régression visuelle : la première exécution sauvegarde une image de référence. Chaque exécution suivante compare l'état actuel à cette référence. Échoue si les images diffèrent au-delà du seuil.
Assertions sur les réponses API
Avec la fixture request pour les tests d'API :
test('GET /api/items retourne des données valides', async ({ request }) => {
const response = await request.get('/api/items');
// Code de statut
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy(); // vrai pour les codes 200-299
// Corps de la réponse
const items = await response.json();
expect(items).toHaveLength(5);
expect(items[0]).toHaveProperty('id');
expect(items[0]).toHaveProperty('title');
expect(items[0].status).toBe('planned');
// En-têtes
expect(response.headers()['content-type']).toContain('application/json');
});response.ok() est une méthode intégrée (pas une assertion) qui retourne vrai pour les codes de statut 2xx.
Assertions sur les valeurs génériques
Pour les valeurs non-élément comme les variables, réponses API, valeurs calculées :
// Égalité
expect(items.length).toBe(5);
expect(user.role).toBe('admin');
// Inégalité
expect(errorCode).not.toBe(0);
// Truthy/falsy
expect(isVisible).toBeTruthy();
expect(errorMessage).toBeFalsy();
// Null/undefined
expect(result).toBeNull();
expect(result).not.toBeNull();
expect(result).toBeDefined();
expect(result).toBeUndefined();
// Nombres
expect(count).toBeGreaterThan(0);
expect(price).toBeGreaterThanOrEqual(9.99);
expect(discount).toBeLessThan(100);
// Tableaux
expect(statuses).toContain('completed');
expect(items).toHaveLength(3);
expect(tags).toEqual(['qa', 'automation', 'playwright']); // correspondance exacte du tableau
// Objets
expect(user).toMatchObject({ email: 'admin@becomeqa.com', role: 'admin' }); // correspondance partielle
expect(user).toEqual({ id: 1, email: 'admin@becomeqa.com', role: 'admin' }); // correspondance exacte
// Chaînes
expect(message).toContain('success');
expect(slug).toMatch(/^[a-z0-9-]+$/);Négation : not
Toute assertion peut être niée avec .not :
await expect(page.getByRole('dialog')).not.toBeVisible();
await expect(page.getByText('Error')).not.toBeAttached();
expect(response.status()).not.toBe(404);Assertions soft : collecter tous les échecs
Par défaut, la première assertion qui échoue arrête le test. Les assertions soft continuent même après un échec et rapportent tous les échecs à la fin :
test('les données du tableau de bord sont correctes', async ({ page }) => {
await page.goto('/dashboard');
// Ces assertions ne s'arrêtent pas au premier échec
await expect.soft(page.getByRole('heading')).toHaveText('My Travel Items');
await expect.soft(page.getByRole('row')).toHaveCount(6);
await expect.soft(page).toHaveURL('/dashboard');
// Ceci lève une erreur si une assertion soft ci-dessus a échoué
expect(test.info().errors).toHaveLength(0);
});Utilisez les assertions soft quand vous voulez voir tout ce qui est cassé en une seule exécution de test, pas seulement le premier échec.
Messages d'assertion personnalisés
Quand une assertion échoue, Playwright montre ce qui était attendu vs. ce qui a été trouvé. Vous pouvez ajouter un message personnalisé pour rendre les échecs plus lisibles :
await expect(page.getByRole('heading'), 'La page doit afficher le tableau de bord après connexion')
.toHaveText('My Travel Items');
expect(items.length, `Attendu 5 éléments mais trouvé ${items.length}`)
.toBe(5);Configurer les timeouts
Le timeout d'assertion par défaut est de 5 secondes. Vous pouvez le modifier par assertion ou globalement :
// Par assertion (10 secondes pour une opération lente)
await expect(page.getByText('Rapport prêt')).toBeVisible({ timeout: 10000 });
// Global dans playwright.config.ts
export default defineConfig({
expect: {
timeout: 10000, // toutes les assertions attendent jusqu'à 10 secondes
},
});Ne pas augmenter les timeouts pour corriger des tests flaky. Un test flaky avec un timeout plus long reste flaky. Il échoue juste plus lentement. Corrigez la cause racine.
Erreurs courantes
Utiliserpage.locator().isVisible() au lieu de expect().toBeVisible()
// Mauvais : vérifie une seule fois, pas de retry, retourne un booléen
const visible = await page.getByText('Success').isVisible();
expect(visible).toBe(true);
// Correct : réessaie jusqu'à ce que ce soit visible ou timeout
await expect(page.getByText('Success')).toBeVisible();La première version peut échouer de façon intermittente car elle vérifie une seule fois. La deuxième réessaie.
Asserter sur des locators périmés// Ne pas sauvegarder des locators avant la navigation et les asserter après
const heading = page.getByRole('heading');
await page.goto('/new-page');
await expect(heading).toHaveText('New Page'); // peut être périmé
// Mieux : créer le locator près de l'assertion
await page.goto('/new-page');
await expect(page.getByRole('heading')).toHaveText('New Page');expect(await locator.textContent()).toBe(...) au lieu de toHaveText
// Mauvais : évalue une seule fois, pas de retry
expect(await page.getByRole('heading').textContent()).toBe('Dashboard');
// Correct : réessaie avec auto-attente
await expect(page.getByRole('heading')).toHaveText('Dashboard');Si une liste charge de façon asynchrone, assertez le nombre après avoir asserté que la liste est visible :
await expect(page.getByRole('list')).toBeVisible(); // attendre l'apparition de la liste
await expect(page.getByRole('listitem')).toHaveCount(5); // puis compter les élémentsUn test complet utilisant plusieurs types d'assertions
import { test, expect } from '@playwright/test';
test('l\'utilisateur peut ajouter et voir un élément de voyage', async ({ page }) => {
await page.goto('/');
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();
// Assertion sur la page : vérifier la navigation
await expect(page).toHaveURL('/dashboard');
// Assertion de visibilité
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
// Assertion de texte
await expect(page.getByRole('heading', { level: 1 })).toHaveText('My Travel Items');
// Ajouter un nouvel élément
await page.getByRole('button', { name: 'Add Item' }).click();
await page.getByLabel('Destination').fill('Tokyo');
await page.getByRole('button', { name: 'Save' }).click();
// Assertion de comptage : le tableau doit avoir une ligne de plus
const rows = page.getByRole('row');
await expect(rows).toHaveCount(7); // en-tête + 6 éléments
// Assertion sur le contenu textuel de la nouvelle ligne
await expect(page.getByRole('cell', { name: 'Tokyo' })).toBeVisible();
// Assertion de statut sur le nouvel élément
const tokyoRow = page.getByRole('row').filter({ hasText: 'Tokyo' });
await expect(tokyoRow.getByRole('cell').last()).toHaveText('Planned');
});FAQ
Pourquoi mon assertion passe en local mais échoue en CI ?Le timing. Les machines CI sont plus lentes. L'élément existe mais prend plus de temps à apparaître. Augmentez le timeout d'assertion dans playwright.config.ts ou cherchez pourquoi l'élément charge lentement dans l'environnement CI.
toEqual et toBe ?
toBe vérifie l'égalité référentielle (même objet en mémoire, ou primitives identiques). toEqual vérifie l'égalité profonde (même structure et valeurs, fonctionne pour les objets et tableaux). Pour comparer des objets et tableaux, utilisez toEqual. Pour les chaînes, nombres et booléens, utilisez toBe.
Quand utiliser toMatchObject plutôt que toEqual ?
toMatchObject est une correspondance partielle. L'objet réel peut avoir plus de propriétés que celles spécifiées. toEqual requiert une correspondance exacte. Pour les réponses API où vous voulez vérifier des champs clés sans lister chaque champ, utilisez toMatchObject.
Mon toHaveText échoue parce que le texte réel a des espaces supplémentaires. Comment corriger ?
toHaveText supprime automatiquement les espaces au début et à la fin. Pour les espaces internes (plusieurs espaces, sauts de ligne), utilisez une regex : toHaveText(/destination:\s+Tokyo/i).
→ See also: Fixtures Personnalisés dans Playwright: Le Modèle qui Rend les Tests Lisibles | Déboguer les Tests Instables: Un Guide Pratique