Playwright expose les événements WebSocket via page.on('websocket'), ce qui permet de vérifier l'établissement de la connexion et d'inspecter les messages entrants et sortants. Il permet aussi de simuler des réponses serveur pour tester des fonctionnalités temps réel sans serveur actif.

Ce que ressemble le test WebSocket en pratique

Contrairement aux requêtes HTTP, les WebSockets sont des connexions bidirectionnelles persistantes. Une seule connexion peut transporter des milliers de messages pendant sa durée de vie. Tester des WebSockets signifie :

1. Vérifier que la connexion est établie

2. Vérifier que l'application envoie les bons messages

3. Vérifier que l'application gère correctement les messages reçus

4. Tester ce qui se passe quand la connexion tombe

Écouter les événements WebSocket

Playwright déclenche des événements pour les connexions WebSocket :

import { test, expect } from '@playwright/test';

test('l\'application de chat établit une connexion WebSocket', async ({ page }) => {
  // Écouter les connexions WebSocket
  const wsConnected = page.waitForEvent('websocket');

  await page.goto('/chat');

  const ws = await wsConnected;
  expect(ws.url()).toContain('/ws/chat');

  console.log('Connecté à :', ws.url());
});

Capturer les messages WebSocket

test('le chat envoie un message via WebSocket', async ({ page }) => {
  const messages: string[] = [];

  page.on('websocket', ws => {
    ws.on('framesent', frame => {
      // Messages envoyés du navigateur vers le serveur
      if (frame.text) messages.push(frame.text);
    });
  });

  await page.goto('/chat');
  await page.getByPlaceholder('Écris un message').fill('Bonjour !');
  await page.keyboard.press('Enter');

  // Vérifier que le message WebSocket a été envoyé
  await expect.poll(() => messages).toContain(
    JSON.stringify({ type: 'message', content: 'Bonjour !' })
  );
});

test('le chat reçoit un message et l\'affiche', async ({ page }) => {
  const receivedFrames: string[] = [];

  page.on('websocket', ws => {
    ws.on('framereceived', frame => {
      // Messages reçus du serveur par le navigateur
      if (frame.text) receivedFrames.push(frame.text);
    });
  });

  await page.goto('/chat');

  // Attendre qu'au moins un message arrive (confirmation de connexion, par exemple)
  await expect.poll(() => receivedFrames.length).toBeGreaterThan(0);

  // Vérifier que le message est affiché dans l'interface
  const firstMessage = JSON.parse(receivedFrames[0]);
  await expect(page.getByText(firstMessage.content)).toBeVisible();
});

framesent : messages que le navigateur a envoyés au serveur. framereceived : messages que le serveur a envoyés au navigateur.

Simuler des réponses WebSocket

Le pattern le plus puissant : intercepter la connexion et simuler des messages serveur sans faire tourner un vrai serveur.

Playwright n'a pas d'API native de mock WebSocket, mais tu peux injecter un mock dans le contexte du navigateur :

test('l\'application affiche une notification quand le serveur envoie une alerte', async ({ page }) => {
  await page.addInitScript(() => {
    const OriginalWebSocket = window.WebSocket;

    window.WebSocket = class MockWebSocket extends EventTarget {
      url: string;
      readyState = 1; // OPEN

      constructor(url: string) {
        super();
        this.url = url;

        // Simuler l'ouverture de la connexion
        setTimeout(() => {
          this.dispatchEvent(new Event('open'));

          // Simuler un message serveur après 100ms
          setTimeout(() => {
            const messageEvent = new MessageEvent('message', {
              data: JSON.stringify({ type: 'notification', text: 'Nouvelle commande reçue !' }),
            });
            this.dispatchEvent(messageEvent);
          }, 100);
        }, 0);
      }

      send(data: string) {
        console.log('WebSocket send:', data);
      }

      close() {
        this.readyState = 3;
        this.dispatchEvent(new CloseEvent('close'));
      }
    } as any;
  });

  await page.goto('/dashboard');

  // Vérifier que la notification apparaît quand le message WebSocket arrive
  await expect(page.getByRole('alert')).toBeVisible({ timeout: 2000 });
  await expect(page.getByRole('alert')).toHaveText('Nouvelle commande reçue !');
});

Tester la déconnexion et la reconnexion

Les applications temps réel doivent gérer la perte de connexion sans casser l'expérience utilisateur :

test('l\'application affiche un état "reconnexion" quand le WebSocket se coupe', async ({ page }) => {
  await page.addInitScript(() => {
    const OriginalWebSocket = window.WebSocket;

    window.WebSocket = class extends OriginalWebSocket {
      constructor(url: string) {
        super(url);
        // Exposer l'instance pour le contrôle du test
        (window as any).__wsInstance = this;
      }
    } as any;
  });

  await page.goto('/chat');

  // Attendre l'établissement de la connexion
  await page.waitForEvent('websocket');

  // Forcer la fermeture de la connexion WebSocket
  await page.evaluate(() => {
    (window as any).__wsInstance?.close();
  });

  // Vérifier que l'interface affiche l'état de reconnexion
  await expect(page.getByText('Reconnexion...')).toBeVisible();

  // Vérifier que la reconnexion aboutit
  await expect(page.getByText('Connecté')).toBeVisible({ timeout: 10000 });
});

Utiliser routeWebSocket dans les versions récentes de Playwright

Playwright 1.48+ a introduit page.routeWebSocket() pour un mock WebSocket plus ergonomique :

// Playwright 1.48+
test('le statut d\'une commande se met à jour en temps réel', async ({ page }) => {
  await page.routeWebSocket('/ws/orders', ws => {
    ws.onopen = () => {
      // Envoyer l'état initial
      ws.send(JSON.stringify({ orderId: '123', status: 'processing' }));
    };

    ws.onmessage = (message) => {
      const data = JSON.parse(message.data);
      if (data.type === 'subscribe' && data.orderId === '123') {
        // Simuler la progression du statut
        setTimeout(() => ws.send(JSON.stringify({ orderId: '123', status: 'shipped' })), 500);
        setTimeout(() => ws.send(JSON.stringify({ orderId: '123', status: 'delivered' })), 1000);
      }
    };
  });

  await page.goto('/orders/123');

  await expect(page.getByTestId('status')).toHaveText('Processing');
  await expect(page.getByTestId('status')).toHaveText('Shipped', { timeout: 2000 });
  await expect(page.getByTestId('status')).toHaveText('Delivered', { timeout: 3000 });
});

Vérifie ta version de Playwright avant d'utiliser routeWebSocket : elle n'est pas disponible dans les versions anciennes.

Quand tester les WebSockets

Toutes les fonctionnalités temps réel ne nécessitent pas des tests WebSocket dédiés. Priorités :

  • Établissement de la connexion pour les fonctionnalités critiques (chat, tableau de bord live)
  • Comportement lors d'une perte de connexion (affiche une erreur ? reconnecte ? perd des données ?)
  • Ordre des messages pour les opérations séquentielles
  • Authentification via WebSocket (la connexion est-elle refusée sans auth valide ?)

Passe les tests au niveau WebSocket quand la fonctionnalité est fiable et que le test E2E UI la couvre déjà. C'est aussi le bon choix si le WebSocket est tiers ou si le format des messages est mieux couvert par un test unitaire backend.

→ See also: Interception Réseau, Mocking et Stubbing dans Playwright | Tests d'API avec l'APIRequestContext de Playwright (Sans Postman) | Tests d'API GraphQL avec Playwright: Requêtes, Mutations et Gestion des Erreurs