Playwright

Pourquoi utiliser Playwright ?

Playwright est un framework moderne d’automatisation de tests end-to-end développé par Microsoft. Il se distingue par :

  • Son support natif multi-navigateurs (Chromium, Firefox, WebKit).
  • Sa capacité à gérer les scénarios complexes (multi-onglets, iframes, authentification, etc.).
  • Une API moderne, stable et complète permettant des tests robustes.
  • Une exécution rapide et fiable, grâce à des mécanismes d’attente automatique.

Quand utiliser Playwright?

  • Tests End-to-End multi-plateformes : il exécute les tests sur Chromium, Firefox, webkit (donc sur windows, mac et linux).
  • Gère nativement ces cas complexes : tests avec des iframes, popups et authentifications
  • Tests sur des apps modernes en JS comme React, Angular, Vue… 
  • Automatisations dans les pipelines CI/CD : grâce à son mode headless rapide, il est idéal dans ces workflows CI
  • Simulation réaliste d’un utilisateur : contrôle clavier, souris, API, réseau, géolocalisation, fichiers, …

Limites de Playwright

Même s’il est très complet, Playwright présente aussi quelques limitations : 

  • Pas de support direct pour les applications mobiles natives (contrairement à Appium). Cependant, il y a Playwright for Android (expérimental) pour piloter du mobile web via WebKit iOS/Chromium Android sur émulateur
  • La communauté est plus jeune que celle de Selenium donc il y a moins de plugins ou d’intégrations 
  • Les tests avec un rendu visuel pixel-perfect sont plus adaptés à des outils spécialisés comme Applitools. Il y a une solution qui existe cependant : prendre un screen shot et le comparer à l’attendu. 

Fonctionnalités principales

  • Comme nous l’avons vu, il est multi-navigateur
  • Multi-onglets et iframes
  • Attente automatique 
  • Générateur de code intégré : npx playwright codegen génére des tests automatiquement à partir de clics dans le navigateur
  • Test réseau et mocking : possibilité d’intercepter les requêtes HTTP/GraphQL et simuler des réponses 
  • Tests parallèles : optimisé pour l’exécution concurrente des tests
  • Vidéos et screenshots
  • Exécution sur CI/CD : compatible avec GitHub Actions, Gitlab CI, Jenkins, … 

Installation de Playwright

L’installation de Playwright est rapide, il suffit d’exécuter :

npm init playwright@latest

Ensuite, vous allez devoir choisir entre Typescript ou Javascript. La valeur sélectionnée par défaut est Typescript.



La prochaine question vous demande d’entrer le nom de votre dossier Tests (la valeur par défaut est tests ou e2e si vous avez déjà un dossier tests dans votre projet)



Ajoutez un workflow GitHub Actions pour exécuter facilement des tests sur CI. Nous verrons plus en détails cette solution dans la partie CI.

Ensuite, il faut y noter l’ajout de playwright.config.ts et deux fichiers de test dans deux nouveaux dossier : tests/example.spect.ts & tests-examples/demo-todo-app.spec.ts. 

Le fichier playwright.config.ts est un fichier de configuration, vous allez le modifier par exemple si vous voulez l’exécution de tests en parallèle, la personnalisation du rapport de tests, etc…

Les deux autres fichiers tests/example.spec.ts & tests-examples/demo-todo-app.spec.ts. sont des fichiers d’exemples de Playwright. Nous vous conseillons de les lire et de les comprendre car ce sont des notions à prendre en main.

A lire

En décembre 2024, YKailash Pathak a publié le livre « Web automation Testing Using Playwright » aux Éditions bpb. Cet ouvrage, couvre les tests E2E, API, accessibilité, … avec Playwright

Ce livre est disponible en version imprimée et format kindle sur amazon.

Le livre est rédigé en anglais.

Reprenons les étapes que nous avions vu dans la partie Sauce Demo.

 

Web automation Testing Using Playwright

Reprenons les étapes que nous avions vu dans la partie Sauce Demo.

Connexion à l’application avec un utilisateur valide

Vous pouvez créer un nouveau fichier connexion.spec.ts. Et dans ce nouveau fichier : 

// tests/login-add-to-cart.spec.ts

import { test, expect } from ‘@playwright/test’;

test(‘Connexion standard_user puis ajout au panier’, async ({ page }) => {

/* 1. Page de login */

await page.goto(‘https://www.saucedemo.com/’);

// Sélection des champs par id (les id sont stables sur ce site-démo)

await page.fill(‘#user-name’, ‘standard_user’);

await page.fill(‘#password’, ‘secret_sauce’);

// Bouton « Login »

await page.click(‘#login-button’);

/* 2. Vérifier la redirection */

// L’URL doit contenir /inventory.html ; on vérifie aussi que le titre « Products » est visible

await expect(page).toHaveURL(/inventory\.html/);

await expect(page.locator(‘.title’)).toHaveText(‘Products’);

/* 3. Sélection du premier produit puis clic sur « Add to cart » */

// .inventory_item représente chaque carte produit ; on prend le premier

const firstItem = page.locator(‘.inventory_item’).first();

// Le bouton porte l’attribut data-test= »add-to-cart-… » : plus fiable que le texte

await firstItem.locator(‘[data-test^= »add-to-cart »]’).click();

/* 4. Vérifier le badge du panier */

const cartBadge = page.locator(‘.shopping_cart_badge’);

await expect(cartBadge).toHaveText(‘1’);

});

Avant d’executer le test, vous pouvez lister les tests qui vont être exécuter afin de voir les tests mais aussi les navigateurs qui vont être utilisés :

npx playwright test –list

Avant d’executer le test, vous pouvez lister les tests qui vont être exécuter afin de voir les tests mais aussi les navigateurs qui vont être utilisés :

Supprimez le fichier example.spect.ts et exécuter à nouveau la commande :

Vous pouvez y voir 3 exécutions, une sur chaque navigateur : Chrome, firefox et webkit.

Exécuter le test avec : 

npx playwright test

Pour voir le rapport, exécuter :

npx playwright show-report

Dans un navigateur, naviguez jusque : 

http://localhost:9323

Ajouter un produit au panier et valider l’affichage des éléments.

Vous pouvez ajouter ce test dans un nouveau fichier : test/checkout-order.spec.ts

 

// tests/checkout-order.spec.ts

import { test, expect } from ‘@playwright/test’;

test(‘Ajouter un produit puis finaliser la commande’, async ({ page }) => {

/* Page de login */

await page.goto(‘https://www.saucedemo.com/’);

await page.fill(‘#user-name’, ‘standard_user’);

await page.fill(‘#password’, ‘secret_sauce’);

await page.click(‘#login-button’);

/* Vérifier la redirection vers l’inventaire */

await expect(page).toHaveURL(/inventory\.html/);

await expect(page.locator(‘.title’)).toHaveText(‘Products’);

/* Sélection du premier produit et ajout au panier */

const firstItem = page.locator(‘.inventory_item’).first();

const productName = (await firstItem.locator(‘.inventory_item_name’).textContent())?.trim();

await firstItem.locator(‘[data-test^= »add-to-cart »]’).click();

/* Accéder au panier et vérifier le contenu */

await page.click(‘.shopping_cart_link’);

await expect(page).toHaveURL(/cart\.html/);

const cartItem = page.locator(‘.cart_item .inventory_item_name’);

await expect(cartItem).toHaveText(productName!);

/* Démarrer le checkout */

await page.click(‘[data-test= »checkout »]’);

/* Formulaire d’informations client */

await expect(page).toHaveURL(/checkout-step-one\.html/);

await page.fill(‘#first-name’, ‘Fanny’);

await page.fill(‘#last-name’, ‘Tester’);

await page.fill(‘#postal-code’, ‘75000’);

await page.click(‘[data-test= »continue »]’);

/* Vérifier le récapitulatif */

await expect(page).toHaveURL(/checkout-step-two\.html/);

await expect(page.locator(‘.summary_info’)).toBeVisible();

/* Finaliser la commande */

await page.click(‘[data-test= »finish »]’);

/* Vérification finale */

await expect(page).toHaveURL(/checkout-complete\.html/);

await expect(page.locator(‘.complete-header’)).toHaveText(‘Thank you for your order!’);

});

 

 

Tester la déconnexion et vérifier la persistance des sessions

Vous pouvez ajouter ce test dans un nouveau fichier logout-session-persistence.spect.ts. 

import { test, expect } from ‘@playwright/test’;

test(‘Déconnexion et vérification post-logout’, async ({ page }) => {

/* Connexion */

await page.goto(‘https://www.saucedemo.com/’);

await page.fill(‘#user-name’, ‘standard_user’);

await page.fill(‘#password’, ‘secret_sauce’);

await page.click(‘#login-button’);

// Contrôle rapide : nous sommes bien sur la page Produits

await expect(page).toHaveURL(/inventory\.html/);

/* Ouverture du menu latéral */

await page.click(‘#react-burger-menu-btn’);

// Le menu glisse depuis la gauche ; attendons qu’il soit réellement visible

await expect(page.locator(‘.bm-menu’)).toBeVisible();

/* Déconnexion */

await page.click(‘#logout_sidebar_link’);

/* Vérifications post-logout */

await expect(page).toHaveURL(‘https://www.saucedemo.com/’);

await expect(page.locator(‘#login-button’)).toBeVisible();

/* Persistance de session : accès direct à /inventory */

await page.goto(‘https://www.saucedemo.com/inventory.html’);

await expect(page).toHaveURL(‘https://www.saucedemo.com/’);

await expect(page.locator(‘#user-name’)).toBeEmpty();

});

 

 

Utiliser un compte « buggué » (problem_user) pour voir comment les erreurs sont gérées

import { test, expect } from ‘@playwright/test’;

test(‘Connexion problem_user, anomalies visuelles et checkout’, async ({ page }) => {

/*Connexion */

await page.goto(‘https://www.saucedemo.com/’);

await page.fill(‘#user-name’, ‘problem_user’);

await page.fill(‘#password’, ‘secret_sauce’);

await page.click(‘#login-button’);

await expect(page).toHaveURL(/inventory\.html/);

await expect(page.locator(‘.title’)).toHaveText(‘Products’);

/* Inspection des images produits */

const images = page.locator(‘.inventory_item_img img’);

const imgCount = await images.count();

for (let i = 0; i < imgCount; i++) {

const img = images.nth(i);

// Vérification visibilité 

await expect.soft(img, `Image ${i + 1} invisible`).toBeVisible();

const width = await img.evaluate(el => (el as HTMLImageElement).naturalWidth);

await expect.soft(width, `Image ${i + 1} cassée`).toBeGreaterThan(0);

}

/* Test des boutons « Add / Remove » */

const firstItem = page.locator(‘.inventory_item’).first();

const addBtn = firstItem.locator(‘[data-test^= »add-to-cart »]’);

await expect.soft(addBtn).toBeVisible();

await expect.soft(addBtn).toBeEnabled();

await addBtn.click();

// Le badge panier doit afficher « 1 »

const badge = page.locator(‘.shopping_cart_badge’);

await expect(badge).toHaveText(‘1’);

/* Vérification du contenu du panier */

await page.click(‘.shopping_cart_link’);

await expect(page).toHaveURL(/cart\.html/);

const cartItems = page.locator(‘.cart_item’);

await expect(cartItems).toHaveCount(1);

await page.click(‘[data-test= »checkout »]’);

await expect(page).toHaveURL(/checkout-step-one\.html/);

await page.fill(‘#first-name’, ‘Bug’);

await page.fill(‘#last-name’, ‘Hunter’);

await page.fill(‘#postal-code’, ‘99999’);

await page.click(‘[data-test= »continue »]’);

await expect(page.locator(‘.summary_info’)).toBeVisible();

await page.click(‘[data-test= »finish »]’);

await expect(page).toHaveURL(/checkout-complete\.html/);

// Message de confirmation

await expect(page.locator(‘.complete-header’))

.toHaveText(‘Thank You for Your Order!’);

/* Déconnexion et contrôle de fin de session */

await page.click(‘#react-burger-menu-btn’);

await expect(page.locator(‘.bm-menu’)).toBeVisible();

await page.click(‘#logout_sidebar_link’);

await expect(page).toHaveURL(‘https://www.saucedemo.com/’);

await expect(page.locator(‘#login-button’)).toBeVisible();

});

Le dernier test est en erreur, vous pouvez allé sur le rapport comme nous l’avons tout à l’heure en vous rendant sur localhost:9323 :

Cliquez sur l’un des tests en erreur :

 

Vous pouvez visualiser l’erreur en détail et également voir les étapes avec le temps que chacune a pris pour s’exécuter.

 

voir aussi l’article sur Sauce Labs

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *