Моки и шпионы запросов
WebdriverIO поставляется со встроенной поддержкой для модификации сетевых ответов, что позволяет сосредоточиться на тестировании вашего фронтенд-приложения без необходимости настраивать бэкенд или сервер-заглушку. Вы можете определить пользовательские ответы для веб-ресурсов, таких как запросы REST API, в вашем тесте и динамически изменять их.
Обратите внимание, что использование команды mock
требует поддержки протокола Chrome DevTools. Такая поддержка предоставляется, если вы запускаете тесты локально в браузере на основе Chromium, через Selenium Grid v4 или выше, или через облачного поставщика с поддержкой протокола Chrome DevTools (например, SauceLabs, BrowserStack, LambdaTest). Полная кросс-браузерная поддержка будет доступна, когда необходимые примитивы появятся в Webdriver Bidi и будут реализованы в соответствующих браузерах.
Создание мока
Прежде чем вы сможете изменять любые ответы, вы должны сначала определить мок. Этот мок описывается URL-адресом ресурса и может быть отфильтрован по методу запроса или заголовкам. Ресурс поддерживает glob-выражения от minimatch:
// мокировать все ресурсы, оканчивающиеся на "/users/list"
const userListMock = await browser.mock('**/users/list')
// или вы можете указать мок, фильтруя ресурсы по заголовкам или
// коду статуса, мокировать только успешные запросы к json-ресурсам
const strictMock = await browser.mock('**', {
// мокировать все json-ответы
requestHeaders: { 'Content-Type': 'application/json' },
// которые были успешными
statusCode: 200
})
Указание пользовательских ответов
После определения мока вы можете определить для него пользовательские ответы. Эти пользовательские ответы могут быть объектом для ответа в формате JSON, локальным файлом для ответа с пользовательским фикстурой или веб-ресурсом для замены ответа ресурсом из Интернета.
Мокирование API-запросов
Чтобы мокировать API-запросы, где вы ожидаете JSON-ответ, все, что вам нужно сделать, это вызвать respond
на объекте мок с произвольным объектом, который вы хотите вернуть, например:
const mock = await browser.mock('https://todo-backend-express-knex.herokuapp.com/')
mock.respond([{
title: 'Injected (non) completed Todo',
order: null,
completed: false
}, {
title: 'Injected completed Todo',
order: null,
completed: true
}], {
headers: {
'Access-Control-Allow-Origin': '*'
},
fetchResponse: false
})
await browser.url('https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/')
await $('#todo-list li').waitForExist()
console.log(await $$('#todo-list li').map(el => el.getText()))
// выводит: "[ 'Injected (non) completed Todo', 'Injected completed Todo' ]"
Вы также можете изменить заголовки ответа, а также код состояния, передав некоторые параметры мок-ответа следующим образом:
mock.respond({ ... }, {
// ответить с кодом состояния 404
statusCode: 404,
// объединить заголовки ответа со следующими заголовками
headers: { 'x-custom-header': 'foobar' }
})
Если вы не хотите, чтобы мок вообще обращался к бэкенду, вы можете передать false
для флага fetchResponse
.
mock.respond({ ... }, {
// не вызывать реальный бэкенд
fetchResponse: false
})
Рекомендуется хранить пользовательские ответы в файлах фикстур, чтобы вы могли просто импортировать их в своем тесте следующим образом:
// требуется Node.js v16.14.0 или выше для поддержки утверждений импорта JSON
import responseFixture from './__fixtures__/apiResponse.json' assert { type: 'json' }
mock.respond(responseFixture)
Мокирование текстовых ресурсов
Если вы хотите изменить текстовые ресурсы, такие как файлы JavaScript, CSS или другие текстовые ресурсы, вы можете просто передать путь к файлу, и WebdriverIO заменит исходный ресурс им, например:
const scriptMock = await browser.mock('**/script.min.js')
scriptMock.respond('./tests/fixtures/script.js')
// или ответить своим пользовательским JS
scriptMock.respond('alert("I am a mocked resource")')
Перенаправление веб-ресурсов
Вы также можете просто заменить веб-ресурс другим веб-ресурсом, если ваш желаемый ответ уже размещен в Интернете. Это работает как с отдельными ресурсами страницы, так и с самой веб-страницей, например:
const pageMock = await browser.mock('https://google.com/')
await pageMock.respond('https://webdriver.io')
await browser.url('https://google.com')
console.log(await browser.getTitle()) // возвращает "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js"
Динамические ответы
Если ваш мок-ответ зависит от ответа исходного ресурса, вы также можете динамически изменять ресурс, передав функцию, которая получает исходный ответ в качестве параметра и устанавливает мок на основе возвращаемого значения, например:
const mock = await browser.mock('https://todo-backend-express-knex.herokuapp.com/', {
method: 'get'
})
mock.respond((req) => {
// заменить содержимое задачи на их порядковый номер
return req.body.map((item, i) => ({ ...item, title: i }))
})
await browser.url('https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/')
await $('#todo-list li').waitForExist()
console.log(await $$('#todo-list li label').map((el) => el.getText()))
// возвращает
// [
// '0', '1', '2', '19', '20',
// '21', '3', '4', '5', '6',
// '7', '8', '9', '10', '11',
// '12', '13', '14', '15', '16',
// '17', '18', '22'
// ]
Прерывание моков
Вместо возврата пользовательского ответа вы также можете просто прервать запрос с одной из следующих ошибок HTTP:
- Failed
- Aborted
- TimedOut
- AccessDenied
- ConnectionClosed
- ConnectionReset
- ConnectionRefused
- ConnectionAborted
- ConnectionFailed
- NameNotResolved
- InternetDisconnected
- AddressUnreachable
- BlockedByClient
- BlockedByResponse
Это очень полезно, если вы хотите заблокировать сторонние скрипты с вашей страницы, которые имеют отрицательное влияние на ваш функциональный тест. Вы можете прервать мок, просто вызвав abort
или abortOnce
, например:
const mock = await browser.mock('https://www.google-analytics.com/**')
mock.abort('Failed')
Шпионы
Каждый мок автоматически является шпионом, который подсчитывает количество запросов, которые браузер сделал к этому ресурсу. Если вы не применяете пользовательский ответ или причину прерывания к моку, он продолжает с использованием ответа по умолчанию, который вы обычно получаете. Это позволяет проверить, сколько раз браузер делал запрос, например, к определенной конечной точке API.
const mock = await browser.mock('**/user', { method: 'post' })
console.log(mock.calls.length) // возвращает 0
// регистрация пользователя
await $('#username').setValue('randomUser')
await $('password').setValue('password123')
await $('password_repeat').setValue('password123')
await $('button[type="submit"]').click()
// проверить, был ли сделан API-запрос
expect(mock.calls.length).toBe(1)
// проверить ответ
expect(mock.calls[0].body).toEqual({ success: true })