Wiki

← Tất cả bài wiki

WebSockets trong Playwright (Bắt và kiểm tra realtime connection)

automationplaywright

Playwright hỗ trợ bắt WebSocket connection qua page.on('websocket'), cho phép đọc từng frame gửi/nhận (framereceived/framesent) để verify app realtime (chat, notification, live dashboard) hoạt động đúng. Giới hạn: chỉ inspect được frame, không mock sẵn được WS server — muốn mock phải can thiệp thêm.

Khái niệm & Bản chất

  • WebSocket là kênh hai chiều bền (persistent) giữa browser và server — khác HTTP request/response một lần. App realtime (chat, noti, giá chứng khoán, live comment) dùng WS hoặc thư viện trên nó (Socket.IO, SignalR).
  • page.on('websocket', handler): event phát khi page mở WS connection (sau handshake HTTP upgrade). Handler nhận WebSocket object của Playwright.
  • Frame là đơn vị dữ liệu: WS truyền bằng frame (text hoặc binary). Playwright phát ws.on('framereceived') khi server → client, ws.on('framesent') khi client → server.
  • Bản chất là OBSERVE: Playwright hiện chỉ cho phép đọc/log frame — KHÔNG có route.fulfill tương đương cho WS. Muốn fake data realtime phải tự build (xem gotchas).

Cách hoạt động & API chính

  • ws.url(): URL của socket (wss://...) — dùng phân biệt nhiều connection (VD: noti socket vs chat socket).
  • ws.on('framereceived', cb): callback nhận { payload } — string (text frame) hoặc Buffer (binary). Parse JSON nếu app dùng JSON message.
  • ws.on('framesent', cb): frame app gửi lên server — verify client emit đúng event/payload (VD: join room, typing).
  • ws.on('close') / ws.isClosed(): theo dõi vòng đời socket.
  • ws.on('socketerror'): bắt lỗi tầng socket.
  • expect.poll + biến collect: vì frame đến bất đồng bộ, thường đẩy payload vào mảng rồi await expect.poll(() => frames.length).toBeGreaterThan(0).

Ví dụ & Cú pháp thực tế

  • ```ts import { test, expect } from '@playwright/test'; test('bắt và log frame websocket', async ({ page }) => { const received: string[] = []; page.on('websocket', (ws) => { console.log('WS opened:', ws.url()); ws.on('framereceived', ({ payload }) => { received.push(payload.toString()); console.log('<<', payload); }); ws.on('framesent', ({ payload }) => console.log('>>', payload)); }); await page.goto('https://chat.hoctest.com'); await page.getByRole('button', { name: 'Kết nối' }).click(); // Frame đến async — poll cho tới khi có await expect.poll(() => received.length).toBeGreaterThan(0); });
  • ```ts test('verify app nhận được message realtime', async ({ page }) => { const messages: any[] = []; page.on('websocket', (ws) => { if (ws.url().includes('/notifications')) { ws.on('framereceived', ({ payload }) => { messages.push(JSON.parse(payload.toString())); }); } }); await page.goto('/dashboard'); // Trigger server push noti (qua API khác chẳng hạn) // ... rồi verify frame đến đúng format await expect .poll(() => messages.some((m) => m.type === 'NEW_ORDER')) .toBe(true); await expect(page.getByText('Đơn hàng mới')).toBeVisible(); });
  • ```ts test('verify client emit đúng event', async ({ page }) => { const sent: any[] = []; page.on('websocket', (ws) => { ws.on('framesent', ({ payload }) => sent.push(payload.toString())); }); await page.goto('/chat/room-1'); await page.getByPlaceholder('Nhắn tin').fill('hello'); await page.getByPlaceholder('Nhắn tin').press('Enter'); // Kiểm tra app gửi frame chứa nội dung tin nhắn await expect .poll(() => sent.some((f) => f.includes('hello'))) .toBe(true); });

Best practices & Khi nào dùng

  • Khi nào dùng: verify tính năng realtime thật sự hoạt động (socket mở được, frame đến đúng format, client emit đúng), debug flaky test liên quan noti/chat, assert UI update khi nhận push event.
  • Filter theo ws.url() ngay trong handler — app thường có nhiều socket (analytics, noti, chat); log hết sẽ rất nhiễu.
  • Parse frame thành object ngay: JSON.parse(payload) vào mảng → assert trên object (m.type, m.payload.id) dễ hơn so sánh chuỗi raw.
  • Kết hợp API trigger: dùng request.post (hoặc 1 page khác) để tạo sự kiện phía server, rồi chờ frame về — pattern verify end-to-end cho realtime.
  • Kiên nhẫn với expect.poll: frame không đến tức thì — poll với timeout hợp lý (5-10s), đừng assert ngay sau click.

Gotchas, So sánh & Lưu ý

  • KHÔNG mock được WS bằng page.route: route chỉ làm việc với HTTP request/response — socket sau handshake thoát khỏi route. Đừng tìm route.fulfill cho WS, không có.
  • Cách 'mock' thực tế: (1) chạy WS server fake trong test bằng package ws rồi trỏ app tới đó (override URL qua route/page.addInitScript), hoặc (2) page.addInitScript thay window.WebSocket bằng class fake — phức tạp nhưng kiểm soát 100%.
  • Socket.IO không phải raw JSON: frame của Socket.IO có protocol prefix (42["event",{...}]) — parse phải strip prefix hoặc match contains.
  • Frame binary: protobuf/MessagePack đến dạng Buffer — cần decoder riêng của app, không đọc được thô.
  • So với HTTP testing: HTTP có request rõ ràng để wait; WS là stream liên tục nên phải đổi tư duy sang 'collect → poll' thay vì 'wait 1 response'.
  • Reconnect/reconnect storm: app reconnect tạo WS object mới → handler cũ vẫn attach vào socket cũ. page.on('websocket') bắt được từng connection mới nên vẫn ok, nhưng mảng collect cần ý thức socket đổi.