請求模擬與間諜
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 資源。 這適用於個別頁面資源以及網頁本身,例如:
const pageMock = await browser.mock('https://google.com/')
await pageMock.respond('https://webdriverio.dev.org.tw')
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 錯誤之一來中止請求,而不是傳回自訂回應
- 失敗
- 已中止
- 逾時
- 存取遭拒
- 連線已關閉
- 連線已重設
- 連線遭拒
- 連線已中止
- 連線失敗
- 名稱未解析
- 網路已中斷連線
- 位址無法連線
- 用戶端封鎖
- 回應封鎖
如果您想從您的頁面封鎖對您的功能測試有負面影響的第三方指令碼,這非常有用。 您可以透過呼叫 abort
或 abortOnce
來中止模擬,例如:
const mock = await browser.mock('https://127.0.0.1/**')
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 })