Тестирование WebSockets

WebSockets предоставляют двусторонний канал связи между клиентом и сервером, что делает их популярными для приложений реального времени, таких как чаты, игровые платформы и торговые системы. В тестировании таких приложений стандартные методы работы с DOM и HTTP-запросами часто оказываются недостаточными, поэтому Protractor требует дополнительных подходов для взаимодействия с WebSocket-соединениями.

Подключение и конфигурация

Protractor изначально предназначен для работы с Angular-приложениями и синхронизации с Angular-зоной. Для тестирования WebSocket важно убедиться, что асинхронные операции корректно обрабатываются. Основной инструмент — использование async/await и вспомогательных библиотек для работы с WebSocket, таких как ws или socket.io-client.

Пример подключения к WebSocket через socket.io-client:

const io = require('socket.io-client');

let socket;

beforeEach(async () => {
    socket = io('http://localhost:3000');
    await new Promise(resolve => socket.once('connect', resolve));
});

afterEach(() => {
    if (socket.connected) {
        socket.disconnect();
    }
});

Ключевой момент — гарантировать установку соединения до выполнения тестов. Protractor будет ждать завершения await и не продолжит выполнение, пока соединение не установлено.

Отправка и получение сообщений

После установления соединения необходимо тестировать как отправку, так и получение сообщений. WebSocket-сообщения могут приходить с задержкой, поэтому важно использовать ожидания с тайм-аутами.

Пример проверки получения сообщения:

it('should receive a greeting message from the server', async () => {
    socket.emit('hello', { name: 'Protractor' });

    const message = await new Promise(resolve => {
        socket.on('greeting', resolve);
    });

    expect(message.text).toEqual('Hello, Protractor!');
});

Здесь используется промис для ожидания события, что позволяет интегрировать WebSocket-логику в асинхронный поток теста.

Обработка нескольких сообщений и событий

В реальных приложениях WebSocket часто передаёт несколько типов событий одновременно. Для тестирования таких сценариев полезно реализовать хранилище полученных сообщений и фильтровать их по типу события:

let messages = [];

beforeEach(() => {
    messages = [];
    socket.on('update', msg => messages.push({ type: 'update', data: msg }));
    socket.on('notification', msg => messages.push({ type: 'notification', data: msg }));
});

it('should handle multiple events correctly', async () => {
    socket.emit('triggerEvents');

    await browser.sleep(1000); // ожидание прихода событий

    const updates = messages.filter(m => m.type === 'update');
    const notifications = messages.filter(m => m.type === 'notification');

    expect(updates.length).toBeGreaterThan(0);
    expect(notifications.length).toBeGreaterThan(0);
});

Использование временной задержки (browser.sleep) допускается для тестирования потоковых событий, но рекомендуется минимизировать её и применять ожидания на основе фактического события.

Интеграция с Protractor и Angular

Для Angular-приложений важно, чтобы Protractor правильно синхронизировал асинхронные операции WebSocket с зоной Angular. Если события WebSocket изменяют состояние приложения, которое отображается в DOM, нужно использовать browser.wait или ExpectedConditions:

const EC = protractor.ExpectedConditions;

it('should update the DOM after receiving a WebSocket message', async () => {
    socket.emit('updateDOM');

    const elementToUpdate = element(by.id('status'));

    await browser.wait(EC.textToBePresentInElement(elementToUpdate, 'Updated'), 5000);

    const text = await elementToUpdate.getText();
    expect(text).toEqual('Updated');
});

Такой подход обеспечивает корректное ожидание обновлений интерфейса без использования жёстких задержек и повышает стабильность тестов.

Логирование и диагностика

WebSocket-тесты часто сложно отлаживать из-за асинхронной природы. Рекомендуется логировать все события и отправляемые сообщения:

socket.onAny((event, payload) => {
    console.log(`[WebSocket] Event: ${event}, Data:`, payload);
});

Это позволяет видеть последовательность событий и выявлять пропуски или задержки в тестируемой логике.

Параллельное тестирование и очистка соединений

При запуске нескольких тестов параллельно важно закрывать все WebSocket-соединения, чтобы избежать конфликтов и утечек памяти. Каждый тест должен создавать отдельный экземпляр сокета и корректно его отключать в afterEach. В сложных сценариях можно использовать фабрики подключения, возвращающие уникальные сокеты для каждого теста.

Рекомендации по надежности тестов

  • Избегать жёстких тайм-аутов и использовать события для синхронизации.
  • Обрабатывать все типы сообщений и исключения.
  • Логировать события для диагностики и воспроизводимости ошибок.
  • Использовать async/await вместо колбэков для читаемости и стабильности тестов.
  • Учитывать возможные задержки сети и вариативность времени ответа сервера.

Тщательная организация тестов WebSocket в Protractor позволяет проверять не только корректность обмена данными, но и интеграцию сообщений с DOM и бизнес-логикой приложения, обеспечивая высокое качество тестирования реального времени.