← Retour au blog
AngularPlaywrightE2E testingAutomationQuality Assurance

E2E avec Playwright sur une app Angular : stratégie robuste et pièges évités

Playwright s'impose comme l'outil E2E de référence pour les applications Angular modernes. Contrairement à Protractor (déprécié depuis 2022) ou Cypress, Playwright supporte plusieurs navigateurs natifs (Chromium, Firefox, WebKit) et excelle avec les frameworks modernes. Mais tester une SPA Angular présente des défis spécifiques : attendre les changements de détection, gérer les observables asynchrones, valider le state NgRx sans exposer l'API interne. Cet article vous guide à travers une stratégie E2E pragmatique et éprouvée en production.

Configurer Playwright pour Angular : au-delà du setup basique

L'installation classique ( npm install -D @playwright/test ) ne suffit pas. Vous devez configurer Playwright pour attendre que votre app Angular soit vraiment prête. Le hook waitForAngularReady() n'existe plus depuis Angular 12, mais vous pouvez implémenter une stratégie équivalente. Créez un fichier playwright.config.ts qui définit une base URL locale avec un délai de démarrage du serveur dev. Surtout, paramétrez navigationTimeout et actionTimeout à 30 secondes minimum : Angular peut déclencher une détection de changement longue lors du chargement initial. Utilisez l'option webServer pour lancer ng serve automatiquement lors des tests, évitant les oublis de démarrage manuel.

Ensuite, mettez en place une fixture réutilisable qui expose des utilitaires Angular-aware. Cette fixture doit attendre que le DOM soit stable et que les animations initiales soient terminées. Un exemple robuste : attendez que tous les éléments avec [appLoading] disparaissent, ce qui garantit que les résolveurs de route et les requêtes HTTP initiales sont complétées. Vous pouvez aussi injecter une variable globale window.__e2eReady depuis votre main.ts en développement, que Playwright vérifie avec page.evaluate() .

Naviguer sans casser le rendu : routes et détection de changement

Angular gère la navigation via le Router et les ActivatedRoute . Playwright doit respecter ce cycle. Quand vous cliquez sur un lien avec page.click() , Angular déclenche une navigation : le composant s'unmount, le nouveau se mount, les résolveurs s'exécutent. Attendez le moment où la route est activée avec page.waitForURL() en parallèle, jamais en série. Par exemple, au lieu d'attendre que le URL change puis l'élément apparaît, utilisez Promise.all([page.waitForURL(/\/dashboard/), page.click('a:has-text("Dashboard")')]) . Cela évite les race conditions où Playwright juge l'action complète trop tôt.

Pour les guards de route ( canActivate , canDeactivate ), Playwright ne les voit pas directement. Testez leur effet observable : si un guard refuse l'accès, l'URL ne change pas et un message d'erreur apparaît. Créez des tests spécifiques pour chaque scénario (authentifié vs non authentifié, permissions insuffisantes). Évitez de mocker le service d'authentification au niveau E2E : testez la vraie intégration avec votre backend ou un stub réaliste.

Tester NgRx et l'état global sans exposition dangereuse

Les applications Angular modernes utilisent NgRx pour la gestion d'état. L'erreur courante : accéder directement au store avec page.evaluate(() => window.ngRxStore.select(...)) . C'est fragile et crée du couplage fort avec l'implémentation. Préférez valider l'état par ses effets visibles : si un action LoadUsers est dispatchée, attendez que la liste d'utilisateurs s'affiche. Si une erreur survient, cherchez le message d'erreur dans le DOM.

Si vous avez vraiment besoin de vérifier l'état pour déboguer, exposez une API de test stricte. Enveloppez NgRx dans un service E2E disponible seulement en développement, via une garde if (!environment.production) . Exemple : window.__ngRxDebug = { selectState: (selector) => store.select(selector).pipe(take(1)).toPromise() } . Cela reste contrôlé et documenté. En production, ce code ne sera jamais présent.

Gérer les observables et les timers asynchrones

Les observables sont le cœur d'Angular. Une requête HTTP, un WebSocket, un setInterval : tous peuvent introduire du délai. Playwright attend généralement par timeout (30 secondes par défaut), ce qui est rarement le problème. Le vrai piège : les faux positifs. Un élément s'affiche brièvement, disparaît, puis réapparaît. Votre test passe une fois sur trois. Utilisez page.waitForFunction() pour attendre une condition stable, pas juste la présence du DOM.

Par exemple, au lieu de await page.locator('.data-row').count() qui peut retourner un nombre provisoire, utilisez : await page.waitForFunction(() => document.querySelectorAll('.data-row').length === expectedCount && document.querySelector('.loading') === null) . Cela garantit que le nombre de lignes est exact ET que le loader a disparu. Combinez cela avec waitForLoadState('networkidle') pour les requêtes HTTP classiques : Playwright attend que le réseau soit inactif pendant 500ms.

Écrire des tests lisibles et maintenables : Page Object Model adapté

Structurez vos tests avec le Page Object Model (POM), une pratique éprouvée. Créez une classe par page/composant major qui encapsule les sélecteurs et les interactions. L'avantage : si le DOM change, vous corrigez un seul endroit. Exemple : class DashboardPage { async navigateTo() { await page.goto('/dashboard'); } async clickUserFilter(name) { await page.click( button:has-text("${name}") ); } async getUserCount() { return page.locator('.user-row').count(); } } . Vos tests deviennent lisibles : await dashboard.navigateTo(); await dashboard.clickUserFilter('Active'); expect(await dashboard.getUserCount()).toBeGreaterThan(0); .

Ne surchargez pas le POM avec de la logique métier. Gardez-le comme une interface vers le DOM. La logique (attendre, valider, combiner) reste dans le test. Organisez les fichiers en mirroir de votre structure Angular : e2e/pages/dashboard.page.ts correspond à src/app/features/dashboard . Cela facilite la navigation et la maintenance à long terme.

Piège courant : tester les détails d'implémentation au lieu du comportement

Beaucoup de tests E2E Angular échouent car ils testent le ChangeDetectionStrategy ou les détails de NgRx. Exemple problématique : vérifier que *ngIf="isLoading" est false plutôt que vérifier que le loader n'est plus visible. Si vous refactorisez le composant sans changer le comportement, le test casse. Testez toujours le comportement observable : l'utilisateur voit quoi, peut-il interagir, les données sont-elles correctes. Les détails internes (détection de changement, stratégie OnPush, signal vs observable) doivent rester invisibles au test.

Un autre piège : les délais artificiels. Évitez await page.waitForTimeout(2000) . Si votre code a besoin de 2 secondes, c'est un bug ou une mauvaise architecture. Attendez un événement ou une condition. Les seules exceptions : les animations CSS que vous ne pouvez pas détecter autrement (attendez alors la fin avec page.waitForFunction(() => getComputedStyle(...).opacity === '1') ).

Intégration avec le CI/CD et Angular Universal

En CI/CD, lancez Playwright avec --workers=1 si votre backend n'est pas isolé (sinon, les tests parallèles créent des conflits de données). Utilisez des fixtures Playwright pour créer/nettoyer les données de test avant chaque suite. Avec Angular Universal (SSR), les tests E2E restent identiques : le rendu serveur est transparent pour le navigateur. Playwright teste le DOM final, qu'il vienne du serveur ou du client.

Pour les tests de performance (Core Web Vitals), intégrez @playwright/test avec des outils comme web-vitals . Mesurez le temps de première peinture (FCP) et le temps d'interaction (INP) dans les tests E2E. Cela vous alerte si une refacto ralentit l'app en conditions réelles.

Conclusion : une stratégie E2E durable

Tester une app Angular avec Playwright demande de la discipline : respecter le cycle de détection de changement, éviter les mocks au niveau E2E, structurer avec le POM, et tester le comportement, pas l'implémentation. Commencez par les happy paths critiques (authentification, flux de données), puis ajoutez les edge cases. Une suite E2E bien écrite devient une documentation vivante de votre app et un filet de sécurité lors des refactos. Playwright offre la stabilité et la vitesse pour rendre cela viable en production.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️