Structure des tests et hooks avec Playwright

Ce chapitre présente comment les tests Playwright sont organisés, y compris le regroupement des tests, le contrôle d'exécution et l'utilisation des hooks de cycle de vie.


test.describe() Regroupement des tests

test.describe()Utilisé pour organiser les tests associés en groupes logiques.

Exemple

// Chemin du fichier : tests/describe-demo.spec.ts
import { test, expect } from '@playwright/test';

// Regroupement de premier niveau
test.describe('Test de la page d'accueil EXAMPLE', () => {

  test('Le titre de la page est correct', async ({ page }) => {
    await page.goto('https://www.example.com/');
    await expect(page).toHaveTitle(/EXAMPLE/);
  });

  test('La barre de navigation existe', async ({ page }) => {
    await page.goto('https://www.example.com/');
    // Vérifier que l'élément de navigation existe
    await expect(page.locator('nav')).toBeVisible();
  });
});

// Plusieurs blocs describe peuvent être définis
test.describe('Test de la fonctionnalité de recherche', () => {

  test('La zone de recherche est visible', async ({ page }) => {
    await page.goto('https://www.example.com/');
    await expect(
      page.getByPlaceholder('Rechercher')
    ).toBeVisible();
  });
});

Le rapport de test seradescribeaffiché en groupes, ce qui facilite la consultation des résultats.


describe imbriqué

test.describe()Ils peuvent être imbriqués pour former une structure arborescente :

Exemple

test.describe('Module utilisateur', () => {

  test.describe('Fonctionnalité de connexion', () => {
    test('Connexion avec un compte et un mot de passe corrects', async ({ page }) => { /* ... */ });
    test('La connexion échoue avec un mot de passe incorrect', async ({ page }) => { /* ... */ });
  });

  test.describe('Fonctionnalité d’inscription', () => {
    test('S’inscrire avec des informations correctes', async ({ page }) => { /* ... */ });
    test('L’inscription échoue avec un e-mail existant', async ({ page }) => { /* ... */ });
  });
});

test.skip() Ignorer les tests

test.skip()Utilisé pour ignorer un test ; les tests ignorés ne seront pas exécutés.

Exemple

// Ignorer directement (souvent utilisé pour désactiver temporairement des tests de fonctionnalité en échec)
test.skip('Test de fonctionnalité pas encore terminé', async ({ page }) => {
  // Ce test ne sera pas exécuté
});

// Ignorer conditionnellement (déterminé à l’exécution)
test('Ignorer sous certaines conditions', async ({ page, browserName }) => {
  // Ignorer dans Firefox
  test.skip(browserName === 'firefox', 'Firefox ne prend pas encore en charge cette fonctionnalité');

  // La logique du test ne s'exécute que dans les navigateurs non Firefox
  await page.goto('https://www.example.com/');
});

test.fail() Marquer un échec attendu

test.fail()Utilisé pour marquer un test connu pour échouer.

Exemple

// Marqué comme échec attendu (ne sera pas signalé comme un échec de test)
test.fail('Bug connu : la fonctionnalité de recherche n'est pas encore implémentée', async ({ page }) => {
  await page.goto('https://www.example.com/');
  // Cette assertion est censée échouer
  await expect(page.locator('.search-results')).toBeVisible();
});

// Si le test réussit de manière inattendue, il sera signalé comme un échec
// Ceci vous rappelle : "Le bug est corrigé, vous pouvez maintenant retirer le marqueur test.fail"

test.fail()Son objectif est un marquage inversé : échec du test = comme prévu, réussite du test = anormal (le marqueur devra peut-être être retiré).


test.only() Exécuter uniquement le test actuel

test.only()Utilisé pour exécuter uniquement des tests spécifiques pendant le développement et le débogage, en ignorant les autres tests du fichier.

Exemple

// Pendant le développement et le débogage, exécuter uniquement le test actuel
test.only('Je débogue ce test', async ({ page }) => {
  // Seul ce test sera exécuté
  await page.goto('https://www.example.com/');
});

test('Ce test sera ignoré', async ({ page }) => {
  // À cause de test.only() ci-dessus, ceci ne sera pas exécuté
});

test.only()est un outil de débogage et ne doit pas être commité dans le dépôt de code. Si vousplaywright.config.tsdéfinissez ... dansforbidOnly: true, CI détecteraonlyet fera échouer le build.


test.fixme() Marquer comme à corriger

test.fixme()ettest.skip()Similaire, mais sémantiquement cela signifie « ce test doit être corrigé. »

Exemple

// Marqué comme nécessitant une correction (ne s'exécutera pas)
test.fixme('Le flux de connexion doit être mis à jour en raison de modifications de l’API', async ({ page }) => {
  // Ce test ne s'exécutera pas pour l'instant
});

test.slow() Marquer les tests comme lents

test.slow()Multiplie par 3 le délai d'attente du test.

Exemple

// Marqué comme test lent, le délai d'attente est triplé
test.slow('Test sur un grand volume de données', async ({ page }) => {
  // Le délai d'attente par défaut pour ce test sera de 90 secondes (30s × 3)
  // Convient aux scénarios nécessitant un chargement important de données ou des calculs complexes
});

Hooks de test

Les fonctions de hook vous permettent d’effectuer des tâches de préparation et de nettoyage à différentes étapes des tests.

test.beforeEach() et test.afterEach()

beforeEachS'exécute avant chaque test,afterEachet s'exécute après chaque test.

Exemple

// Chemin du fichier : tests/hooks-demo.spec.ts
import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  // Naviguer vers la même page avant chaque test
  await page.goto('https://www.example.com/');
});

test.afterEach(async ({ page }) => {
  // Effectuer le nettoyage après chaque test (par exemple, supprimer les données créées)
  // Playwright nettoie automatiquement les contextes de navigateur,
  // Le nettoyage ici concerne principalement l'état côté serveur
  console.log('Test terminé, démarrage du nettoyage...');
});

test('Premier test', async ({ page }) => {
  // la page est déjà sur la page d'accueil EXAMPLE
  await expect(page).toHaveTitle(/EXAMPLE/);
});

test('Deuxième test', async ({ page }) => {
  // la page est aussi déjà sur la page d'accueil EXAMPLE
  // Il s'agit d'un tout nouveau contexte ; les cookies/stockage du test précédent ne persisteront pas
  await expect(page.locator('nav')).toBeVisible();
});

test.beforeAll() et test.afterAll()

beforeAllDansDans la portée actuelle,s'exécute une fois avant tous les tests,afterAllet s'exécute une fois après tous les tests.

Exemple

test.describe('Gestion des utilisateurs', () => {
  test.beforeAll(async ({ browser }) => {
    // Créer un utilisateur de test avant tous les tests (exécuté une seule fois)
    // Remarque : beforeAll ne peut pas utiliser la fixture page
  });

  test.afterAll(async ({ browser }) => {
    // Supprimer l'utilisateur de test après tous les tests (exécuté une seule fois)
  });

  test('Voir la liste des utilisateurs', async ({ page }) => { /* ... */ });
  test('Modifier les informations de l'utilisateur', async ({ page }) => { /* ... */ });
});

beforeAlletafterAllimpossible d'accéderpagela fixture, car page est indépendante pour chaque test. Si vous devez intervenir sur une page, utilisezbeforeEach。


Portée des hooks et ordre d'exécution

La portée des hooks dépend de l'endroit où ils sont définis :

Exemple

// Hook au niveau du fichier — s'applique à l'ensemble du fichier

test.beforeEach(async () => {
  console.log('beforeEach au niveau du fichier');
});

test.describe('Groupe A', () => {
  // Hook au niveau du groupe — s'applique uniquement au groupe A
  test.beforeEach(async () => {
    console.log("beforeEach du groupe A");
  });

  test('Test A1', async () => { /* ... */ });
  test('Test A2', async () => { /* ... */ });
});

test.describe('Groupe B', () => {
  // Le groupe B n'a pas de hooks propres ; seul le hook au niveau du fichier prend effet
  test('Test B1', async () => { /* ... */ });
});

Ordre d'exécution : les hooks externes s'exécutent en premier, les hooks internes s'exécutent ensuite.

Pour A1 et A2 : première sortie"文件级 beforeEach", puis la sortie"分组 A 的 beforeEach"。

Pour B1 : uniquement la sortie"文件级 beforeEach"。

Autres extensions