Modèle objet de page (POM) Playwright

Ce chapitre présente la philosophie de conception et la mise en œuvre du Page Object Model pour vous aider à organiser de grandes suites de tests.


Qu'est-ce que le POM

Le Page Object Model est un patron de conception de test qui encapsule les éléments et les opérations de pagedans des classes indépendantesin.

Chaque objet de page représente une page ou un composant dans une application web et contient les localisateurs et les méthodes d'opération pour cette page.


Avantages du POM

AvantageDescription
Simplifie l'écriture des testsLe code de test utilise des API de haut niveau (telles queloginPage.login('user', 'pass')) au lieu d'opérations Locator brutes
Réduit les coûts de maintenanceLes modifications de la structure de la page ne nécessitent que la modification de la classe Page Object, et non de chaque test
Améliore la lisibilitéLe code de test se lit davantage comme un langage métier, ce qui le rend compréhensible même pour des personnes non techniques
Évite la duplicationLes mêmes localisateurs et opérations ne doivent être définis qu'une seule fois

Création d'une classe Page Object

Ce qui suit utilise le site EXAMPLE comme exemple pour créer un Page Object pour une page de connexion.

Exemple

// Chemin du fichier : pages/LoginPage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  // Déclarer tous les localisateurs comme propriétés en lecture seule
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    // Initialiser tous les localisateurs dans le constructeur
    this.usernameInput = page.getByLabel('Username');
    this.passwordInput = page.getByLabel('Password');
    this.loginButton = page.getByRole('button', { name: 'Log in' });
    this.errorMessage = page.getByTestId('login-error');
  }

  // Naviguer vers la page de connexion
  async goto() {
    await this.page.goto('/login');
  }

  // Effectuer l'opération de connexion
  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }

  // Vérifier le message d'erreur en cas d'échec de connexion
  async expectLoginError(message: string) {
    await expect(this.errorMessage).toBeVisible();
    await expect(this.errorMessage).toContainText(message);
  }
}

Utilisation du Page Object dans les tests

Exemple

// Chemin du fichier : tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('Fonctionnalité de connexion', () => {
  test('La connexion réussit avec le nom d'utilisateur et le mot de passe corrects', async ({ page }) => {
    const loginPage = new LoginPage(page);

    // Utiliser les méthodes du Page Object
    await loginPage.goto();
    await loginPage.login('example_user', 'correct_password');

    // Vérifier la redirection après une connexion réussie
    await expect(page).toHaveURL('/dashboard');
  });

  test('La connexion échoue avec un mot de passe incorrect', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await loginPage.goto();
    await loginPage.login('example_user', 'wrong_password');

    // Utilisez les méthodes d'assertion du Page Object
    await loginPage.expectLoginError('Nom d'utilisateur ou mot de passe incorrect');
  });
});

Page Objects multi-niveaux

Dans de vrais projets, vous pouvez créer plusieurs Page Objects, et ils peuvent être associés les uns aux autres.

Exemple

// Chemin du fichier : pages/DashboardPage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class DashboardPage {
  readonly page: Page;
  readonly welcomeText: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.welcomeText = page.getByText('Bienvenue à nouveau');
    this.logoutButton = page.getByRole('button', { name: 'Déconnexion' });
  }

  async expectWelcomeMessage(username: string) {
    await expect(this.welcomeText).toContainText(username);
  }

  async logout() {
    await this.logoutButton.click();
  }
}

Combinaison des objets de page :

Exemple

test('Flux complet de connexion et de déconnexion', async ({ page }) => {
  const loginPage = new LoginPage(page);
  const dashboardPage = new DashboardPage(page);

  // Connexion
  await loginPage.goto();
  await loginPage.login('example_user', 'password');

  // Vérifier le tableau de bord
  await dashboardPage.expectWelcomeMessage('example_user');

  // Se déconnecter
  await dashboardPage.logout();
  await expect(page).toHaveURL('/login');
});

Bonnes pratiques POM

PratiqueDescription
Centraliser les localisateurs dans le constructeurDéfinir tous les localisateurs dans le constructeur ; ne pas les créer dynamiquement dans les méthodes
Les méthodes renvoient des objets PageLes méthodes d'opération peuvent renvoyer un nouvel objet Page (par ex., return DashboardPage après connexion)
Encapsuler les assertions, mais ne pas en abuserEncapsuler les combinaisons d'assertions couramment utilisées dans des méthodes ; écrire les assertions simples directement dans les tests
Utiliser la composition au lieu de l'héritageÉviter l'héritage profond ; utiliser le modèle de composition pour associer plusieurs objets Page
Un ensemble d'objets couvre une page/un composantGardez une granularité modérée ; ne créez pas de classes distinctes pour chaque petit élément

POM n'est pas obligatoire.

Pour les petits projets avec seulement quelques tests, écrire les localisateurs directement dans les tests est plus clair. Lorsqu'il y a plus de 5 fichiers de test, envisagez d'introduire POM.

Autres extensions