TECHNOLOGIES
Playwright, notre outil de tests end-to-end
Playwright est un framework de tests E2E open source développé par Microsoft. Il nous permet de simuler les interactions utilisateurs dans un vrai navigateur et de valider que chaque parcours critique fonctionne correctement.
Chez SmartBooster, Playwright est notre outil de référence pour les tests de bout en bout : couverture des parcours métier, tests multi-navigateurs et intégration dans notre pipeline GitLab CI/CD.
PRÉSENTATION
Qu'est-ce que Playwright ?
Playwright est un framework de tests end-to-end open source créé par Microsoft. Il pilote de vrais navigateurs (Chromium, Firefox, WebKit) de façon programmatique pour simuler exactement ce qu'un utilisateur ferait sur votre application.
Contrairement aux tests unitaires qui vérifient des fonctions isolées, les tests Playwright valident l'application complète : rendu de l'interface, appels API, transitions de page, gestion des erreurs. C'est la couche de test la plus proche du comportement réel de l'utilisateur.
Playwright s'intègre nativement avec nos projets Vue.js et TypeScript. Il supporte les pages rendues par Vite, les Single Page Applications et les applications hybrides Symfony + Vue.js via Inertia.js.
POURQUOI PLAYWRIGHT
Ce qui en fait notre outil de tests E2E de référence
Playwright résout les problèmes classiques des tests end-to-end : tests fragiles, synchronisation difficile, couverture multi-navigateurs coûteuse et débogage opaque.
Validation des parcours critiques
Playwright simule un vrai utilisateur dans le navigateur. Chaque parcours critique — connexion, saisie de formulaire, validation métier — est vérifié automatiquement avant chaque mise en production.
Multi-navigateurs natif
Un seul test, trois moteurs : Chromium, Firefox et WebKit (Safari). Aucune régression ne passe entre navigateurs sans être détectée, sans multiplier le code de test.
Rapidité et fiabilité
Playwright attend automatiquement que les éléments soient interactibles avant d'agir. Pas de sleep arbitraires, pas de tests flaky qui échouent aléatoirement selon la vitesse du serveur CI.
Tests API inclus
Au-delà de l'interface, Playwright peut tester directement les endpoints API de nos applications Symfony. Un outil unique pour les tests UI et les tests d'intégration API, sans jongler entre plusieurs bibliothèques.
TypeScript natif
Les tests Playwright s'écrivent en TypeScript avec une autocomplétion complète. Le même langage que nos composants Vue.js, des types corrects dès l'écriture et des refactorings sûrs.
Captures et traces de débogage
En cas d'échec, Playwright génère une capture d'écran, une vidéo et une trace complète de l'exécution. Le débogage d'un test raté se fait en quelques secondes depuis l'interface Trace Viewer.
NOTRE USAGE
Comment nous utilisons Playwright chez SmartBooster
Nous intégrons Playwright dans les projets Vue.js et Symfony dès lors que des parcours utilisateurs critiques doivent être couverts et que l'application est déployée en continu.
Playwright s'intègre naturellement avec Storybook : Storybook utilise Playwright en interne comme librairie DOM, ce qui rend la base commune pour les tests comportementaux des composants directement dans leurs stories.
Parcours utilisateurs critiques
Nous identifions les parcours à fort enjeu métier (authentification, tunnel de commande, soumission de formulaires complexes) et les couvrons avec des scénarios Playwright. Ces tests constituent le filet de sécurité principal avant chaque déploiement.
Intégration CI/CD GitLab
Les tests Playwright s'exécutent automatiquement dans notre pipeline GitLab CI/CD à chaque push sur la branche principale. Un test en échec bloque le déploiement et alerte l'équipe immédiatement. En pratique, nous nous autorisons à livrer sans relancer l'intégralité des tests lorsque la réactivité prime — la couverture E2E reste un filet, pas un frein.
Tests de régression sur les composants Vue.js
En complément des tests unitaires Vitest et de la documentation interactive Storybook (qui permet également d'exécuter des tests sur les composants isolés), Playwright valide que les composants Vue.js s'affichent et se comportent correctement dans le contexte applicatif complet, avec les vraies données et les vraies interactions.
MIGRATION
Migrer de Cypress à Playwright
Nous utilisions Cypress pour nos tests end-to-end avant de migrer vers Playwright. Le changement a été motivé par trois limites concrètes de Cypress que nous rencontrions au quotidien : l'absence de support WebKit, des pipelines CI lents sur les grosses suites et l'impossibilité d'intégrer nativement les tests avec Storybook.
Tests multi-navigateurs sans surcoût
Cypress ne supporte officiellement que Chromium (Chrome, Edge) et Firefox — WebKit/Safari est à ce jour au stade expérimental. Playwright teste les trois moteurs dans le même runner, sans licence ni service tiers. Une régression Safari est détectée en CI sans changer une ligne de code.
Architecture out-of-process plus rapide
Cypress s'exécute dans le navigateur avec le code de l'application, ce qui engendre des limitations et des lenteurs sur les suites volumineuses. Playwright pilote le navigateur depuis l'extérieur via le protocole CDP, ce qui le rend plus rapide et stable sur les pipelines CI avec de nombreux tests.
Intégration native avec Storybook
Playwright s'intègre directement avec Storybook via @storybook/addon-vitest. Concrètement, chaque story devient un test : Playwright navigue vers la story, la rend dans un vrai navigateur et vérifie qu'elle s'affiche sans erreur. On peut aussi générer des captures d'écran automatiques de chaque composant pour détecter les régressions visuelles entre deux builds, sans outillage visuel dédié supplémentaire.
Migration progressive depuis Cypress
Playwright dispose d'un outil de migration officiel (playwright codegen) qui enregistre les interactions et génère les tests correspondants. Nous avons migré nos suites Cypress existantes en commençant par les parcours critiques, en faisant cohabiter les deux outils le temps de la transition sans bloquer les déploiements.
BONNES PRATIQUES & DOCUMENTATION
Nos conventions Playwright
Documentation de référence pour l'équipe SmartBooster.
Installation des navigateurs pour Playwright dans la CI
Installation des navigateurs pour Playwright dans la CI
Les navigateurs Playwright (Chromium, Firefox, WebKit) pèsent plusieurs centaines de mégaoctets et n'ont rien à faire dans les dépendances du projet. Ils ne servent qu'à l'exécution des tests. Deux approches selon le contexte du projet.
Pourquoi un job CI dédié dans les deux cas ?
- Les tests E2E sont plus lents que les tests unitaires : les isoler évite de bloquer le pipeline sur un job mixte
- Les navigateurs ne sont téléchargés ou démarrés que si le job E2E est déclenché, pas à chaque
yarn install
Cas 1 : image Docker applicative existante (Symfony + Node)
Le runner utilise déjà une image Docker personnalisée pour le projet. Il faut alors télécharger le navigateur en début de job, juste avant de lancer les tests :
variables:
# Redirige l'install des binaires Playwright vers un dossier du projet pour pouvoir le mettre en cache GitLab CI
PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.playwright-browsers"
e2e:
stage: test
when: manual
artifacts:
when: on_failure
expire_in: 2 days
paths:
- test-results/ # screenshots, videos et traces générés par Playwright en cas d'échec
cache:
- key: "${CI_PROJECT_ID}_cache"
paths:
- node_modules/
- key: "${CI_PROJECT_ID}_playwright_browsers"
paths:
- .playwright-browsers/
script:
- yarn install
- yarn playwright install chromium --with-deps
- yarn test:e2e:ci
L'option --with-deps installe également les dépendances système requises par Chromium (librairies GTK, NSS...),
indispensables sur les runners CI qui partent d'une image Linux/Node minimale.
Cas 2 : projet front Node.js (Vue.js, React...) — image officielle Playwright
Microsoft publie une image Docker officielle avec Chromium, Firefox et WebKit déjà installés et leurs
dépendances système pré-configurées. C'est l'approche recommandée pour les projets full front : aucune étape
playwright install dans le script, le job démarre directement sur yarn install puis les tests.
La version de l'image doit correspondre exactement à la version de @playwright/test dans package.json.
e2e:
stage: test
when: manual
artifacts:
when: on_failure
expire_in: 2 days
paths:
- test-results/
image: mcr.microsoft.com/playwright:v1.59.1-noble
script:
- yarn install
- yarn test:e2e:ci
Script Playwright à ajouter dans le package.json
Script Playwright à ajouter dans le package.json
{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ci": "playwright test --reporter list",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"test:e2e:codegen": "playwright codegen http://localhost:8080"
}
}
test:e2e: lance tous les tests E2E en mode headless sur le poste du développeur. Le reporter par défaut génère un rapport HTML complet à l'issue de l'exécution : détail de chaque test, captures d'écran, traces, et bouton "Copy prompt" sur les tests en échec.test:e2e:ci: variante pour la CI. L'option--reporter listremplace le rapport HTML par une sortie stdout ligne par ligne, adaptée aux logs des runners GitLab CI qui ne peuvent pas ouvrir un fichier HTML.test:e2e:ui: ouvre l'interface graphique de Playwright. Elle permet d'explorer les tests, de les rejouer un par un, de voir les captures d'écran et les traces d'exécution. Indispensable pour comprendre pourquoi un test échoue.test:e2e:debug: lance les tests avec le Playwright Inspector ouvert. L'exécution est mise en pause à chaque étape pour inspecter le DOM, les sélecteurs et l'état de la page. Utile pour déboguer un test précis plutôt que de relancer toute la suite.test:e2e:codegen: lance le mode enregistrement surhttp://localhost:8080. Playwright ouvre un navigateur et génère automatiquement le code de test correspondant à chaque action effectuée dans l'interface. Point de départ idéal pour créer un nouveau test sans écrire le sélecteur à la main.
Syntaxe pour ne lancer qu'une seul spec de test
Syntaxe pour ne lancer qu'une seul spec de test
Il y a 2 manières de faire :
Cibler directement le fichier de la spec :
yarn test:e2e tests/e2e/specs/parcours_simple.spec.js
Cette version de la commande doit être ajoutée en tête du fichier de spec en commentaire pour simplifier l'exécution du test lors de sa rédaction ou modification.
Ou avec un pattern glob si on souhaite matcher par nom :
yarn test:e2e --grep "Complétion du parcours simple"
--grep filtre par nom de test.describe ou de test(), pratique pour cibler un test précis dans un fichier.
Ces deux formes d'isolation (chemin de fichier et
--grep) fonctionnent aussi avectest:e2e:ui: l'interface graphique s'ouvrira en n'affichant que les tests correspondants, ce qui accélère la navigation quand la suite complète est volumineuse.
Comment bien choisir son selecteur d'élément (Locator) du DOM pour ses tests ?
Comment bien choisir son selecteur d'élément (Locator) du DOM pour ses tests ?
Les locators sont la pièce centrale du système d'attente automatique et de relance (auto-waiting et retry-ability) de Playwright. Concrètement, un locator représente une façon de trouver un ou plusieurs éléments sur la page à n'importe quel moment : à chaque action, Playwright interroge le DOM à nouveau plutôt que de s'appuyer sur une référence capturée au départ.
Choisir le bon locator est important car les deux objectifs d'un bon test E2E sont parfois en tension : représenter au plus près ce que l'utilisateur perçoit et interagi, tout en garantissant un test stable qui ne casse pas à chaque refactoring mineur du HTML.
Recommandation Playwright & SmartBooster :
getByRole()est la façon la plus proche de ce que voient l'utilisateur et les technologies d'assistance (lecteurs d'écran). C'est le point de départ par défaut pour tout élément interactif.
Si le besoin de votre test est plus fin, voici l'ordre de priorité recommandé :
| Priorité | Locator | Quand l'utiliser |
|---|---|---|
| 1 | getByRole() |
Tout élément interactif : bouton, lien, champ, case à cocher. Correspond aux attributs d'accessibilité implicites et explicites. |
| 2 | getByLabel() |
Champs de formulaire associés à un <label>. Reflète exactement ce que l'utilisateur lit avant de saisir. |
| 3 | getByPlaceholder() |
Inputs sans label visible mais avec un placeholder explicite. |
| 4 | getByText() |
Éléments non interactifs (div, span, p) identifiés par leur contenu textuel. |
| 5 | getByAltText() |
Images et éléments avec un attribut alt significatif. |
| 6 | getByTitle() |
Éléments portant un attribut title (info-bulle). Usage peu fréquent. |
| 7 | getByTestId() |
Quand aucun locator sémantique ne suffit, ou quand la stabilité du test prime. Nécessite d'ajouter data-testid dans le code source de l'élément. |
getByTestId() pour la stabilité maximale : tester via un attribut data-testid est la méthode la plus
résistante aux changements. Même si le texte du bouton ou son rôle évolue, le test passe. En contrepartie, ce
locator ne reflète pas ce que l'utilisateur voit réellement. À réserver aux cas où le rôle ou le texte ne
suffisent pas, ou quand la stabilité prime sur la lisibilité du test.
Exemple de configuration pour tester avec plusieurs navigateurs
Exemple de configuration pour tester avec plusieurs navigateurs
Playwright gère nativement Chromium, Firefox et WebKit (le moteur de Safari) via la clé projects dans
playwright.config.ts. Chaque projet est un navigateur indépendant : la même suite de tests s'exécute
sur les trois moteurs sans modifier une ligne de code de test.
Les binaires Chromium, Firefox et WebKit sont téléchargés par Playwright lui-même (playwright install).
Edge en revanche repose sur le binaire du navigateur installé sur la machine (via channel: 'msedge')
et n'est donc pas disponible sur les runners CI Linux standards, d'où son exclusion par défaut.
projects: [
// Edge réutilise le binaire Chromium (pas d'install séparée), mais nécessite
// Edge installé sur la machine avec channel: 'msedge'
// { name: 'edge', use: { ...devices['Desktop Edge'] } },
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
]
Obtenir un premier diagnostic d'erreur de test avec l'IA
Obtenir un premier diagnostic d'erreur de test avec l'IA
Quand un test échoue en mode CLI, Playwright génère automatiquement un rapport HTML et propose de l'ouvrir dans le navigateur. Sur la page du rapport, chaque test en échec dispose d'un bouton "Copy prompt" : en cliquant dessus, Playwright copie dans le presse-papiers un prompt structuré contenant toutes les informations liées à l'erreur (message d'erreur, stack trace, captures d'écran, étapes ayant échoué...).
Ce prompt peut être collé directement dans Claude Code pour obtenir un premier diagnostic sans avoir à extraire manuellement les logs. Claude Code peut identifier la cause probable de l'échec et, si l'erreur est dans le code de test lui-même (mauvais sélecteur, assertion incorrecte, ordre d'étapes inadapté), proposer un début de correction applicable immédiatement.
Pourquoi codegen m'indique `spinbutton` pour mon locator input de type nombre ?
Pourquoi codegen m'indique `spinbutton` pour mon locator input de type nombre ?
getByRole('spinbutton') est bien le locator correct pour un <input type="number">. Ce rôle n'est pas inventé
par Playwright : il correspond au rôle ARIA implicite de tous les champs numériques, tel que défini par la
spec WAI-ARIA. Un lecteur d'écran annoncera ce champ comme un
"spinbutton" (zone de saisie numérique incrémentale) à ses utilisateurs.
Conformément à l'ordre de priorité des locators décrit plus haut, getByRole est le sélecteur à privilégier
car il reflète ce que l'utilisateur et les technologies d'assistance perçoivent réellement. Le codegen suit donc
la bonne pratique en proposant ce rôle plutôt qu'un sélecteur CSS fragile comme input[type="number"].
Mocker les appels API externe avec page.route()
Mocker les appels API externe avec page.route()
Intercepter les appels réseau avec page.route() permet de substituer la réponse d'une API par une donnée
contrôlée, sans toucher au serveur réel. Trois raisons principales justifient cette pratique :
- Éviter le spam des services tiers : une suite E2E qui tourne en CI peut déclencher des dizaines d'appels vers une API de géolocalisation, d'envoi d'email ou de paiement. Le mock isole le test de ces effets de bord et garanti de ne pas se retrouver blacklisté.
- Indépendance vis-à-vis de l'uptime externe : si l'API tierce est en maintenance ou ralentit, le test ne doit pas échouer pour une raison qui ne lui appartient pas.
- Contrôle du format de réponse : définir la réponse à l'avance permet de tester des cas précis (ville trouvée, liste vide, erreur 500...) sans dépendre des données réelles du service.
page.route(pattern, handler) intercepte toutes les requêtes dont l'URL correspond au pattern glob fourni.
Quand une requête correspond, le handler reçoit un objet route et peut appeler route.fulfill() pour
retourner une réponse simulée directement au navigateur, sans qu'aucun appel réseau réel ne soit émis.
Ordre impératif :
page.route()doit être enregistré avant l'action qui déclenche l'appel API. Playwright enregistre les handlers dans l'ordre et les applique dès qu'une requête correspondante est émise. Un handler ajouté après l'appel ne sera jamais exécuté pour cette requête.
// Le mock est déclaré EN PREMIER, avant toute interaction avec la page
await page.route('**/geographic/cities/**', route => route.fulfill({
json: { cities: [{ postalCode: '69001', name: 'LYON 01' }] }
}))
// L'action utilisateur qui déclenche l'appel API vient ensuite
await page.getByPlaceholder('Code postal ou nom de ville').pressSequentially('69001')
Le pattern **/geographic/cities/** utilise la syntaxe glob : ** correspond à n'importe quel segment d'URL,
ce qui permet d'intercepter la route quelle que soit l'origine ou les paramètres de chemin autour du segment
ciblé.
route.fulfill({ json: ... }) sérialise automatiquement l'objet en JSON et positionne le header
Content-Type: application/json.
Mise en place du coverage sur Playwright
Mise en place du coverage sur Playwright
Playwright expose une API native page.coverage basée sur le Chrome DevTools Protocol. Elle collecte les données
de couverture JavaScript directement dans le navigateur pendant l'exécution des tests, sans modifier le build.
Contrainte majeure : Chromium uniquement. page.coverage n'est pas disponible sur Firefox ni WebKit.
Le coverage E2E est donc limité au projet chromium de Playwright, ce qui est suffisant car le coverage mesure
quels chemins de code sont exercés — pas la compatibilité cross-browser.
Dépendances
yarn add -D v8-to-istanbul nyc
v8-to-istanbul: convertit les données V8 brutes retournées parstopJSCoverage()en format Istanbulnyc: lit les fichiers Istanbul dans.nyc_output/et génère les rapports finaux (HTML, lcov, cobertura)
Fixture auto (tests/e2e/fixtures.js)
Le coverage est collecté via une fixture auto qui s'exécute autour de chaque test sans modifier les specs.
Elle ne s'active que lorsque la variable d'environnement COVERAGE=true est présente.
import { test as base } from '@playwright/test'
import { writeFileSync, mkdirSync } from 'node:fs'
import { randomUUID } from 'node:crypto'
import { resolve } from 'node:path'
import v8toIstanbul from 'v8-to-istanbul'
export const test = base.extend({
collectCoverage: [async ({ page }, use) => {
if (process.env.COVERAGE) {
await page.coverage.startJSCoverage({ resetOnNavigation: false })
}
await use()
if (!process.env.COVERAGE) return
const entries = await page.coverage.stopJSCoverage()
const istanbulCoverage = {}
for (const entry of entries) {
if (!entry.url.includes('/src/') || !entry.source) continue
const filePath = resolve(process.cwd(), entry.url.replace(/^https?:\/\/[^/]+\//, ''))
const converter = v8toIstanbul(filePath, 0, { source: entry.source })
await converter.load()
converter.applyCoverage(entry.functions)
Object.assign(istanbulCoverage, converter.toIstanbul())
}
if (Object.keys(istanbulCoverage).length > 0) {
mkdirSync('.nyc_output', { recursive: true })
writeFileSync(`.nyc_output/${randomUUID()}.json`, JSON.stringify(istanbulCoverage))
}
}, { auto: true }]
})
export { expect } from '@playwright/test'
Points clés :
resetOnNavigation: falseest indispensable si votre projet est une app de type SPA avec vue-router : sans cette option, les données de coverage seraient réinitialisées à chaque changement de route pendant un test- Le filtre
!/src/exclut les scripts Vite internes (@vite/client), les chunksnode_moduleset les scripts inline sans lien avec le code applicatif randomUUID()garantit un nom de fichier unique par test même avecfullyParallel: trueet plusieurs workers- Les specs importent
testetexpectdepuis../fixtures.jsau lieu de@playwright/test
Configuration du rapport (.nycrc.json)
{
"reporter": ["html", "text", "lcov", "cobertura"],
"include": ["src/**"],
"exclude": ["src/**/*.stories.{js,vue}", "src/stories/**"],
"report-dir": "coverage/e2e"
}
html: rapport navigable danscoverage/e2e/index.htmltext: résumé affiché dans la sortie stdout du CI (nécessaire pour la regex GitLab)lcov: format standard pour les outils tierscobertura: format lu par GitLab pour les annotations de coverage inline dans les diffs de MR
Scripts package.json
"test:e2e:coverage-chromium": "rm -rf .nyc_output && COVERAGE=true playwright test --reporter list --project=chromium",
"test:e2e:coverage-report": "nyc report"
Le rm -rf .nyc_output en tête de commande est important : sans lui, nyc report mergerait les fichiers
d'un run précédent avec les nouveaux et fausserait le calcul du coverage.
Job GitLab CI (test-e2e-coverage-chromium)
test-e2e-coverage-chromium:
stage: test
image: mcr.microsoft.com/playwright:v1.59.1-noble
when: manual
coverage: /All files\s*\|\s*(\d+\.?\d*)/
artifacts:
when: always
expire_in: 2 days
paths:
- test-results/
- coverage/e2e/
reports:
coverage_report:
coverage_format: cobertura
path: coverage/e2e/cobertura-coverage.xml
script:
- yarn install
- yarn test:e2e:coverage-chromium
- yarn test:e2e:coverage-report
coverage:: regex qui extrait le pourcentage global depuis la sortietextde nyc (All files | 67.5 | ...) et l'affiche dans l'UI pipeline et sur les MR GitLabartifacts: when: always: le rapport HTML et le XML cobertura sont uploadés même si les tests passentcoverage_report: cobertura: active les annotations de coverage inline dans les diffs de MR GitLab- Le job est
when: manualcar si vous avez beaucoup de test E2E il n'est pas nécessaire de lancer ce job automatiquement
Pour aller plus loin
Documentation utile
Documentation complète, guides de démarrage et référence API pour Playwright.
Guide d'installation et premiers tests en TypeScript, avec configuration recommandée.
Pour aller plus loin
Approfondir votre réflexion
Playwright couvre les tests E2E, Vitest couvre les tests unitaires Vue.js. Ensemble, ils forment une stratégie de test complète sur nos projets frontend.
Playwright valide les parcours utilisateurs de nos applications Vue.js en conditions réelles de navigation, en complément des tests unitaires Vitest.
Les tests Playwright s'exécutent automatiquement dans notre pipeline GitLab. Un échec bloque le déploiement et garantit la qualité en production.
Playwright s'intègre avec Storybook pour générer des captures d'écran des composants et détecter les régressions visuelles directement depuis la documentation interactive.