Как отслеживать и перехватывать сетевые запросы и ответы
Обзор
Данный рецепт работает только при использовании Chrome DevTools Protocol (CDP).
Читайте подробности в разделе «Как использовать CDP в Testplane»
В CDP имеются домены Fetch и Network, с помощью которых можно получить полный доступ ко всем сетевым запросам и ответам. При использовании Webdriver-протокола нам бы пришлось писать отдельный proxy-сервер и весь трафик направлять через него.
В webdriverio для работы с сетевыми запросами существует метод mock, который использует API домена Fetch.
С помощью этого метода мы можем:
- замокать запрос к ресурсу и вернуть свои данные;
- отменить запрос, вернув необходимую ошибку;
- модифицировать о твет от ресурса;
- перенаправить с запрошенного ресурса на какой-то другой ресурс;
- замокать ресурс и, например, собрать информацию о том, сколько раз этот ресурс вызывался и какой ответ он возвращал.
Давайте попробуем написать тесты с использованием этого API и покрыть разные кейсы. Сразу уточним, что все графические изображения с процессом выполнения тестов замедлены в 2 раза, так как тесты локально выполняются очень быстро и что-то разглядеть довольно проблематично.
Пример 1: мокаем запрос к google.com и возвращаем свой ответ
it("should mock google.com", async function () {
// Мокаем поход на google.com
const mock = await this.browser.mock("https://google.com");
// Возвращаем строку "Hello, world!" вместо ответа от сайта.
// Опция "fetchResponse" отвечает за то, нужно ли делать запрос
// на замоканный ресурс, по умолчанию - true
mock.respond("Hello, world!", { fetchResponse: false });
await this.browser.url("https://google.com");
});
По графическому изображению видно, что мы вернули свой текст, при этом в строке браузера отображается как будто мы выполнили переход на google.com. Также видно, что мы не замокали фавиконку и она приехала снаружи. Этот же самый пример мы можем написать с использованием API puppeteer'а, для этого в webdriverio реализована команда getPuppeteer():
it("should mock google.com using puppeteer api", async function () {
// Получаем инстанс puppeteer'а
const puppeteer = await this.browser.getPuppeteer();
// Получаем первую открытую страницу (считаем, что она активная в данный момент)
const [page] = await puppeteer.pages();
// Активируем перехват всех запросов
await page.setRequestInterception(true);
page.on("request", async request => {
if (request.url() !== "https://google.com/") {
// Если урл запроса не матчится на https://google.com/,
// то выполняем запрос (т. е. не перехватываем его)
return request.continue();
}
// отвечаем своими данными
return request.respond({ body: "Hello, world!" });
});
// Здесь можно было бы вызвать и "page.goto('https://google.com')", но лучше вызывать "url",
// так как в большинстве плагинов есть обертки команды "url", добавляющие дополнительную
// логику. Например, в testplane добавляется урл в мету.
await this.browser.url("https://google.com");
});
Хардкорный вариант с использованием CDP напрямую
А теперь представим, что в puppeteer еще нет API для мока запросов, но это уже реализовано в домене Fetch CDP. В этом случае воспользуемся методом этого домена через общение с CDP-сессией напрямую. Для этого в puppeteer есть метод CDPSession.send():
it("should mock google.com using cdp fetch domain", async function () {
// Получаем инстанс puppeteer'а
const puppeteer = await this.browser.getPuppeteer();
// Получаем первую открытую страницу (считаем, что она активная в данный момент)
const [page] = await puppeteer.pages();
// Создаем CDP-сессию
const client = await page.target().createCDPSession();
// Включаем возможность перехватить запрос с помощью подписки на событие "requestPaused"
await client.send("Fetch.enable");
client.on("Fetch.requestPaused", event => {
const {
request: { url },
requestId,
responseHeaders,
} = event;
if (url !== "https://google.com/") {
// Если урл запроса не матчится на https://google.com/,
// то выполняем запрос (т. е. не перехватываем его)
return client.send("Fetch.continueRequest", { requestId });
}
// Подменяем ответ на свой и упаковываем его в base64
return client.send("Fetch.fulfillRequest", {
requestId,
responseCode: 200,
responseHeaders,
body: Buffer.from("Hello, world!", "utf8").toString("base64"),
});
});
await this.browser.url("https://google.com");
});
Очевидно, что при использовании API webdriverio для мока запросов код получается сильно короче, но API webdriverio сильно ограничен и для более сложных кейсов необходимо использовать API puppeteer'а. При этом в самом puppeteer'е тоже может не быть API для каких-то новых методов или доменов CDP. Поэтому в редких случаях может пригодиться общение по CDP напрямую с помощью CDPSession.send().
Пример 2: отменяем запрос за логотипом гугла
it("should abort request to logo on google.com", async function () {
// В качестве урла можно использовать маску
const mock = await this.browser.mock("https://www.google.com/images/**/*.png");
// Кидаем ошибку "ERR_FAILED" при загрузке ресурса, сматчившегося на маску мока
mock.abort("Failed");
await this.browser.url("https://google.com");
});
По графическому изображению видно, что логотип не отобразился и в логе присутствует ошибка net::ERR_FAILED
. Такое решение может быть удобно для отключения каких-то скриптов, которые мешают быстрому выполнению теста. Например, можно отключить сбор аналитики.