Понедельник, утро, открываешь проект, а авторизация на dev-стенде снова сломана. Или же надо поторопиться с релизом фичи, а бэк еще не готов, и разрабатывать нужно параллельно, и тут без моков не обойтись. Ну или классика: в командировке лежит интернет, и вся работа встала колом. Знакомо?

Что тут можно поделать? Можно подождать, пока починят стенд. Можно залезть в код и что-нибудь там нахардкодить, переполнив его ненужной логикой моков. Можно поставить Postman, Insomnia или другие популярные решения. Но у каждого подхода есть подводные камни: простои и сорванные дедлайны, забытые хардкоды в продакшене, необходимость согласований с инфобезом (особенно если вы работаете в банке) и зависимость от внешних серверов.

Мы попробовали существующие решения и поняли: ни одно из них не закрывает наши потребности полностью. Нужно было что-то свое: простое в установке, работающее офлайн, не требующее дополнительных приложений и аккаунтов. Что-то, что можно быстро настроить под себя и не зависеть от внешних решений вендора. Так появился наш велосипед — браузерное расширение Req-Saver. 

Да, представимся. Мы — Александр Битько и Дмитрий Панфилов, фронтенд-разработчики в ПСБ. Сегодня расскажем, как превратили мокирование запросов из головной боли в простую и понятную работу.

Что предлагает рынок: популярные решения и подводные камни

Прежде чем изобретать велосипед, мы изучили, что уже существует. Инструменты есть, но у каждого свои проблемы.

Chromium Overrides — встроенная фича Chrome и Яндекс Браузера. Казалось бы, идеально: ничего устанавливать не нужно, открыл DevTools и мокай запросы. Но, как оказалось, этим не очень удобно пользоваться. Чтобы создать правило, нужно сначала поймать живой запрос, а потом уже от него плясать. Вдобавок никакого импорта из OpenAPI, все правила только руками.

Пример работы в Chromium Overrides
Пример работы в Chromium Overrides

Postman с его Mock Server — это уже довольно зрелый продукт. У него удобный редактор, импорт схем, множество настроек. Но есть нюанс: нужно качать отдельное приложение, поднимать мок-сервер, заводить аккаунт. И самое неприятное — моки работают только на серверах Postman, так что в офлайне все равно ничего не работает.

Пример работы в Postman
Пример работы в Postman

Insomnia — та же история, только с более современным интерфейсом и своими багами при импорте OpenAPI. В платной версии можно поднять сервер локально, но опять же — отдельное приложение.

Пример работы в Insomnia
Пример работы в Insomnia

Mockoon предлагает бесплатный локальный запуск, что уже лучше. Но суть та же: требуется отдельное приложение.

Пример работы в Mockoon
Пример работы в Mockoon

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

Браузерное расширение Req-Saver

В итоге мы пошли своим путем: никаких отдельных приложений и аккаунтов, просто расширение для браузера. Скачал архив, распаковал, добавил в Яндекс Браузер или Chrome — и все работает.

Главное окно Req-Saver
Главное окно Req-Saver

Под капотом проект собран на React с UI-фреймворком Material-UI — получается знакомый и понятный интерфейс. Для сборки используем Vite, потому что у него есть готовый плагин для горячей перезагрузки при разработке расширений. Это серьезно экономит время разработки.

Самое интересное — то, как мы перехватываем запросы. Используем встроенный Chrome DevTools Protocol (CDP), который позволяет активировать режим отладчика и перехватывать все HTTP-запросы на лету. А для редактирования JSON-ответов встроили Monaco Editor — тот самый движок, что работает в VS Code. Так что, если вы привыкли к Visual Studio Code, здесь будете чувствовать себя как дома.

Пример кода по обработке перехваченного запроса:

Debugger.Fetch.Events.RequestPaused.subscribe(async (eventData) => {
 let rule: IOverrideRule | null = null;
 try {
   rule = ruleMatcher.matchRule(eventData);
 } catch {
   console.warn("Не удалось найти правило");
 }
 if (rule) {
   const requestMutator = new RequestMutator();
   const mutableRequest = MutableRequest.fromEvent(eventData);
   requestMutator.mutate(mutableRequest, rule);
   const fullRequest = mutableRequest.build();
   if (
     fullRequest.response ||
     fullRequest.statusCode ||
     fullRequest.statusPhrase
   ) {
     const fulfillRequest: Protocol.Fetch.FulfillRequestRequest = {
       requestId: fullRequest.id,
       body: fullRequest.response,
       responseCode: fullRequest.statusCode ?? 200,
       responsePhrase: fullRequest.statusPhrase || "OK",
     };
     if (eventData.responseStatusCode === undefined) {
       Debugger.Fetch.Methods.FulfillRequest.send(fulfillRequest);
     } else {
       Debugger.Fetch.Methods.ContinueResponse.send(fulfillRequest);
     }
     return;
   }
   const requestHeaders: Protocol.Fetch.HeaderEntry[] = Object.entries(
     fullRequest.requestHeaders
   ).map((entry) => ({ name: entry[0], value: entry[1] }));

   Debugger.Fetch.Methods.ContinueRequest.send({
     requestId: fullRequest.id,
     headers: requestHeaders,
   });
   return;
 }
 const isResponse = eventData.responseStatusCode !== undefined;
 if (isResponse) {
   Debugger.Fetch.Methods.ContinueResponse.send({
     requestId: eventData.requestId,
   });
 } else {
   Debugger.Fetch.Methods.ContinueRequest.send({
     requestId: eventData.requestId,
   });
 }
});

Идея простая: установил за 30 секунд и работаешь сразу. Весь код лежит на GitLab, легко поддается модификации под нужды команды. Полный контроль над решением означает, что мы можем быстро добавлять фичи, которые реально нужны разработчикам, а не гнаться за универсальностью.

Дополнительно мы сделали импорт и экспорт настроек, запросов и тому подобного в JSON, чтобы все, что нужно, можно перекинуть коллеге или использовать как бэкап.

Немного подробностей технической реализации 

Что такое Chrome DevTools Protocol

Chrome DevTools Protocol (CDP) — это низкоуровневый протокол взаимодействия с движком Chromium и браузерами на его основе (Chrome, Edge, Electron и т.д.). По сути это JSON-RPC поверх WebSocket или pipe-соединения, которое позволяет напрямую управлять внутренними процессами браузера: от сетевых запросов и исполнения JavaScript до работы с DOM, рендерингом и профилированием.

Протокол построен вокруг доменов (Domains), которые группируют команды и события по областям функциональности. Например:

  • Network — перехват и модификация HTTP-запросов/ответов;

  • Runtime — исполнение JavaScript-кода в контексте страницы;

  • Debugger — управление точками останова, пошаговая отладка;

  • Fetch — более высокий уровень перехвата запросов с возможностью модификации;

  • Page, DOM, CSS, Profiler, Log и многие другие.

Каждый домен предоставляет:

  • методы (Methods) — команды, которые можно отправить браузеру (например, Fetch.continueRequest, Network.enable, Runtime.evaluate);

  • события (Events) — уведомления от браузера о происходящих изменениях (например, Fetch.requestPaused, Debugger.paused, Network.responseReceived).

Принцип работы CDP

Клиент (например, наше расширение или отдельный инструмент) открывает соединение с браузером через WebSocket.

Для каждого действия формируется JSON-запрос с уникальным id, именем метода (method) и аргументами (params). 

{ "id": 1, "method": "Fetch.enable", "params": { "patterns": [{ "urlPattern": "*" }] } } 

Браузер возвращает результат выполнения команды с этим же id. 

{ "id": 1, "result": {} } 

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

{ "method": "Fetch.requestPaused", "params": { "requestId": "1234", "request": { "url": "https://example.com", "headers": {} } } } 

Основной функционал, используемый в нашем случае

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

  • Debugger.Fetch.Events.RequestPaused.subscribe(callback) // Подписаться на событие Fetch.requestPaused

  • Debugger.Fetch.Methods.ContinueRequest.send(payload) // Отправить команду Fetch.continueRequest

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

Механизм отлова запросов использует событие Fetch.requestPaused, которое позволяет получить всю необходимую информацию о входящем запросе (URL, заголовки, тело). Далее запрос можно:

  • модифицировать (например, подменить тело ответа, статус или заголовки);

  • продолжить без изменений (Fetch.continueRequest);

  • заблокировать или заменить другим ответом (Fetch.fulfillRequest).

Перехват происходит путем перебора каждого правила и списка критериев. Если запрос соответствует одному из правил, запускается цепочка модификаторов:

  • модификатор тела ответа;

  • модификатор статуса ответа;

  • модификатор заголовков.

Каждый модификатор вносит свои изменения, и на выходе формируется уже новый вариант запроса/ответа, который возвращается на фронт.

Взаимодействие фронта и воркера

Чтобы обеспечить коммуникацию между воркером расширения и фронтовой частью, используется глобальный объект chrome.runtime, доступный как в браузерном контексте, так и в сервис-воркере. Общение строится по классической модели:

  • фронт отправляет сообщение через chrome.runtime.sendMessage;

  • воркер слушает событие через chrome.runtime.onMessage.addListener;

  • при необходимости воркер отправляет ответ обратно.

Таким образом, мы получаем полную цепочку:

Три сценария использования: от простого к сложному

В реальной работе у нас возникают разные кейсы для мокирования запросов. Мы выделили три основных, под которые и заточили интерфейс.

1. Бэкенд еще в разработке

Классика: дизайн готов, фронтенд почти написан, а API еще только в планах. Раньше приходилось либо ждать бэкендеров, либо хардкодить данные прямо в компонентах.

В Req-Saver создаешь правило с нуля, настраивая критерии: указываешь метод (поддерживается GET, POST, DELETE, PATCH, OPTIONS, PUT, HEAD), путь к эндпоинту. В редакторе JSON набиваешь нужный ответ, выставляешь статус — и вуаля, фронтенд думает, что работает с настоящим API. Можно спокойно разрабатывать и тестировать логику, не дожидаясь готового бэкенда.

2. Тестируем edge-случаи

А вот этот кейс посложнее. Допустим, нужно проверить несколько путей пользователя: основной флоу работает, но что будет при ошибке 500? А если API вернет пустой массив? А если в ответе придут некорректные данные?

Тут удобнее не создавать правила с нуля, а работать с готовыми. В Req-Saver включаешь режим записи, проходишь обычный сценарий — все правила автоматически сохраняются. Потом берешь любое записанное правило и модифицируешь: меняешь статус на 500, очищаешь массив данных, ломаешь JSON. Мы предусмотрели удобный интерфейс, позволяющий формировать различные группы под отладку различных кейсов и при разработке удобно в онлайн-режиме подмены запросов переключаться между ними.

Переключаешься между вариантами одним кликом и смотришь, как ведет себя интерфейс.

3. Сломанная инфраструктура / умершая сеть

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

При рабочей авторизации записываешь весь пользовательский путь — обычно это порядка 60 запросов от логина до основной работы. Когда что-то ломается, включаешь режим подмены, выбираешь записанную группу — и авторизация проходит по сохраненным ответам. Дальше проходишь свой конечный кейс.

Пользовательский интерфейс

Следуя за задачами, визуальный интерфейс мы разделили на две логические части: левая панель — управление правилами, группами, переключение режимов записи и подмены; правая — детальная работа с конкретным правилом и его кастомизация. Никаких лишних вкладок и меню, все под рукой.

Основные критерии для перехвата запросов, как уже говорилось выше, — это метод (GET, POST, DELETE, PATCH, OPTIONS, PUT, HEAD), сам путь к эндпоинту, плюс можно добавлять query-параметры вроде фильтров или поиска. Еще один полезный критерий — статус ответа. Это позволяет очень точно настроить, какие именно запросы нужно перехватывать, а какие пусть идут как обычно. Когда работаешь в режиме подмены, видишь рядом с каждым правилом циферку — сколько раз он сработал. Кажется мелочью, но на практике очень полезно: сразу видно, какие моки реально используются в твоем сценарии, а какие висят мертвым грузом.

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

Да, немного о группах: представьте, вы работаете с несколькими приложениями одновременно. Без группировки все правила сваливаются в одну кучу — получается мусор, в котором ничего не найдешь. А так создаешь группу для каждого проекта, записываешь туда соответствующие правила — и порядок. Когда переключаешься на режим подмены, можно комбинировать правила из нескольких групп. Допустим, есть группа с основным флоу и группа с error-кейсами, выбираешь нужные правила из обеих и получаешь идеальную настройку для конкретной задачи.

Для работы с JSON реализован полноценный редактор с подсветкой синтаксиса — можно копипастить готовые объекты или писать с нуля. Статус ответа меняется простым выбором из списка, особенно это удобно при тестировании error-кейсов.

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

Еще мы добавили импорт OpenAPI-схемы, которая позволяет создавать большое количество правил всего в пару кликов. Загружаешь файл с описанием API (тот же swagger) и автоматически получаешь заготовки для всех эндпоинтов проекта, а тестовые данные автоматически генерируются с помощью библиотеки faker.js. Это серьезно ускоряет первоначальную настройку моков.

Планы развития

Сейчас у нас есть работающий MVP (да, несмотря на приличный функционал, мы все еще считаем его MVP), но впереди много интересного. Из планов по развитию: добавить новые возможности для перехвата запросов — использование переменных и локальных сниппетов. Переменные позволят делать ответы более динамичными, а сниппеты — выполнять небольшие куски вашего кода, давая вам полную свободу при подмене.

Вместо заключения

Что же в результате? Мы довольны тем, что не зависим от решения вендора и можем дорабатывать и кастомизировать инструмент под нужды команды. Уточню, что Req-Saver стал удобным инструментом не только для разработчиков, но и для тестировщиков. 

Возможность записи запросов на практике оказалась критически важной. Не все конкуренты предоставляют эту фишку, а она реально нужна. Вспомните кейс с авторизацией — 60 запросов, представьте, если бы их пришлось описывать руками.

И конечно, мы постарались сделать решение удобным. Гибкий user-friendly интерфейс, переключение между правилами, подмена только нужных запросов, комбинирование в единые группы под разные задачи — все это работает уже сейчас.

Рады были поделиться. Если возникли вопросы по технической реализации, задавайте в комментариях.

Комментарии (0)