JavaScript >> Javascript 文檔 >  >> JavaScript

使用 Cypress 在 JavaScript 中進行端到端測試

簡介

端到端測試自動化是任何基於 Web 的應用程序開發生命週期的重要組成部分。選擇正確的工具 為你 為您的應用程序可以說是更重要的。

在本指南中,我們將了解使用賽普拉斯的端到端測試 .

為什麼使用賽普拉斯?

使用 Cypress 的最大優點很容易被 Cypress 的開發人員稱為 “時間旅行” .

它允許您在其命令日誌中查看測試中發生的所有事情,從而簡化了調試過程 及其應用預覽 .每個步驟都會顯示應用程序在執行時的狀態,讓您在出現問題時準確定位問題。

我們將他們的大部分認知感知建立在我們的視覺上,“時間旅行” 讓我們能夠直觀地(人工)尋找錯誤,同時仍然為我們帶來自動化的好處。

這也是一種非常自然的錯誤搜索方法,因為這是一個專注於端到端測試的框架,這意味著除了測試功能之外,我們實際上可以看到最終用戶會看到什麼。

您可能想要使用 Cypress 的其他一些原因是:

  • 它不是基於 Selenium 的,因此它不存在相同的問題並提供了一個新的視角。 Cypress 是從頭開始構建的。
  • 高度關注端到端測試 .
  • 如果可以在瀏覽器中運行,可以使用 Cypress 進行測試。
  • 您只需學習 JavaScript。
  • 設置超級簡單,速度極快。
  • 它的創建考慮了測試驅動開發。
  • 大量官方文檔。
  • 您可以查看從瀏覽器發出的每個網絡請求,並可以訪問所有數據。
  • 您可以存根任何網絡請求,同時還可以創建任何網絡請求(這意味著您也可以使用賽普拉斯進行 API 測試)。
  • 積極透明的開發者。

Cypress 建立在 Mocha 之上 和 ,它們都是現代和流行的 BDD 和 TDD 庫,因此實際上借用了一些語法。如果您以前使用過這些,您會注意到 Cypress 掛鉤 直接從Mocha借來的。

為什麼不使用賽普拉斯?

沒有完美的工具,並且通過擴展 - 沒有完美的測試工具。賽普拉斯雖然很棒,但也不例外。

根據您的個人或項目要求,列出的一些優點可能會變成缺點:

  • 由於它不使用 Selenium 並且基於 JavaScript,因此您需要具備 Ja​​vaScript 知識。 Selenium 支持 JavaScript、Java、Python、Ruby 和 C#。
  • 由於它非常專注於端到端測試,因此它不會成為您可以應用於所有其他類型的測試(API 測試除外)的解決方案。
  • 它不(並且可能永遠不會)支持所有瀏覽器(您可以在此處找到支持的瀏覽器列表)這可能是一個問題,因為某些類型的客戶端可能會請求 IE、Opera 或 Safari 支持。
  • 沒有移動測試。
  • 已知在使用直接 URL 導航時會不穩定。
  • 不能使用多個標籤。
  • 無法導航到其他域 URL - 如果您的解決方案中有多個應用程序,或者您需要在第三方 UI 上測試某些內容,這可能是一個巨大的問題。您需要為其他應用程序保留一個單獨的項目,或者完全依賴網絡請求來獲取數據。
  • 相對較新,所以它沒有那麼多社區 材料作為一些較舊的測試工具。
  • 某些路線圖功能似乎已經退居二線,因為您可能在應用程序中通常會執行一些操作,例如文件上傳、懸停和滾動。您必須找到解決方法。
  • 如果您想要直接的數據庫通信或直接瀏覽器工作之外的任何事情,則需要做大量的工作。不過,他們正計劃發布其他語言的後端適配器。本指南將在發布後及時更新。

其中一些將永遠不會改變 而有些計劃改變。如果您想詳細了解哪些功能會保留哪些不會保留,他們的權衡頁面是一個很好的起點。

安裝和設置賽普拉斯

為了使測試賽普拉斯變得容易並允許開發人員測試其所有功能 - 賽普拉斯團隊編譯了各種演示應用程序 如果您還沒有啟動項目並準備好進行測試,您可以使用它。

注意: 對於 Windows 用戶,運行 npm run start:ci:windows 啟動應用程序。

啟動應用程序後,讓我們使用 npm 安裝 Cypress :

$ npm install cypress --save-dev

最後,我們可以啟動庫,使用 npxyarn

$ ./node_modules/.bin/cypress run open # Directly
$ npx cypress open # Using npx
$ yarn run cypress open # Using yarn

如果您使用的是演示應用程序,那麼您已經有了很多示例規範:

點擊其中任何一個(例如 actions.specs.js ) 將啟動跑步者:

Cypress API 和样式

Cypress 建立在 Mocha 和 Chai 之上,並藉用了它們的一些語法和特性。

也就是說,最顯著的借用元素是 describe() , context() , it() specify() 方法。它們本質上是用於註釋測試組的實際測試方法的包裝器 帶標籤。

值得注意的是 specify()it() 是同義詞,就像 describe()context() .根據你覺得更自然的聲音,你可以使用這些的任意組合。

describe() 用於為一組測試提供上下文,而 it() 描述個別測試。通常,您會將它們嵌套在類似於以下的結構中:

describe("Element X Testing", () => {
    it("Does Y", () => {
        // Test...
    });
    it("Does Z", () => {
        // Test...
    });
});

這是純粹 讓我們自己和其他開發人員更容易快速了解正在發生的事情,而無需遍歷用於測試某項內容的整個(可能很長的)方法鏈。

在每個測試中,我們將依賴 Cypress 實例 (cy ) 來運行各種方法,例如 visit() , get() , fixture() 等,以及這些結果的鍊式方法。

visit()get() 通常非常常用的方法也斷言元素和訪問的 URL 存在,如果沒有拋出錯誤,則認為它們是通過的測試。他們也是開始 每個鏈,因此,它們被稱為 parent 方法。

與斷言存在類似,您可以檢查元素是否 contains() 一個值。

exec() 方法在命令行界面上執行命令,request() 方法發送一個 HTTP 請求。

type() 方法將文本內容輸入到可以接受文本內容和 click() 的元素中 單擊選定的元素。

只需這幾個方法,你就可以做很多事情,而一個測試集通常會包含其中的大部分:

describe("Testing CRUD Form", () => {
    it("Visits the addition page", () => {
        cy.visit('/addProduct');
    });
    it("Gets the input field and inputs text", () => {
        cy.get('.input-element')
          .type('Product 1');
    });
    it("Clicks the 'Add Product' button", () => {
        cy.contains('Add Product')
          .click();
    });
    it("Checks if X was added correctly", () => {
        cy.get('product-title')
          .should('have.value', 'Product 1');
    });
    it("Runs a CLI Command", () => {
        cy.exec('npm run other-service');
    });
    it("Sends POST HTTP request", () => {
        cy.request('POST', '/host/other-service/updateCustomers', { mail: 'Product 1 is out!' })
          .its('body');
    });
});

Cypress 中使用的 Mocha 語法非常簡單、直接和直觀。 describe()的用法 和 it() 塊允許我們非常自然地瀏覽測試並註釋它們的作用。

should() 方法依賴於 Chai 斷言,這也相當直觀。

最後,當您準備好運行測試時,您可以運行:

$ cypress run --browser chrome

此命令運行所有已註冊的測試,直到完成。讓我們繼續將 Cypress 添加到實際項目中。

將賽普拉斯添加到項目中

選擇您選擇的代碼編輯器,打開項目根目錄,然後導航到 /cypress/integration/examples/actions.specs.js 查看它運行的所有測試背後的代碼。

這裡已經有很多例子了,讓我們創建自己的 spec.js 稍等片刻,然後探索:

  • 配置(cypress.js ) 文件
  • 如何使用夾具文件
  • 如何使用命令

配置文件,稱為 cypress.js 將在項目根目錄中自動生成,默認情況下僅包含項目 ID 的佔位符:

{	
   "projectId": "yourId"
}

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

讓我們添加 baseUrl 鍵,並為其分配適當的值。由於我們的應用程序在端口 8080 上運行 ,在 localhost 下 讓我們指出它:

{
  "projectId": "4b7344",
  "baseUrl": "http://localhost:8080"
}

現在,讓我們在 /integration 下新建一個目錄 稱為 my_tests 和一個名為 tests.spec.js 的文件 .您會注意到,在賽普拉斯中,您已經提示您選擇運行這個新文件,因為它會響應掃描 /integration 新測試的目錄。

讓我們繼續在我們的 tests.spec.js 中定義幾個測試 文件:

/// <reference types="cypress" />

/* In general, it is a good practice to store 
 all your selectors in variables, since they 
 might change in the future */
var email;
var emailSelector = '.action-email';

describe('Email Input', () => {
    /* beforeEach() which will run before every 
    it() test in the file. This is great 
    if you want to perform some common actions 
    before each test */
    beforeEach(() => {
        /* Since we defined `baseUrl` in cypress.json,
        using `/` as the value in `cy.visit()` will navigate to it.
        Adding `commads/actions` will add the value to the `baseUrl`. */
        cy.visit('/commands/actions');
        /* We are reading the example fixture file and assigning its
        value to a global variable so it is accessible in every test */
        cy.fixture('example').then((json)=>{
            email = json.email;
        });
    });

    it('Clicks on Actions, and writes the email from the fixture', () => {
        cy.get(emailSelector)
            .type(email)
            .should('have.value', email);
    });
});

beforeEach() 方法在每個 it() 之前運行 方法。這意味著我們可以在那裡設置一些常見的任務,以避免在每個 it() 中重複它們 稱呼。在這裡,我們只是導航到 localhost:8080/commands/actions . visit() 方法接受要導航到的字符串 (URL),並將其附加到 baseUrl 在配置文件中定義。

此外,我們還設置了 fixture . Fixtures 只是靜態文檔(自然,JSON 是一種流行的數據存儲格式),我們可以使用它來將數據注入到我們的測試中。它們也常用於存根網絡請求。

在這裡,我們從 example 讀取 JSON 數據 文件,位於 cypress/fixtures/example.json 下 ,並使用提取的值將其分配給我們的 email 變量。

這樣,我們可以在測試中使用示例電子郵件,而不是使用字符串文字。

正如我們已經註意到的,get() 方法使用 action-email 檢索元素 班級。我們已經將這個類保存為 emailSelector 多變的。然後,我們編寫 email 來自 example.json 將文件放入該元素 - 有效地輸入它。

最後,我們通過 Chai 的 斷言該操作是成功的 should() 方法。讓我們繼續運行這個測試:

$ cypress run

結果是:

在 Fixtures 中配置全局變量

如果我們需要訪問 emailSelector 變量比這些測試更規律 - 我們可能希望將其定義為全局變量。這又是一個完美的夾具用例,我們可以通過 cy.fixture() 輕鬆訪問 .

讓我們在 /fixtures 下創建一個新的 JSON 填充 目錄,稱為 selectors.js 它將包含我們應用程序的所有全局級別選擇器:

{
 "emailSelector": ".action-email"
}

注意: 您也可以將其添加到任何現有的夾具文件中,但一般來說,最好為特定數據創建新文件,而不是為所有內容創建通用文件。這使組織更容易,更一致。

創建自定義方法

此外,如果您想執行相同的 beforeEach() 在多個規範文件上 - 您可能也希望通過將其添加到 commands.js 來避免這種冗餘 文件。任何添加到 commands.js 的方法 文件將被註冊為您可以通過 cy 使用的自定義方法 實例。所以如果我們經常訪問 commands/actions URL,還不如創建一個方法,比如,navigateToActionsPage() 這是全球可訪問的。

commands.js 文件位於 /support 下 目錄:

Cypress.Commands.add('navigateToActionsPage', () => {
    cy.visit('/commands/actions');
})

這樣,我們可以添加 N 要運行的命令,只需引用它們而不是一次又一次地編寫它們。讓我們回到tests.spec.js 並重做我們的代碼以刪除 cy.visit() 調用並使用 commands.js 中的方法 文件:

/// <reference types="cypress" />

var email;
var emailSelector;

describe('Email Input', () => {
    beforeEach(() => {
        // Our method in `commands.js`
        // can now be used from anywhere 
        cy.navigateToActionsPage();
        cy.fixture('example').then((json)=>{
            email = json.email;
        });
        // We can now read the selectors fixture 
        // file and load it into our global variable 
        // to store the selector
        cy.fixture('selectors').then((json)=>{
            emailSelector = json.emailSelector;
        });
    });
    it('Clicks on Actions, and writes the email from fixture', () => {
        cy.get(emailSelector).type(email).should('have.value', email);
    });
});

現在看起來可能差別不大,但是如果一個項目的一個頁面有 20 個容易更改的輸入字段,這意味著有一個集中的空間來保存選擇器對於良好的代碼維護是必要的。

為 XHR 請求設置別名

XMLHttpRequest (XHR 請求)可用於從網頁發送和檢索數據,而無需重新加載。它最初是為 XML 數據傳輸而構建的,但更常用於發送和請求 JSON 數據,儘管名稱表明它僅適用於 XML。這種情況並不少見,因為許多 Web 應用程序發送各種請求並在網頁上顯示發送給這些請求的響應。

我們可以給 XHR 請求起別名,以通過賽普拉斯測試它們的功能。首先,讓我們在 commands.js 中添加另一個自定義方法 文件,以便我們可以將其作為全局方法訪問並在我們的 beforeEach() 中使用它 鉤子:

Cypress.Commands.add('navigateToAliasingPage', () => {
    cy.visit('/commands/aliasing');
})

接下來,讓我們創建一個名為 aliasing_tests.spec.js 的新文件 在我們的 /my_tests 目錄。

注意: 或者,您也可以添加另一段代碼(包裝在 describe() ) 在我們現有的文件中。不過,一般來說,最好將一個功能保留在一個文件中,在測試不同功能時創建一個新功能。

我們將使用 cy.intercept()cy.wait() 這裡的方法,用於斷言網絡請求和響應。 cy.intercept() 方法用於監視和存根網絡請求和響應,並替換了 cy.route() 方法。另一方面,cy.wait() 方法用於等待固定時間 直到別名資源解決。

我們將向 /comments 發送 XHR 請求 端點,等待響應並對其進行測試。這正是 intercept() 的正確用例 (存根請求)和 wait() (等到返回的資源被解析)。

讓我們在 aliasing_tests.spec.js 中添加幾個測試 文件:

/// <reference types="cypress" />
context('Aliasing XHR', () => {    
  // Aliasing in beforeEach makes the route aliased in every test in this context    
  beforeEach(() => {        
    // Stub and access any XHR GET request and route to **/comments/*.         
    // The ** and * are wild cards, meaning it could be `/microservice/comments/1`
    // or in our case `https://jsonplaceholder.cypress.io/comments/1`       
    // the `as()` function allows us to later `get()` this route with any valid chainable function
    cy.intercept('GET', '**/comments/*').as('getComment');        
    cy.navigateToAliasingPage();    
  });        
  it('clicks a button and expects a comment', () => {        
    // Clicking this button will create and XHR request that generates a comment        
    cy.get('.network-btn').click()        
    // `wait()` is one of the valid chainable actions where we can use the aliased endpoint
    // `then()` will allow us to access all the elements of the response 
    // for validation whether or not this is available on UI        
    cy.wait('@getComment').then((getCommentResponse) => {            
      // `getCommentResponse` contains all the data from our response. 
      // You can investigate this in the network tab of your browser            
      // Check that the response code is what we expect            
      expect(getCommentResponse.response.statusCode).to.equal(200);            
      // Check that the `response.body` has a parameter named 'email', equal to a certain value
      expect(getCommentResponse.response.body.email).to.equal('[email protected]');            
      // Perform same check but for the `name` parameter            
      expect(getCommentResponse.response.body.name).to.equal('id labore ex et quam laborum');        
    });    
  });
});

讓我們繼續運行這個測試:

$ cypress run --record --spec "cypress/integration/my_tests/aliasing_tests.spec.js"

結果是:

模擬 XHR 請求響應

另一個需要注意的非常有用的功能是,您可以完全跳過上一節創建評論的過程。您可以通過使用 cy.intercept() 存根此網絡請求來創建自己的模擬響應 .這在後端尚未開發時很有用,因此您可以在響應完成之前模擬響應,或者您只想測試應用程序的這一部分。

我們這裡還有夾具文件的另一個用途。讓我們創建一個名為 mock_comment.json 這將包含評論的模擬數據,並添加以下 JSON 內容:

{  
  "postId": 1,  
  "id": 1,  
  "name": "My Name",  
  "email": "[email protected]",  
  "body": "My Comment Body"
}

現在,讓我們創建另一個文件,名為 intercepting_requests.spec.js /my_tests 下 目錄。在這裡,我們將攔截相同的端點,但將我們的夾具作為響應注入,完全跳過 actual 請求:

/// <reference types="cypress" />
describe('Intercepting XHR', () => {
  beforeEach(() => {       
    // By adding an object `{fixture: 'mock_comment.json'}` in the `intercept()` call,
    // we are telling cypress to use the JSON file as the response.      
    // It can also be aliased using `.as()`.  
    cy.intercept('GET', '**/comments/*',
                 {fixture: 'mock_comment.json'}).as('getComment');       
    cy.navigateToAliasingPage();    
  });        
  it('Clicks a button and expects a comment', () => {        
    cy.get('.network-btn').click()        
    // There is no need to validate the response now since we mocked it,
    // but there is a need to validate the UI        
    cy.fixture('mock_comment').then((json)=>{           
      // We are accessing the comment directly from `mock_comment.json`
      // and checking that the UI is displaying it in its fullest         
      cy.get('.network-comment').should('have.text', json.body);        
    });    
  });
});

讓我們運行這個測試:

$ cypress run --record --spec "cypress/integration/my_tests/intercepting_requests.spec.js"

結果是:

結論

Cypress 是一個偉大的新興測試工具。它超級輕量級且易於設置,並且構建在 Mocha 和 Chai 之上的 TDD 令人驚嘆。它允許您完全模擬您的後端,這也非常適合在與後端集成之前進行測試,或者在您的後端尚不存在的情況下進行測試。在某些情況下,它還可以幫助塑造後端,因為它將準確地勾勒出前端的期望。

但是,如果您正在尋找一種在其涵蓋範圍內非常靈活的工具,並且您需要一個大型、個性化和定制的框架,那麼您可能希望堅持使用 Selenium。


Tutorial JavaScript 教程
  1. 邏輯與問題解決

  2. CSS動畫

  3. 使用 Google Maps API 的轉彎路線

  4. 反應 18 更快?

  5. Bootstrap 5:滾動 300 像素後隱藏/顯示導航欄

  6. 如何使用 TypeORM 播種數據庫

  7. 創建一個 JavaScript 庫。添加鍵盤控件並改進輔助功能支持

  1. 戰鬥引擎開發日誌 #2 - 冒險時間

  2. 編寫現代 JavaScript 代碼

  3. Backbone.js 入門

  4. 如何使用 VueJS 創建簡單的標籤系統

  5. 問我一些關於函數式編程的愚蠢問題

  6. 嵌入式 Web 遊戲控制台上的小故障 Scratch 3.0

  7. 當 tbody 不存在時附加到表以及如何使所有現有的 jquery 為該行工作

  1. 回歸基礎:Javascript 中的原始類型和對像類型

  2. 使用 PHP 的簡單動態表單驗證函數

  3. 如何使用 Supbase 中的函數運行自定義 SQL 查詢

  4. 通過 Binding.Pry 對 API 進行故障排除