Mocks y Spies de Solicitudes
WebdriverIO viene con soporte integrado para modificar respuestas de red que te permite enfocarte en probar tu aplicación frontend sin tener que configurar tu backend o un servidor de simulación. Puedes definir respuestas personalizadas para recursos web como solicitudes de API REST en tu prueba y modificarlas dinámicamente.
Ten en cuenta que usar el comando mock requiere soporte para el protocolo Chrome DevTools. Ese soporte se proporciona si ejecutas pruebas localmente en un navegador basado en Chromium, a través de Selenium Grid v4 o superior, o a través de un proveedor en la nube con soporte para el protocolo Chrome DevTools (por ejemplo, SauceLabs, BrowserStack, TestMu AI (anteriormente LambdaTest)). El soporte completo para todos los navegadores estará disponible una vez que las primitivas requeridas se incorporen en Webdriver Bidi y se implementen en los respectivos navegadores.
Creando un mock
Antes de poder modificar cualquier respuesta, debes definir un mock primero. Este mock se describe mediante la URL del recurso y puede filtrarse por el método de solicitud o cabeceras. El recurso admite expresiones glob mediante minimatch:
// simular todos los recursos que terminan con "/users/list"
const userListMock = await browser.mock('**/users/list')
// o puedes especificar el mock filtrando recursos por cabeceras o
// código de estado, solo simular solicitudes exitosas a recursos json
const strictMock = await browser.mock('**', {
// simular todas las respuestas json
requestHeaders: { 'Content-Type': 'application/json' },
// que fueron exitosas
statusCode: 200
})
Especificando respuestas personalizadas
Una vez que hayas definido un mock, puedes definir respuestas personalizadas para él. Estas respuestas personalizadas pueden ser un objeto para responder con un JSON, un archivo local para responder con un fixture personalizado o un recurso web para reemplazar la respuesta con un recurso de internet.
Simulando solicitudes de API
Para simular solicitudes de API donde esperas una respuesta JSON, todo lo que necesitas hacer es llamar a respond en el objeto mock con un objeto arbitrario que quieras devolver, por ejemplo:
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()))
// muestra: "[ 'Injected (non) completed Todo', 'Injected completed Todo' ]"
También puedes modificar las cabeceras de respuesta así como el código de estado pasando algunos parámetros de respuesta mock de la siguiente manera:
mock.respond({ ... }, {
// responder con código de estado 404
statusCode: 404,
// combinar cabeceras de respuesta con las siguientes cabeceras
headers: { 'x-custom-header': 'foobar' }
})
Si no quieres que el mock llame al backend en absoluto, puedes pasar false para la bandera fetchResponse.
mock.respond({ ... }, {
// no llamar al backend real
fetchResponse: false
})
Se recomienda almacenar respuestas personalizadas en archivos fixture para que puedas simplemente requerirlos en tu prueba de la siguiente manera:
// requiere Node.js v16.14.0 o superior para soportar aserciones de importación JSON
import responseFixture from './__fixtures__/apiResponse.json' assert { type: 'json' }
mock.respond(responseFixture)
Simulando recursos de texto
Si deseas modificar recursos de texto como JavaScript, archivos CSS u otros recursos basados en texto, puedes simplemente pasar una ruta de archivo y WebdriverIO reemplazará el recurso original con él, por ejemplo:
const scriptMock = await browser.mock('**/script.min.js')
scriptMock.respond('./tests/fixtures/script.js')
// o responder con tu JS personalizado
scriptMock.respond('alert("I am a mocked resource")')
Redireccionar recursos web
También puedes simplemente reemplazar un recurso web con otro recurso web si tu respuesta deseada ya está alojada en la web. Esto funciona tanto con recursos de página individuales como con una página web completa, por ejemplo:
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()) // devuelve "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js"
Respuestas dinámicas
Si tu respuesta mock depende de la respuesta del recurso original, también puedes modificar dinámicamente el recurso pasando una función que recibe la respuesta original como parámetro y establece el mock basado en el valor de retorno, por ejemplo:
const mock = await browser.mock('https://todo-backend-express-knex.herokuapp.com/', {
method: 'get'
})
mock.respond((req) => {
// reemplazar el contenido del todo con su número de lista
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()))
// devuelve
// [
// '0', '1', '2', '19', '20',
// '21', '3', '4', '5', '6',
// '7', '8', '9', '10', '11',
// '12', '13', '14', '15', '16',
// '17', '18', '22'
// ]
Abortando mocks
En lugar de devolver una respuesta personalizada, también puedes simplemente abortar la solicitud con uno de los siguientes errores HTTP:
- Failed
- Aborted
- TimedOut
- AccessDenied
- ConnectionClosed
- ConnectionReset
- ConnectionRefused
- ConnectionAborted
- ConnectionFailed
- NameNotResolved
- InternetDisconnected
- AddressUnreachable
- BlockedByClient
- BlockedByResponse
Esto es muy útil si quieres bloquear scripts de terceros de tu página que tienen una influencia negativa en tu prueba funcional. Puedes abortar un mock simplemente llamando abort o abortOnce, por ejemplo:
const mock = await browser.mock('https://www.google-analytics.com/**')
mock.abort('Failed')
Espías (Spies)
Cada mock es automáticamente un espía que cuenta la cantidad de solicitudes que el navegador realizó a ese recurso. Si no aplicas una respuesta personalizada o una razón de aborto al mock, continúa con la respuesta predeterminada que normalmente recibirías. Esto te permite verificar cuántas veces el navegador hizo la solicitud, por ejemplo, a un determinado endpoint de API.
const mock = await browser.mock('**/user', { method: 'post' })
console.log(mock.calls.length) // devuelve 0
// registrar usuario
await $('#username').setValue('randomUser')
await $('password').setValue('password123')
await $('password_repeat').setValue('password123')
await $('button[type="submit"]').click()
// verificar si se realizó la solicitud de API
expect(mock.calls.length).toBe(1)
// verificar respuesta
expect(mock.calls[0].body).toEqual({ success: true })
Si necesitas esperar hasta que una solicitud coincidente haya respondido, usa mock.waitForResponse(options). Consulta la referencia de API: waitForResponse.