# Encapsuler vos sélecteurs Playwright dans le pattern Page Object, pour de bon

> Un sélecteur répété dans quinze fichiers de test casse quinze fois quand la page change. Le pattern Page Object centralise chaque page dans une classe dédiée.

- Auteur : WordPress Développement
- Publié le : 2022-06-21
- Mis à jour le : 2022-06-21
- Catégorie : Tests
- URL : https://www.wpmoderne.fr/tests/page-object-playwright-selecteurs/

## L’essentiel

- Chaque page de l'application obtient sa propre classe avec ses sélecteurs et ses actions
- Un test lit alors comme une suite d'actions métier, pas comme une liste de sélecteurs
- Un changement de sélecteur ne se corrige plus qu'à un seul endroit

Un test unitaire isolé se répare en général en quelques minutes ; un test de bout en bout qui répète le même sélecteur dans quinze fichiers différents transforme le moindre changement de balisage en chasse au trésor. C'est exactement la situation qui pousse la plupart des équipes Playwright à adopter, tôt ou tard, le pattern Page Object, hérité des tests Selenium bien avant l'arrivée de Playwright.

Ce billet montre comment encapsuler chaque page de l'application dans une classe dédiée qui centralise ses sélecteurs et ses actions, sans entrer dans la comparaison entre Playwright et Cypress, qui relève d'un autre débat.

## Le symptôme : des sélecteurs dispersés dans chaque fichier de test

```
test( 'ajout au panier', async ( { page } ) => {
  await page.goto( '/boutique/produit-exemple' );
  await page.locator( '.single_add_to_cart_button' ).click();
  await expect( page.locator( '.woocommerce-message' ) ).toBeVisible();
} );

test( 'suppression du panier', async ( { page } ) => {
  await page.goto( '/panier' );
  await page.locator( '.woocommerce-cart-form .remove' ).first().click();
  await expect( page.locator( '.cart-empty' ) ).toBeVisible();
} );
```

Le sélecteur `.single_add_to_cart_button` apparaît ici une fois, mais dans une suite réelle, un même sélecteur de ce type se retrouve fréquemment dans une dizaine de fichiers distincts. Le jour où ce bouton change de classe CSS, chaque occurrence doit être corrigée individuellement.

## Créer une classe dédiée par page

```
class PageProduit {
  constructor( page ) {
    this.page = page;
    this.boutonAjouterAuPanier = page.locator( '.single_add_to_cart_button' );
    this.messageConfirmation = page.locator( '.woocommerce-message' );
  }

  async ouvrir( slug ) {
    await this.page.goto( `/boutique/${ slug }` );
  }

  async ajouterAuPanier() {
    await this.boutonAjouterAuPanier.click();
  }
}
```

Le test devient alors une suite d'actions métier lisibles, sans qu'aucun sélecteur CSS n'apparaisse directement dans le fichier de test :

```
test( 'ajout au panier', async ( { page } ) => {
  const pageProduit = new PageProduit( page );
  await pageProduit.ouvrir( 'produit-exemple' );
  await pageProduit.ajouterAuPanier();
  await expect( pageProduit.messageConfirmation ).toBeVisible();
} );
```

> L'essentiel à retenir : Chaque page de l'application obtient sa propre classe avec ses sélecteurs et ses actions ; Un test lit alors comme une suite d'actions métier, pas comme une liste de sélecteurs ; Un changement de sélecteur ne se corrige plus qu'à un seul endroit

## Un sélecteur, un seul endroit à corriger

Si le bouton d'ajout au panier change de classe CSS, une seule ligne dans `PageProduit` doit être corrigée, quel que soit le nombre de fichiers de test qui utilisent cette classe. C'est le gain principal du pattern : la connaissance de la structure HTML de la page reste localisée à un seul endroit du code.

## Composer plusieurs Page Objects pour un parcours complet

Un parcours de commande complet traverse plusieurs pages successives. Chaque étape reste représentée par sa propre classe, et le test orchestre l'ensemble sans jamais connaître le détail de chaque page :

```
test( 'tunnel de commande complet', async ( { page } ) => {
  const pageProduit = new PageProduit( page );
  const pagePanier = new PagePanier( page );
  const pageCommande = new PageCommande( page );

  await pageProduit.ouvrir( 'produit-exemple' );
  await pageProduit.ajouterAuPanier();
  await pagePanier.validerLeContenu();
  await pageCommande.remplirCoordonnees( { nom: 'Client Test', email: 'client@exemple-test.invalid' } );
  await pageCommande.confirmerLaCommande();

  await expect( page.locator( '.woocommerce-order-received' ) ).toBeVisible();
} );
```

## Où placer la limite entre Page Object et test

Une erreur fréquente consiste à faire porter les assertions au Page Object lui-même plutôt qu'au test. La convention la plus lisible garde les assertions dans le fichier de test, et réserve au Page Object les actions et l'exposition des localisateurs nécessaires pour ces assertions :

- Le Page Object encapsule les sélecteurs et les actions (cliquer, remplir, naviguer).
- Le test contient les assertions, en s'appuyant sur les localisateurs exposés par le Page Object.
- Une méthode qui retourne un booléen plutôt qu'un localisateur limite les possibilités d'assertion côté test.

> Un Page Object qui contient lui-même des assertions devient difficile à réutiliser d'un scénario à l'autre : mieux vaut le garder strictement descriptif de la page qu'il représente.

## En résumé

Le pattern Page Object centralise, pour chaque page de l'application, la connaissance de sa structure HTML dans une seule classe dédiée, ce qui transforme les fichiers de test en une suite d'actions métier lisibles et réduit à un seul endroit la correction nécessaire après un changement de sélecteur. Cette organisation, simple à mettre en place dès les premiers tests Playwright d'un projet, évite la dette technique qui s'accumule silencieusement dans une suite qui grossit sans elle.
