跳至主要内容

请求模拟和间谍

WebdriverIO 内置支持修改网络响应,允许您专注于测试前端应用程序,而无需设置后端或模拟服务器。您可以在测试中为 Web 资源(如 REST API 请求)定义自定义响应,并动态修改它们。

信息

请注意,使用 mock 命令需要支持 Chrome DevTools 协议。如果您在基于 Chromium 的浏览器中本地运行测试、通过 Selenium Grid v4 或更高版本运行测试,或者通过支持 Chrome DevTools 协议的云供应商(例如 SauceLabs、BrowserStack、LambdaTest)运行测试,则会提供此支持。一旦所需的基元在Webdriver Bidi中落地并在相应的浏览器中实现,将提供完整的跨浏览器支持。

创建模拟

在修改任何响应之前,您需要先定义一个模拟。此模拟由资源 URL 描述,并可以通过请求方法标头进行过滤。该资源通过minimatch支持 glob 表达式。

// mock all resources ending with "/users/list"
const userListMock = await browser.mock('**/users/list')

// or you can specify the mock by filtering resources by headers or
// status code, only mock successful requests to json resources
const strictMock = await browser.mock('**', {
// mock all json responses
headers: { 'Content-Type': 'application/json' },
// that were successful
statusCode: 200
})

指定自定义响应

定义模拟后,您可以为其定义自定义响应。这些自定义响应可以是对象以响应 JSON,本地文件以响应自定义夹具,或 Web 资源以使用来自互联网的资源替换响应。

模拟 API 请求

为了模拟您期望 JSON 响应的 API 请求,您只需在模拟对象上调用 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()))
// outputs: "[ 'Injected (non) completed Todo', 'Injected completed Todo' ]"

您还可以修改响应标头以及状态代码,方法是传入一些模拟响应参数,如下所示:

mock.respond({ ... }, {
// respond with status code 404
statusCode: 404,
// merge response headers with following headers
headers: { 'x-custom-header': 'foobar' }
})

如果您希望模拟完全不调用后端,可以将 fetchResponse 标志设置为 false

mock.respond({ ... }, {
// do not call the actual backend
fetchResponse: false
})

建议将自定义响应存储在夹具文件中,以便您可以在测试中按如下方式引入它们:

// requires Node.js v16.14.0 or higher to support JSON import assertions
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')

// or respond with your custom JS
scriptMock.respond('alert("I am a mocked resource")')

重定向 Web 资源

如果所需的响应已托管在 Web 上,您也可以用另一个 Web 资源替换 Web 资源。这适用于单个页面资源以及网页本身,例如:

const pageMock = await browser.mock('https://google.com/')
await pageMock.respond('https://webdriverio.node.org.cn')
await browser.url('https://google.com')
console.log(await browser.getTitle()) // returns "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) => {
// replace todo content with their list number
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()))
// returns
// [
// '0', '1', '2', '19', '20',
// '21', '3', '4', '5', '6',
// '7', '8', '9', '10', '11',
// '12', '13', '14', '15', '16',
// '17', '18', '22'
// ]

中止模拟

除了返回自定义响应外,您还可以使用以下 HTTP 错误之一中止请求:

  • 失败
  • 中止
  • 超时
  • 访问被拒绝
  • 连接关闭
  • 连接重置
  • 连接被拒绝
  • 连接中止
  • 连接失败
  • 名称未解析
  • 网络断开连接
  • 地址无法访问
  • 客户端阻止
  • 响应阻止

如果您想阻止对页面产生负面影响的第三方脚本,这非常有用。您可以通过调用 abortabortOnce 来中止模拟,例如:

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) // returns 0

// register user
await $('#username').setValue('randomUser')
await $('password').setValue('password123')
await $('password_repeat').setValue('password123')
await $('button[type="submit"]').click()

// check if API request was made
expect(mock.calls.length).toBe(1)

// assert response
expect(mock.calls[0].body).toEqual({ success: true })

欢迎!我怎样才能帮到您?

WebdriverIO AI Copilot