Подключение к WebSocket

Основы WebSocket

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

Cypress изначально ориентирован на тестирование фронтенда через HTTP-интерфейсы и DOM, но взаимодействие с WebSocket также возможно. Основная сложность заключается в асинхронной природе WebSocket и отсутствии встроенного API Cypress для полноценного управления соединениями. Решения обычно основаны на использовании сторонних библиотек и специальных хуков.

Настройка Cypress для работы с WebSocket

Для подключения к WebSocket в Cypress чаще всего применяются JavaScript-библиотеки, такие как ws или встроенные браузерные API WebSocket. В Node.js-контексте для тестов на уровне сервера удобно использовать ws, а для браузерных тестов — стандартный объект WebSocket.

Пример установки:

npm install ws --save-dev

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

Создание WebSocket-клиента в тесте

Использование WebSocket в тестах Cypress требует управления асинхронными событиями: открытие соединения, получение сообщений и обработка ошибок.

Пример простого подключения:

describe('WebSocket тестирование', () => {
  it('Подключение и получение сообщений', () => {
    cy.window().then((win) => {
      const socket = new win.WebSocket('ws://localhost:8080');

      socket.addEventListener('open', () => {
        console.log('Соединение установлено');
      });

      socket.addEventListener('message', (event) => {
        console.log('Получено сообщение:', event.data);
      });

      socket.addEventListener('close', () => {
        console.log('Соединение закрыто');
      });

      socket.addEventListener('error', (err) => {
        console.error('Ошибка WebSocket:', err);
      });

      // Пример отправки сообщения
      socket.addEventListener('open', () => {
        socket.send(JSON.stringify({ type: 'ping' }));
      });
    });
  });
});

Важный момент: все операции с WebSocket должны выполняться через cy.window() или кастомные Cypress-команды, чтобы сохранялась синхронизация с тестовым раннером. Прямое использование Node.js-версии WebSocket может применяться для тестов на уровне сервера или интеграционных тестов.

Асинхронная синхронизация с Cypress

Cypress управляет тестами синхронно, но WebSocket работает асинхронно. Чтобы интегрировать WebSocket с Cypress, используют Cypress.Promise или кастомные команды, которые оборачивают асинхронные события.

Пример ожидания конкретного сообщения:

Cypress.Commands.add('waitForWebSocketMessage', (socket, expectedType) => {
  return new Cypress.Promise((resolve) => {
    socket.addEventListener('message', function handler(event) {
      const data = JSON.parse(event.data);
      if (data.type === expectedType) {
        socket.removeEventListener('message', handler);
        resolve(data);
      }
    });
  });
});

describe('WebSocket ожидание события', () => {
  it('Должен дождаться сообщения типа "update"', () => {
    cy.window().then((win) => {
      const socket = new win.WebSocket('ws://localhost:8080');

      socket.addEventListener('open', () => {
        socket.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
      });

      cy.waitForWebSocketMessage(socket, 'update').then((message) => {
        expect(message).to.have.property('payload');
      });
    });
  });
});

Этот подход позволяет синхронизировать события WebSocket с Cypress и использовать стандартные проверки и ассерт-методы.

Рекомендации по тестированию WebSocket

  • Изоляция тестов: Каждый тест должен создавать собственное соединение и закрывать его после выполнения. Это предотвращает пересечение сообщений между тестами.
  • Обработка ошибок: WebSocket соединение может неожиданно закрываться или выдавать ошибки. Необходимо всегда подписываться на событие error и корректно завершать соединение.
  • Таймауты: Использовать Cypress-таймауты (cy.wait, Cypress.Promise) для ожидания сообщений, чтобы тесты не зависали навсегда.
  • Логирование: В процессе разработки полезно выводить все входящие и исходящие сообщения, чтобы понимать последовательность событий.
  • Мокация сервера: Для стабильных тестов рекомендуется использовать локальный WebSocket-сервер или моки, чтобы исключить зависимость от внешнего API.

Интеграция с Node.js и серверными тестами

Для тестирования WebSocket на уровне сервера используется библиотека ws:

const WebSocket = require('ws');

describe('Серверный WebSocket тест', () => {
  let server;

  before(() => {
    server = new WebSocket.Server({ port: 8080 });
    server.on('connection', (ws) => {
      ws.on('message', (message) => {
        const data = JSON.parse(message);
        if (data.type === 'ping') {
          ws.send(JSON.stringify({ type: 'pong' }));
        }
      });
    });
  });

  after(() => {
    server.close();
  });

  it('Должен ответить на ping', (done) => {
    const ws = new WebSocket('ws://localhost:8080');

    ws.on('open', () => {
      ws.send(JSON.stringify({ type: 'ping' }));
    });

    ws.on('message', (message) => {
      const data = JSON.parse(message);
      if (data.type === 'pong') {
        done();
      }
    });
  });
});

Такой подход позволяет тестировать серверные обработчики сообщений и их взаимодействие с клиентом в контролируемой среде.

Использование кастомных команд Cypress

Для упрощения повторного кода создаются кастомные команды, которые инкапсулируют подключение и отправку/ожидание сообщений:

Cypress.Commands.add('connectWebSocket', (url) => {
  return cy.window().then((win) => {
    const socket = new win.WebSocket(url);
    return new Cypress.Promise((resolve) => {
      socket.addEventListener('open', () => resolve(socket));
    });
  });
});

Cypress.Commands.add('sendWebSocketMessage', (socket, message) => {
  socket.send(JSON.stringify(message));
});

Использование команд делает тесты компактными и читабельными:

cy.connectWebSocket('ws://localhost:8080').then((socket) => {
  cy.sendWebSocketMessage(socket, { type: 'ping' });
});

Поддержка сложных сценариев

WebSocket-тестирование часто требует:

  • Подписки на несколько каналов одновременно.
  • Параллельного отслеживания сообщений разных типов.
  • Симуляции отключений и повторного подключения.

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


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