Cypress系列(101)- intercept() 命令詳解

如果想從頭學起Cypress,可以看下面的系列文章哦

//www.cnblogs.com/poloyy/category/1768839.html

 

作用

使用該命令在網路層管理 HTTP 請求的行為

 

包含以下功能

  • 對任何類型的 HTTP 請求進行 stub 或 spy
  • 在 HTTP 請求發送到目標伺服器前,可以修改 HTTP 請求 body、headers、URL(類似抓包工具對請求進行打斷點然後修改)
  • 動態或靜態地對 HTTP 請求的響應進行 stub
  • 接收 HTTP 響應後可對 HTTP 響應 body、headers、status、code 進行修改(類似抓包工具對響應進行打斷點然後修改)
  • 在所有階段都可以完全訪問所有 HTTP 請求

 

相較於 cy.route() 的不同

 cy.route() 命令詳解://www.cnblogs.com/poloyy/p/13852941.html

  • 可以攔截所有類型的網路請求,包括 Fetch API,頁面載入,XMLHttpRequest,資源載入等
  • 不需要在使用前調用 cy.server() ,實際上 cy.server() 根本不影響 cy.intercept() 
  • 默認情況下沒有將請求方法設置為 GET

 

語法格式

cy.intercept(url, routeHandler?)
cy.intercept(method, url, routeHandler?)
cy.intercept(routeMatcher, routeHandler?)

 

url

要匹配的請求 URL ,可以是字元串也可以是正則表達式

cy.intercept('//example.com/widgets')
cy.intercept('//example.com/widgets', { fixture: 'widgets.json' })

沒有指定請求方法的話,可以匹配任意類型的請求方法

 

method

請求方法

cy.intercept('POST', '//example.com/widgets', {
  statusCode: 200,
  body: 'it worked!'
})

 

routeMatcher 

  • 它是一個對象
  • 用於匹配此路由將處理哪些傳入的 HTTP 請求
  • 所有對象屬性都是可選的,不是必填的
  • 設置的所有屬性必須與路由匹配才能處理請求
  • 如果將字元串傳遞給任何屬性,則將使用 minimatch 將與請求進行全局匹配

它有以下屬性

{
  /**
   * 與 HTTP Basic身份驗證中使用的用戶名和密碼匹配
   */
  auth?: { username: string | RegExp, password: string | RegExp }

  /**
   * 與請求上的 HTTP Headers 匹配
   */
  headers?: {
    [name: string]: string | RegExp
  }

  /**
   * 與請求上的 hostname 匹配
   */
  hostname?: string | RegExp

  /**
   * If 'true', 只有 https 的請求會被匹配
   * If 'false', 只有 http 的請求會被匹配
   */
  https?: boolean

  /**
   * 與請求上的 method 請求方法匹配
   * 默認 '*', 匹配全部類型的 method
   */
  method?: string | RegExp

  /**
   * 主機名後的路徑, 包括了 ? 後面的查詢參數
   * www.baidu.com/s?wd=2
   */
  path?: string | RegExp

  /**
   * 和 path 一樣, 不過不管 ? 後面的查詢參數
   * www.baidu.com/s
   */
  pathname?: string | RegExp

  /**
   * 與指定的埠匹配, 或者傳遞多個埠組成的數組, 其中一個匹配上就行了
   */
  port?: number | number[]

  /**
   * 與請求路徑 ? 後面跟的查詢參數匹配上
   * wd=2
   */
  query?: {
    [key: string]: string | RegExp
  }

  /**
   * 完整的請求 url
   * //www.baidu.com/s?wd=2
   */
  url?: string | RegExp
}

   

routeHandler 

  • routeHandler 定義了如果請求和 routeMatcher 匹配將對請求進行的指定的處理
  • 可接受的數據類型:string、object、Function、StaticResponse

 

StaticResponse

  • 相當於一個自定義響應體對象
  • 可以自定義 Response headers、HTTP 狀態碼、Response body 等
  • 詳細栗子將在後面展開講解

 

StaticResponse 對象的屬性

{
  /**
   * 將 fixture 文件作為響應主體, 以 cypress/fixtures 為根目錄
   */
  fixture?: string
  /**
   * 將字元串或 JSON 對象作為響應主體
   */
  body?: string | object | object[]
  /**
   * 響應 headers
   * @default {}
   */
  headers?: { [key: string]: string }
  /**
   * 響應狀態碼
   * @default 200
   */
  statusCode?: number
  /**
   * 如果 true, Cypress 將破壞網路連接, 並且不發送任何響應
   * 主要用於模擬無法訪問的伺服器
   * 請勿與其他選項結合使用
   */
  forceNetworkError?: boolean
  /**
   * 發送響應前要延遲的毫秒數
   */
  delayMs?: number
  /**
   * 以多少 kbps 發送響應體
   */
  throttleKbps?: number
}

 

string

  • 如果傳遞一個字元串,這個值相當於響應 body 的值
  • 等價於 StaticResponse 對象 { body: “foo” } 

 

object

  • 如果傳遞了沒有 StaticResponse 密鑰的對象,則它將作為 JSON 響應 Body 發送
  • 例如, {foo:’bar’} 等價於 StaticResponse 對象 {body:{foo:’bar’}} 

 

function

  • 如果傳遞了一個回調函數,當一個請求匹配上了該路由將會自動調用這個函數
  • 函數第一個參數是請求對象
  • 在回調函數內部,可以修改外發請求、發送響應、訪問實際響應
  • 詳細栗子將在後面展開講解

 

命令返回結果

  • 返回 null
  • 可以鏈接 as() 進行別名,但不可鏈接其他命令
  • 可以使用 cy.wait() 等待 cy.intercept() 路由匹配上請求,這將會產生一個對象,包含匹配上的請求/響應相關資訊

 

實際栗子的前置準備

Cypress 官方項目的下載地址://github.com/cypress-io/cypress-example-kitchensink

 

下載好後進入下圖項目文件夾

 

啟動項目

npm start

 

通過 URL 路由匹配請求的栗子

測試程式碼

 

等價於 route() 的測試程式碼

註:  route()  未來將會被棄用

 

運行結果

登錄請求匹配上了路由

 

Console 查看 cy.wait() 返回的對象

最重要的當然是 request 和 response 兩個屬性

 

通過 RouteMatcher 路由匹配請求的栗子

測試程式碼

斷言請求體和響應狀態碼

 

運行結果

 

Console 查看 cy.wait() 返回的對象

 

另一種斷言方式

// 斷言匹配此路由的請求接收到包含【username】的請求 body
cy.wait('@login3').its('request.body').should('have.property', 'username')

// 斷言匹配此路由的請求接收到 HTTP 狀態碼為 500
cy.wait('@login3').its('response.statusCode').should('eq', 200)

// 斷言匹配此路由的請求接收到包含【redirect】的請求 body
cy.wait('@login3').its('response.body').should('have.property', 'redirect')

不過這樣的話只能每次寫一條不能同時三條都寫,所以還是建議像程式碼圖一樣,先 .then() 再進行斷言

 

自定義不同類型的響應體的各種栗子

自定義一個純字元串的響應體

測試程式碼

 

運行結果

 

介面響應

 

自定義一個 JSON 的響應體

測試程式碼

會從cypress安裝目錄/fixtures 下讀取對應的數據文件,它會變成響應 body 的數據

 

test.json 數據文件

 

運行結果

 

介面響應

 

自定義一個 StaticResponse 的響應體

測試程式碼

自定義了響應body、statusCode,還有返迴響應的延時時間

 

運行結果

延時生效了

 

body 和 statusCode 變成自定義的數據了

 

攔截請求的栗子

前置操作

beforeEach(() => {
    cy.visit('//localhost:7079/login')
})

 

斷言請求的栗子

測試程式碼

 

運行結果

 

Console 查看列印結果

可以看到回調函數只有一個參數,就是 request 參數

 

重點

回調函數內不能包含 cy.**() 的命令,如果包含會報錯

簡單來說就是

 cy.type() 命令執行完後會返回一個 promise 對象,同時又會調用回調函數,而回調函數內又調用了 cy.get() 返回了一個 promise 對象,Cypress 會將這種情況當做測試失敗處理

 

將請求傳遞給下一個路由處理程式

前言

意思就是一個請求可以同時匹配上多個路由

 

測試程式碼

 

運行結果

一個登錄請求匹配成功了兩個路由,且回調函數會按匹配的順序執行

 

總結

回調函數的參數就是一個請求對象,它其實可以調用以下方法

{
  /**
   * 銷毀該請求並返回網路錯誤的響應
   */
  destroy(): void

  /**
   * 控制請求的響應
   * 如果傳入的是一個函數, 則它是回調函數, 當響應時會調用
   * 如果傳入的是一個 StaticResponse 對象, 將不會發出請求, 而是直接將這個對象當做響應返回
   */
  reply(interceptor?: StaticResponse | HttpResponseInterceptor): void

  /**
   * 使用 response body(必填) 和 response header(可選) 響應請求
   */
  reply(body: string | object, headers?: { [key: string]: string }): void

  /**
   * 使用 HTTP 狀態碼(必填)、 response body(可選)、response header(可選) 響應請求
   */
  reply(status: number, body?: string | object, headers?: { [key: string]: string }): void

  /**
   * 重定向到新的 location 來響應請求,
   * @param statusCode 用來重定向的 HTTP 狀態程式碼, Default: 302
   */
  redirect(location: string, statusCode?: number): void
}

 

攔截響應的栗子

req.reply() 函數詳解

前言

可以使用 req.reply() 函數來動態控制對請求的響應

 

使用講解

cy.intercept('/login', (req) => {
    // functions on 'req' can be used to dynamically respond to a request here

    // 將請求發送到目標伺服器
    req.reply()

    // 將這個 JSON 對象響應請求
    req.reply({plan: 'starter'})

    // 將請求發送到目標伺服器, 並且攔截伺服器返回的實際響應, 然後進行後續操作(類似抓包工具對響應打斷點)
    req.reply((res) => {
        // res 就是實際的響應對象
    })
})

 

.reply() 直接修改響應的栗子

測試程式碼

 

介面響應內容

 

攔截響應的小栗子

測試程式碼

 

運行結果

 

Console 查看列印結果

一個是 request 對象,一個是 response 對象

 

自定義響應內容

前言

  • 可以使用 resp.send() 函數動態控制傳入的響應
  • 另外,當響應發送到瀏覽器時,對 resp 的任何修改都將保留
  • 如果尚未調用 resp.send() ,則它會在 req.reply() 回調函數完成後隱式調用

 

使用講解

cy.intercept('/notification', (req) => {
    req.reply((resp) => {
        // Success 將作為 response body 返回到瀏覽器
        resp.send('Success')

        // 將 success.json 裡面的數據作為 response body 返回到瀏覽器
        resp.send({fixture: 'success.json'})

        // 將響應延遲 1000ms
        resp.delay(1000)

        // 將響應限制為 64kbps
        resp.throttle(64)
    })
})

 

傳遞字元串作為響應內容

測試程式碼

 

介面響應內容

 

傳遞 JSON 對象作為響應內容

測試程式碼

 

介面響應內容

 

傳遞 StaticResponse 對象作為響應內容

測試程式碼

 

介面響應內容

 

resp 可調用的函數總結

{
/**
* 可以自定義 response statusCode、response body、response header
* 也可以直接傳 StaticResponse 對象
*/
send(status: number, body?: string | number | object, headers?: { [key: string]: string }): void
send(body: string | object, headers?: { [key: string]: string }): void
send(staticResponse: StaticResponse): void
/**
* 繼續返迴響應
*/
send(): void
/**
* 等待 delayMs 毫秒,然後再將響應發送給客戶端
*/
delay: (delayMs: number) => IncomingHttpResponse
/**
* 以多少 kbps 的速度發送響應
*/
throttle: (throttleKbps: number) => IncomingHttpResponse
}