Щоб зрозуміти WebSocket — спочатку треба зрозуміти як працює звичайний HTTP.
HTTP запит — як SMS:
Ти відправляєш повідомлення → сервер відповідає → з’єднання закривається. Щоразу коли треба щось отримати — треба відправити новий запит. Кожен запит це окреме з’єднання яке відкривається і закривається. Ефективно для сайтів де дані рідко змінюються — завантажив сторінку, все, чекати нічого.
WebSocket — як телефонна розмова:
З’єднання відкривається один раз і лишається відкритим. Обидві сторони можуть надсилати повідомлення одна одній в будь-який момент без нового запиту. Ефективно коли потрібен постійний обмін даними в реальному часі — чати, онлайн-ігри, фінансові тікери, і саме Playwright для керування браузером.
Чому Playwright використовує WebSocket: Playwright повинен постійно надсилати команди браузеру і отримувати результати. Якби використовував HTTP — кожна дія (click, fill, evaluate) потребувала б нового з’єднання. WebSocket відкривається один раз і через нього проходять всі команди протягом всього тесту — швидко і ефективно.
Схема: HTTP vs WebSocket
// ── HTTP — нове з'єднання на кожен запит ─────────────────────────
//
// Клієнт Сервер
// ─────── ──────
// "GET /page" ──────► з'єднання відкрилось
// ◄────── HTML з'єднання закрилось
//
// "GET /api/data" ───► нове з'єднання
// ◄──── JSON закрилось
//
// Кожен запит = відкрити + закрити з'єднання
// ── WebSocket — одне з'єднання на весь час ───────────────────────
//
// Клієнт Сервер
// ─────── ──────
// "привіт" ────────► з'єднання відкрилось — І ЛИШАЄТЬСЯ ВІДКРИТИМ
// ◄──── "привіт"
// "клікни .btn" ───► (те саме з'єднання)
// ◄──── "ok"
// "заповни #input" ► (те саме з'єднання)
// ◄──── "ok"
// ... з'єднання закривається тільки в кінці
Як WebSocket виглядає в Playwright — під капотом
// Ти пишеш звичайний код тесту:
await page.goto('/products');
await page.locator('.add-to-cart').click();
await page.fill('#promo', 'SAVE10');
// Під капотом Playwright надсилає через WebSocket команди браузеру:
// → { method: "Page.navigate", params: { url: "/products" } }
// ← { result: { frameId: "..." } }
//
// → { method: "DOM.querySelector", params: { selector: ".add-to-cart" } }
// ← { result: { nodeId: 42 } }
//
// → { method: "Input.dispatchMouseEvent", params: { type: "click", ... } }
// ← { result: {} }
//
// Всі ці повідомлення йдуть через ОДНЕ WebSocket з'єднання
// Це і є Chrome DevTools Protocol (CDP) — мова на якій Playwright говорить з браузером
TypeScript — WebSocket в тестах: page.on(‘websocket’)
// Окрема тема: WebSocket на самій сторінці
// Якщо сторінка використовує WebSocket для реалтайм даних (чат, тікери) —
// Playwright дозволяє слухати ці з'єднання через page.on('websocket')
test('WebSocket отримує оновлення ціни', async ({ page }) => {
const wsMessages: string[] = [];
// Підписуємось на WebSocket з'єднання сторінки
page.on('websocket', ws => {
console.log('WS відкрито:', ws.url());
// Слухаємо фрейми які приходять від сервера
ws.on('framereceived', event => {
wsMessages.push(event.payload as string);
});
ws.on('close', () => console.log('WS закрито'));
});
await page.goto('/prices');
// Чекаємо поки прийде повідомлення про ціну
// ✅ Правильно: wsMessages живе в Node.js — перевіряємо через expect.poll
await expect.poll(() => wsMessages.length, { timeout: 10_000 })
.toBeGreaterThan(0);
// ❌ НЕ ПРАВИЛЬНО: window.wsMessages не існує в браузері!
// await page.waitForFunction(() => (window as any).wsMessages?.length > 0);
// wsMessages — це змінна Node.js, вона недоступна в контексті браузера
// Перевіряємо вміст WebSocket повідомлення
const priceUpdate = JSON.parse(wsMessages[0]);
expect(priceUpdate).toHaveProperty('price');
});
💡 Два різних WebSocket в контексті Playwright:
1. WebSocket між Playwright і браузером — внутрішній, завжди є, через нього Playwright надсилає команди. Це Chrome DevTools Protocol (CDP). Ти його не бачиш, він просто існує під капотом.
2. WebSocket на самій сторінці — якщо сайт використовує WebSocket для реалтайм даних (чат, стрімінг цін). Цей можна слухати через page.on('websocket').
🔧 TypeScript / JS патерни в цьому прикладіpage.on('event', callback) — патерн підписки на події (Observer/EventEmitter): передаємо callback-функцію яка викличеться коли подія відбудеться; ws => — стрілочна функція як callback; ws.on('framereceived', event => {}) — вкладені підписки на події; new DataTransfer() — створення екземпляру класу через new.
🗣 Як читати вголос: WebSocket — це протокол для постійного двостороннього з’єднання між двома програмами. Відрізняється від звичайного HTTP тим що HTTP це запит-відповідь-закрито, кожна взаємодія це окреме з’єднання — як SMS. WebSocket відкривається один раз і лишається відкритим, обидві сторони можуть надсилати повідомлення в будь-який момент — як телефонна розмова. Playwright використовує WebSocket щоб керувати браузером: на початку тесту відкривається одне з’єднання через яке проходять всі команди — navigate, click, fill, evaluate — і всі відповіді від браузера. Це Chrome DevTools Protocol, CDP — мова на якій Playwright говорить з браузером. Це внутрішнє з’єднання яке ти не бачиш але воно є завжди. Окремо від цього, якщо сама сторінка яку ти тестуєш використовує WebSocket для реалтайм даних — наприклад чат або стрімінг цін — ти можеш слухати ці з’єднання через page.on('websocket') і перевіряти вміст повідомлень які приходять від сервера.