量角器 - 快速指南



量角器 - 簡介

本章將為您介紹量角器,您將瞭解此測試框架的起源以及為什麼要選擇它,以及此工具的工作原理和侷限性。

什麼是量角器?

量角器是一個用於 Angular 和 AngularJS 應用程式的開源端到端測試框架。它由 Google 基於 WebDriver 構建。它也作為現有的 AngularJS E2E 測試框架“Angular Scenario Runner”的替代品。

它還可以作為解決方案整合器,結合 NodeJS、Selenium、Jasmine、WebDriver、Cucumber、Mocha 等強大的技術。除了測試 AngularJS 應用程式外,它還可以為普通 Web 應用程式編寫自動化的迴歸測試。它允許我們像真實使用者一樣測試我們的應用程式,因為它使用實際瀏覽器執行測試。

下圖將簡要概述量角器 -

Overview of Protractor

觀察上圖,我們有 -

  • 量角器 - 如前所述,它是 WebDriver JS 的一個包裝器,專門為 Angular 應用程式設計。

  • Jasmine - 它基本上是一個用於測試 JavaScript 程式碼的行為驅動開發框架。我們可以使用 Jasmine 輕鬆編寫測試。

  • WebDriver JS - 它是 Selenium 2.0/WebDriver 的 Node JS 繫結實現。

  • Selenium - 它只是自動化瀏覽器。

起源

如前所述,量角器是現有的 AngularJS E2E 測試框架“Angular Scenario Runner”的替代品。基本上,量角器的起源始於 Scenario Runner 的結束。這裡出現了一個問題,為什麼我們需要構建量角器?要了解這一點,我們首先需要檢查其前身 - Scenario Runner。

量角器的起源

Julie Ralph 是量角器開發的主要貢獻者,她在 Google 內部其他專案中使用 Angular Scenario Runner 的過程中獲得了以下經驗。這進一步成為構建量角器的動力,特別是為了填補空白 -

“我們嘗試使用 Scenario Runner,發現它真的無法做到我們需要測試的事情。我們需要測試諸如登入之類的事情。您的登入頁面不是 Angular 頁面,Scenario Runner 無法處理它。它也無法處理諸如彈出視窗和多個視窗、瀏覽瀏覽器歷史記錄等內容。”

量角器最大的優勢在於 Selenium 專案的成熟度,它封裝了 Selenium 的方法,以便於 Angular 專案使用。量角器的設計方式使其能夠測試應用程式的所有層,例如 Web UI、後端服務、持久層等等。

為什麼選擇量角器?

眾所周知,幾乎所有應用程式都使用 JavaScript 進行開發。由於應用程式數量的增加,當 JavaScript 的規模增加並變得複雜時,測試人員的任務變得困難。大多數情況下,使用 JUnit 或 Selenium WebDriver 在 AngularJS 應用程式中捕獲 Web 元素變得非常困難,使用擴充套件的 HTML 語法來表達 Web 應用程式元件。

這裡的問題是為什麼 Selenium Web Driver 無法找到 AngularJS Web 元素?原因是 AngularJS 應用程式具有一些擴充套件的 HTML 屬性,例如 ng-repeater、ng-controller 和 ng-model 等,這些屬性未包含在 Selenium 定位器中。

在這裡,量角器的重要性就體現出來了,因為量角器在 Selenium 之上可以處理和控制 AngularJS Web 應用程式中的這些擴充套件 HTML 元素。這就是為什麼我們可以說大多數框架專注於對 AngularJS 應用程式進行單元測試,而量角器用於測試應用程式的實際功能。

量角器的工作原理

量角器這個測試框架與 Selenium 協同工作,為模擬使用者與在瀏覽器或移動裝置上執行的 AngularJS 應用程式互動提供自動化測試基礎設施。

可以透過以下步驟瞭解量角器的工作原理 -

  • 步驟 1 - 在第一步中,我們需要編寫測試。這可以透過 Jasmine 或 Mocha 或 Cucumber 的幫助來完成。

  • 步驟 2 - 現在,我們需要執行測試,這可以透過量角器的幫助來完成。它也稱為測試執行器。

  • 步驟 3 - 在此步驟中,Selenium 伺服器將幫助管理瀏覽器。

  • 步驟 4 - 最後,使用 Selenium WebDriver 呼叫瀏覽器 API。

Working of Protractor

優點

這個開源端到端測試框架提供了以下優勢 -

  • 作為開源工具,量角器非常易於安裝和設定。

  • 與 Jasmine 框架配合良好以建立測試。

  • 支援測試驅動開發 (TDD)。

  • 包含自動等待,這意味著我們不需要在測試中顯式新增等待和休眠。

  • 提供 Selenium WebDriver 的所有優勢。

  • 支援透過多個瀏覽器進行並行測試。

  • 提供自動同步的優勢。

  • 測試速度快。

侷限性

這個開源端到端測試框架具有以下侷限性 -

  • 由於它是 WebDriver JS 的包裝器,因此不會發現瀏覽器自動化中的任何垂直領域。

  • 使用者必須瞭解 JavaScript,因為它僅適用於 JavaScript。

  • 僅提供前端測試,因為它是一個 UI 驅動的測試工具。

量角器 - JavaScript 測試的概念

由於瞭解 JavaScript 對於使用量角器至關重要,因此在本章中,讓我們詳細瞭解 JavaScript 測試的概念。

JavaScript 測試和自動化

JavaScript 是最流行的動態型別解釋型指令碼語言,但最具挑戰性的任務是測試程式碼。這是因為,與 JAVA 和 C++ 等其他編譯語言不同,JavaScript 中沒有編譯步驟可以幫助測試人員找出錯誤。此外,基於瀏覽器的測試非常耗時;因此,需要支援 JavaScript 自動化測試的工具。

自動化測試的概念

編寫測試始終是一個好習慣,因為它使程式碼更好;手動測試的問題在於它有點耗時且容易出錯。對於程式設計師來說,手動測試的過程也相當乏味,因為他們需要重複該過程,編寫測試規範、更改程式碼並重新整理瀏覽器多次。此外,手動測試還會減慢開發過程。

由於上述原因,擁有可以自動化這些測試並幫助程式設計師擺脫這些重複和乏味步驟的工具始終是有用的。開發人員應該如何使測試過程自動化?

基本上,開發人員可以在 CLI(命令列直譯器)或開發 IDE(整合開發環境)中實現工具集。然後,即使沒有開發人員的輸入,這些測試也會在單獨的過程中持續執行。JavaScript 的自動化測試也不是什麼新鮮事物,並且已經開發了許多工具,例如 Karma、Protractor、CasperJS 等。

JavaScript 測試型別

可以針對不同目的進行不同的測試。例如,一些測試是為了檢查程式中函式的行為,而另一些測試是為了測試模組或功能的流程。因此,我們有以下兩種型別的測試 -

單元測試

測試是在程式的最小可測試部分(稱為單元)上進行的。單元基本上是在隔離狀態下進行測試的,而無需該單元依賴於其他部分。在 JavaScript 的情況下,具有特定行為的單個方法或函式可以是程式碼單元,並且必須以隔離的方式測試這些程式碼單元。

單元測試的優點之一是可以按任何順序測試單元,因為單元彼此獨立。單元測試的另一個真正重要的優點是,它可以隨時執行測試,如下所示 -

  • 從開發過程的開始。
  • 完成任何模組/功能的開發後。
  • 修改任何模組/功能後。
  • 在現有應用程式中新增任何新功能後。

對於 JavaScript 應用程式的自動化單元測試,我們可以從許多測試工具和框架中進行選擇,例如 Mocha、Jasmine 和 QUnit。

端到端測試

它可以定義為用於測試應用程式從開始到結束(從一端到另一端)的流程是否根據設計正常工作的測試方法。

端到端測試也稱為功能/流程測試。與單元測試不同,端到端測試測試各個元件如何作為應用程式協同工作。這是單元測試和端到端測試之間的主要區別。

例如,假設我們有一個註冊模組,使用者需要提供一些有效資訊才能完成註冊,那麼該特定模組的 E2E 測試將遵循以下步驟來完成測試 -

  • 首先,它將載入/編譯表單或模組。
  • 現在,它將獲取表單元素的 DOM(文件物件模型)。
  • 接下來,觸發提交按鈕的點選事件以檢查它是否正在工作。
  • 現在,出於驗證目的,從輸入欄位中收集值。
  • 接下來,應驗證輸入欄位。
  • 出於測試目的,呼叫偽 API 來儲存資料。

每個步驟都會給出自己的結果,這些結果將與預期結果集進行比較。

現在,出現的問題是,雖然這種端到端或功能測試也可以手動執行,但為什麼我們需要自動化測試呢?主要原因是自動化將使這個測試過程變得更容易。為此,一些可輕鬆與任何應用程式整合的可用工具包括Selenium、PhantomJS和Protractor。

測試工具與框架

我們有各種用於Angular測試的測試工具和框架。以下是其中一些眾所周知的工具和框架:

Karma

Karma 由 Vojta Jina 建立,是一個測試執行器。最初這個專案被稱為 Testacular。它不是一個測試框架,這意味著它使我們能夠輕鬆地在真實瀏覽器上自動執行 JavaScript 單元測試。Karma 是為 AngularJS 構建的,因為在 Karma 出現之前,還沒有針對基於 Web 的 JavaScript 開發人員的自動化測試工具。另一方面,透過 Karma 提供的自動化,開發人員可以執行一個簡單的單一命令,並確定整個測試套件是否透過或失敗。

使用 Karma 的優點

以下是一些與手動流程相比使用 Karma 的優點:

  • 自動在多個瀏覽器以及裝置上進行測試。
  • 監控檔案錯誤並修復它們。
  • 提供線上支援和文件。
  • 簡化與持續整合伺服器的整合。

使用 Karma 的缺點

以下是使用 Karma 的一些缺點:

使用 Karma 的主要缺點是它需要一個額外的工具來配置和維護。

如果你正在將 Karma 測試執行器與 Jasmine 一起使用,那麼在處理一個元素具有多個 ID 的情況下,關於如何設定 CSS 的資訊可用文件較少。

Jasmine

Jasmine 是一個用於測試 JavaScript 程式碼的行為驅動開發框架,由 Pivotal Labs 開發。在 Jasmine 框架積極開發之前,Pivotal Labs 還開發了一個類似的單元測試框架 JsUnit,它具有內建的測試執行器。可以透過包含 SpecRunner.html 檔案或將其用作命令列測試執行器來透過 Jasmine 測試執行瀏覽器測試。它也可以與或不與 Karma 一起使用。

使用 Jasmine 的優點

以下是使用 Jasmine 的一些優點:

  • 一個獨立於瀏覽器、平臺和語言的框架。

  • 支援測試驅動開發 (TDD) 以及行為驅動開發。

  • 與 Karma 預設整合。

  • 易於理解的語法。

  • 提供測試間諜、模擬和直通功能,作為輔助測試的附加功能。

使用 Jasmine 的缺點

以下是使用 Jasmine 的一個缺點:

  • 測試必須由使用者在更改時重新執行,因為 Jasmine 在執行測試時沒有檔案監視功能。

Mocha

Mocha 是為 Node.js 應用程式編寫的,它是一個測試框架,但也支援瀏覽器測試。它與 Jasmine 非常相似,但它們之間的主要區別在於 Mocha 需要一些外掛和庫,因為它不能作為測試框架獨立執行。另一方面,Jasmine 是獨立的。但是,Mocha 比 Jasmine 更靈活。

使用 Mocha 的優點

以下是使用 Mocha 的一些優點:

  • Mocha 非常易於安裝和配置。
  • 使用者友好且簡單的文件。
  • 包含多個節點專案的外掛。

使用 Mocha 的缺點

以下是使用 Mocha 的一些缺點:

  • 它需要單獨的模組進行斷言、間諜等操作。
  • 它還需要額外的配置才能與 Karma 一起使用。

QUnit

QUnit 最初由 John Resig 於 2008 年作為 jQuery 的一部分開發,是一個強大且易於使用的 JavaScript 單元測試套件。它可用於測試任何通用的 JavaScript 程式碼。雖然它專注於在瀏覽器中測試 JavaScript,但它對開發人員來說非常方便使用。

使用 QUnit 的優點

以下是使用 QUnit 的一些優點:

  • 易於安裝和配置。
  • 使用者友好且簡單的文件。

使用 QUnit 的缺點

以下是使用 QUnit 的一個缺點:

  • 它主要為 jQuery 開發,因此不太適合與其他框架一起使用。

Selenium

Selenium 最初由 Jason Huggins 於 2004 年作為 ThoughtWorks 的內部工具開發,是一個開源測試自動化工具。Selenium 將其自身定義為“Selenium 自動化瀏覽器。僅此而已!”。瀏覽器的自動化意味著開發人員可以非常輕鬆地與瀏覽器互動。

使用 Selenium 的優點

以下是使用 Selenium 的一些優點:

  • 包含大量功能集。
  • 支援分散式測試。
  • 透過 Sauce Labs 等服務提供 SaaS 支援。
  • 易於使用,文件簡單,資源豐富。

使用 Selenium 的缺點

以下是使用 Selenium 的一些缺點:

  • 使用 Selenium 的主要缺點是它必須作為單獨的程序執行。
  • 配置有點繁瑣,因為開發人員需要遵循幾個步驟。

量角器 - 入門

在前面的章節中,我們學習了 Protractor 的基礎知識。在本章中,讓我們學習如何安裝和配置它。

先決條件

在計算機上安裝 Protractor 之前,我們需要滿足以下先決條件:

Node.js

Protractor 是一個 Node.js 模組,因此非常重要的先決條件是我們的計算機上必須安裝 Node.js。我們將使用 npm(一個 JavaScript 包管理器)安裝 Protractor 包,它與 Node.js 一起提供。

要安裝 Node.js,請訪問官方連結:https://nodejs.com.tw/en/download/。安裝 Node.js 後,您可以透過在命令提示符中寫入命令 node --versionnpm --version 來檢查 Node.js 和 npm 的版本,如下所示:

Prerequisites

Chrome

Google Chrome 是 Google 構建的 Web 瀏覽器,它將用於在 Protractor 中執行端到端測試,而無需 Selenium 伺服器。您可以透過點選連結下載 Chrome:https://www.google.com/chrome/

Chrome 的 Selenium WebDriver

此工具隨 Protractor npm 模組提供,並允許我們與 Web 應用程式互動。

安裝 Protractor

在計算機上安裝 Node.js 後,我們可以使用以下命令安裝 Protractor:

npm install -g protractor

Protractor 成功安裝後,我們可以透過在命令提示符中寫入 protractor --version 命令來檢查其版本,如下所示:

Installing Protractor

安裝 Chrome 的 WebDriver

安裝 Protractor 後,我們需要安裝 Chrome 的 Selenium WebDriver。它可以使用以下命令安裝:

webdriver-manager update

上述命令將建立一個 Selenium 目錄,其中包含專案中使用的必需的 Chrome 驅動程式。

確認安裝與配置

我們可以透過稍微更改安裝 Protractor 後示例中提供的 conf.js 檔案來確認 Protractor 的安裝和配置。您可以在根目錄 node_modules/Protractor/example 中找到此 conf.js 檔案。

為此,首先在同一目錄(即 node_modules/Protractor/example)中建立一個名為 testingconfig.js 的新檔案。

現在,在 conf.js 檔案中,在原始檔宣告引數下,寫入 testingconfig.js。

接下來,儲存並關閉所有檔案,然後開啟命令提示符。執行 conf.js 檔案,如以下螢幕截圖所示。

如果得到以下輸出,則表示 Protractor 的配置和安裝成功:

Configuration Protractor

以上輸出表明沒有規範,因為我們在 conf.js 檔案的原始檔宣告引數中提供了空檔案。但從以上輸出可以看出,Protractor 和 WebDriver 都成功運行了。

安裝與配置中的問題

在安裝和配置 Protractor 和 WebDriver 期間,我們可能會遇到以下常見問題:

Selenium 未正確安裝

這是安裝 WebDriver 時最常見的問題。如果未更新 WebDriver,則會出現此問題。請注意,我們必須更新 WebDriver,否則我們將無法將其引用到 Protractor 安裝。

找不到測試

另一個常見問題是,執行 Protractor 後,它顯示無法找到測試。為此,我們必須確保相對路徑、檔名或副檔名正確。我們還需要非常仔細地編寫 conf.js 檔案,因為它從配置檔案本身開始。

Protractor - Protractor 和 Selenium 伺服器

如前所述,Protractor 是一個用於 Angular 和 AngularJS 應用程式的開源端到端測試框架。它是一個 Node.js 程式。另一方面,Selenium 是一個瀏覽器自動化框架,包括 Selenium 伺服器、WebDriver API 和 WebDriver 瀏覽器驅動程式。

Protractor 與 Selenium

如果我們談論 Protractor 和 Selenium 的結合,Protractor 可以與 Selenium 伺服器一起工作以提供自動化的測試基礎設施。該基礎設施可以模擬使用者與在瀏覽器或移動裝置上執行的 Angular 應用程式的互動。Protractor 和 Selenium 的結合可以分為三個部分,即測試、伺服器和瀏覽器,如下圖所示:

Protractor with Selenium

Selenium WebDriver 程序

正如我們在上圖中看到的,使用 Selenium WebDriver 進行的測試涉及以下三個過程:

  • 測試指令碼
  • 伺服器
  • 瀏覽器

在本節中,讓我們討論這三個過程之間的通訊。

測試指令碼與伺服器之間的通訊

前兩個過程(測試指令碼和伺服器)之間的通訊取決於 Selenium 伺服器的工作方式。換句話說,Selenium 伺服器的執行方式將決定測試指令碼與伺服器之間通訊過程的形式。

Selenium 伺服器可以在我們的機器上本地執行,作為獨立的 Selenium 伺服器(selenium-server-standalone.jar),或者可以透過服務(Sauce Labs)遠端執行。在獨立的 Selenium 伺服器的情況下,Node.js 和 Selenium 伺服器之間將存在 HTTP 通訊。

伺服器和瀏覽器之間的通訊

眾所周知,伺服器負責在解釋測試指令碼中的命令後將其轉發到瀏覽器。這就是為什麼伺服器和瀏覽器也需要一個通訊媒介,並且此處通訊是藉助於**JSON WebDriver Wire 協議**完成的。瀏覽器擴充套件了用於解釋命令的瀏覽器驅動程式。

可以透過以下圖表瞭解上述關於 Selenium WebDriver 程序及其通訊的概念:

Web Driver processes

在使用 Protractor 時,第一個程序,即測試指令碼使用 Node.js 執行,但在對瀏覽器執行任何操作之前,它會發送一個額外的命令以確保正在測試的應用程式穩定。

設定 Selenium 伺服器

Selenium 伺服器充當測試指令碼和瀏覽器驅動程式之間的代理伺服器。它基本上將命令從我們的測試指令碼轉發到 WebDriver,並將 WebDriver 的響應返回到我們的測試指令碼。設定 Selenium 伺服器有以下幾種選項,這些選項包含在測試指令碼的**conf.js**檔案中:

獨立 Selenium 伺服器

如果我們想在本地機器上執行伺服器,我們需要安裝獨立的 Selenium 伺服器。安裝獨立 Selenium 伺服器的先決條件是 JDK(Java 開發工具包)。我們必須在本地機器上安裝 JDK。可以透過從命令列執行以下命令來檢查它:

java -version

現在,我們可以選擇手動或從測試指令碼安裝和啟動 Selenium 伺服器。

手動安裝和啟動 Selenium 伺服器

要手動安裝和啟動 Selenium 伺服器,我們需要使用 Protractor 附帶的 WebDriver-Manager 命令列工具。安裝和啟動 Selenium 伺服器的步驟如下:

**步驟 1** - 第一步是安裝 Selenium 伺服器和 ChromeDriver。可以透過執行以下命令來完成:

webdriver-manager update

**步驟 2** - 接下來,我們需要啟動伺服器。可以透過執行以下命令來完成:

webdriver-manager start

**步驟 3** - 最後,我們需要在配置檔案中將 seleniumAddress 設定為正在執行的伺服器的地址。預設地址為**https://:4444/wd/hub**。

從測試指令碼啟動 Selenium 伺服器

要從測試指令碼啟動 Selenium 伺服器,我們需要在配置檔案中設定以下選項:

  • **jar 檔案位置** - 我們需要在配置檔案中設定獨立 Selenium 伺服器的 jar 檔案位置,方法是設定 seleniumServerJar。

  • **指定埠** - 我們還需要指定用於啟動獨立 Selenium 伺服器的埠。它可以在配置檔案中透過設定 seleniumPort 來指定。預設埠為 4444。

  • **命令列選項陣列** - 我們還需要設定要傳遞給伺服器的命令列選項陣列。它可以在配置檔案中透過設定 seleniumArgs 來指定。如果需要完整的命令陣列列表,則使用**-help**標誌啟動伺服器。

使用遠端 Selenium 伺服器

執行測試的另一種選擇是遠端使用 Selenium 伺服器。遠端使用伺服器的先決條件是我們必須擁有託管伺服器服務的帳戶。在使用 Protractor 時,我們對以下託管伺服器的服務有內建支援:

TestObject

要使用 TestObject 作為遠端 Selenium 伺服器,我們需要設定 testobjectUser(我們的 TestObject 帳戶的使用者名稱)和 testobjectKey(我們的 TestObject 帳戶的 API 金鑰)。

BrowserStack

要使用 BrowserStack 作為遠端 Selenium 伺服器,我們需要設定 browserstackUser(我們的 BrowserStack 帳戶的使用者名稱)和 browserstackKey(我們的 BrowserStack 帳戶的 API 金鑰)。

Sauce Labs

要使用 Sauce Labs 作為遠端 Selenium 伺服器,我們需要設定 sauceUser(我們的 Sauce Labs 帳戶的使用者名稱)和 SauceKey(我們的 Sauce Labs 帳戶的 API 金鑰)。

Kobiton

要使用 Kobiton 作為遠端 Selenium 伺服器,我們需要設定 kobitonUser(我們的 Kobiton 帳戶的使用者名稱)和 kobitonKey(我們的 Kobiton 帳戶的 API 金鑰)。

直接連線到瀏覽器驅動程式而不使用 Selenium 伺服器

執行測試的另一種選擇是直接連線到瀏覽器驅動程式而不使用 Selenium 伺服器。Protractor 可以直接測試(無需使用 Selenium 伺服器)Chrome 和 Firefox,方法是在配置檔案中設定 directConnect: true。

設定瀏覽器

在配置和設定瀏覽器之前,我們需要知道 Protractor 支援哪些瀏覽器。以下是 Protractor 支援的瀏覽器列表:

  • ChromeDriver
  • FirefoxDriver
  • SafariDriver
  • IEDriver
  • Appium-iOS/Safari
  • Appium-Android/Chrome
  • Selendroid
  • PhantomJS

要設定和配置瀏覽器,我們需要轉到 Protractor 的配置檔案,因為瀏覽器設定是在配置檔案的 capabilities 物件中完成的。

設定 Chrome

要設定 Chrome 瀏覽器,我們需要如下設定 capabilities 物件:

capabilities: {
   'browserName': 'chrome'
}

我們還可以新增 Chrome 特定的選項,這些選項巢狀在 chromeOptions 中,其完整列表可以在https://sites.google.com/a/chromium.org/chromedriver/capabilities中檢視。

例如,如果要在右上角新增 FPS 計數器,則可以在配置檔案中如下操作:

capabilities: {
   'browserName': 'chrome',
   'chromeOptions': {
      'args': ['show-fps-counter=true']
   }
},

設定 Firefox

要設定 Firefox 瀏覽器,我們需要如下設定 capabilities 物件:

capabilities: {
   'browserName': 'firefox'
}

我們還可以新增 Firefox 特定的選項,這些選項巢狀在 moz:firefoxOptions 物件中,其完整列表可以在https://github.com/mozilla/geckodriver#firefox-capabilities中檢視。

例如,如果要在安全模式下在 Firefox 上執行測試,則可以在配置檔案中如下操作:

capabilities: {
   'browserName': 'firefox',
   'moz:firefoxOptions': {
     'args': ['—safe-mode']
   }
},

設定其他瀏覽器

要設定除 Chrome 或 Firefox 之外的任何其他瀏覽器,我們需要從https://docs.seleniumhq.org/download/安裝單獨的二進位制檔案。

設定 PhantonJS

實際上,由於 PhantomJS 存在崩潰問題,因此不再受支援。建議改用無頭 Chrome 或無頭 Firefox。它們可以如下設定:

要設定無頭 Chrome,我們需要使用 –headless 標誌啟動 Chrome,如下所示:

capabilities: {
   'browserName': 'chrome',
   'chromeOptions': {
      'args': [“--headless”, “--disable-gpu”, “--window-size=800,600”]
   }
},

要設定無頭 Firefox,我們需要使用**–headless**標誌啟動 Firefox,如下所示:

capabilities: {
   'browserName': 'firefox',
   'moz:firefoxOptions': {
      'args': [“--headless”]
   }
},

設定多個瀏覽器進行測試

我們還可以針對多個瀏覽器進行測試。為此,我們需要使用 multiCapabilities 配置選項,如下所示:

multiCapabilities: [{
   'browserName': 'chrome'
},{
   'browserName': 'firefox'
}]

哪個框架?

Protractor 支援兩個 BDD(行為驅動開發)測試框架,即 Jasmine 和 Mocha。這兩個框架都基於 JavaScript 和 Node.js。這些框架提供了編寫和管理測試所需的語法、報告和腳手架。

接下來,我們將瞭解如何安裝各種框架:

Jasmine 框架

它是 Protractor 的預設測試框架。安裝 Protractor 時,會隨附 Jasmine 2.x 版本。我們無需單獨安裝它。

Mocha 框架

Mocha 是另一個 JavaScript 測試框架,基本上執行在 Node.js 上。要使用 Mocha 作為我們的測試框架,我們需要使用 BDD(行為驅動開發)介面和 Chai 斷言以及 Chai As Promised。安裝可以透過以下命令完成:

npm install -g mocha
npm install chai
npm install chai-as-promised

如您所見,在安裝 mocha 時使用了 -g 選項,這是因為我們使用 -g 選項全域性安裝了 Protractor。安裝後,我們需要在測試檔案中引入並設定 Chai。可以如下操作:

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;

此後,我們可以像這樣使用 Chai As Promised:

expect(myElement.getText()).to.eventually.equal('some text');

現在,我們需要將配置檔案的 framework 屬性設定為 mocha,方法是在配置檔案中新增 framework: ‘mocha’。mocha 的“reporter”和“slow”等選項可以如下新增到配置檔案中:

mochaOpts: {
   reporter: "spec", slow: 3000
}

Cucumber 框架

要使用 Cucumber 作為我們的測試框架,我們需要使用 framework 選項**custom**將其與 Protractor 整合。安裝可以透過以下命令完成

npm install -g cucumber
npm install --save-dev protractor-cucumber-framework

如您所見,在安裝 Cucumber 時使用了 -g 選項,這是因為我們全域性安裝了 Protractor,即使用 -g 選項。接下來,我們需要將配置檔案的 framework 屬性設定為**custom**,方法是將 framework: ‘custom’ 和 frameworkPath: ‘Protractor-cucumber-framework’ 新增到名為 cucumberConf.js 的配置檔案中。

下面顯示的示例程式碼是一個基本的 cucumberConf.js 檔案,可用於使用 Protractor 執行 cucumber 特性檔案:

exports.config = {
   seleniumAddress: 'https://:4444/wd/hub',

   baseUrl: 'https://angularjs.org/',

   capabilities: {
      browserName:'Firefox'
   },

   framework: 'custom',

   frameworkPath: require.resolve('protractor-cucumber-framework'),

   specs: [
      './cucumber/*.feature'
   ],

   // cucumber command line options
   cucumberOpts: {
      require: ['./cucumber/*.js'],
      tags: [],
      strict: true,
      format: ["pretty"],
      'dry-run': false,
      compiler: []
   },
   onPrepare: function () {
      browser.manage().window().maximize();
   }
};

量角器 - 編寫第一個測試

在本章中,讓我們瞭解如何在 Protractor 中編寫第一個測試。

Protractor 需要哪些檔案

Protractor 需要以下兩個檔案才能執行:

規範或測試檔案

這是執行 Protractor 的重要檔案之一。在這個檔案中,我們將編寫我們的實際測試程式碼。測試程式碼是使用測試框架的語法編寫的。

例如,如果我們使用**Jasmine**框架,則測試程式碼將使用**Jasmine**的語法編寫。此檔案將包含測試的所有功能流程和斷言。

簡單來說,我們可以說此檔案包含與應用程式互動的邏輯和定位器。

示例

以下是簡單的指令碼 TestSpecification.js,其中包含導航到 URL 並檢查頁面標題的測試用例:

//TestSpecification.js
describe('Protractor Demo', function() {
   it('to check the page title', function() {
      browser.ignoreSynchronization = true;
      browser.get('https://tutorialspoint.tw/tutorialslibrary.htm');
      browser.driver.getTitle().then(function(pageTitle) {
         expect(pageTitle).toEqual('Free Online Tutorials and Courses');
      });
   });
});

程式碼說明

上述規範檔案程式碼的解釋如下:

瀏覽器

它是 Protractor 建立的全域性變數,用於處理所有瀏覽器級別的命令。它基本上是 WebDriver 例項的一個包裝器。browser.get() 是一個簡單的 Selenium 方法,它會告訴 Protractor 載入特定的頁面。

  • describeit - 都是 Jasmine 測試框架的語法。’Describe’ 用於包含測試用例的端到端流程,而 ‘it’ 包含一些測試場景。我們可以在測試用例程式中有多個 ‘it’ 塊。

  • Expect - 它是一個斷言,我們在這裡將網頁標題與一些預定義資料進行比較。

  • ignoreSynchronization - 它是瀏覽器的標籤,當我們嘗試測試非 Angular 網站時使用。Protractor 期望僅與 Angular 網站一起工作,但如果我們想與非 Angular 網站一起工作,則必須將此標籤設定為 “true”

配置檔案

顧名思義,此檔案提供所有 Protractor 配置選項的說明。它基本上告訴 Protractor 以下內容:

  • 在哪裡查詢測試或規範檔案
  • 選擇哪個瀏覽器
  • 使用哪個測試框架
  • 在哪裡與 Selenium 伺服器通訊

示例

以下是包含測試的簡單指令碼 config.js

// config.js
exports.config = {
   directConnect: true,

   // Capabilities to be passed to the webdriver instance.
   capabilities: {
      'browserName': 'chrome'
   },

   // Framework to use. Jasmine is recommended.
   framework: 'jasmine',

   // Spec patterns are relative to the current working directory when
   // protractor is called.
   specs: ['TestSpecification.js'],

程式碼說明

上述包含三個基本引數的配置檔案程式碼可以解釋如下:

Capabilities 引數

此引數用於指定瀏覽器的名稱。它可以在 conf.js 檔案的以下程式碼塊中看到:

exports.config = {
   directConnect: true,

   // Capabilities to be passed to the webdriver instance.
   capabilities: {
      'browserName': 'chrome'
},

如上所示,這裡給出的瀏覽器名稱是 'chrome',它是 Protractor 的預設瀏覽器。我們也可以更改瀏覽器的名稱。

Framework 引數

此引數用於指定測試框架的名稱。它可以在 config.js 檔案的以下程式碼塊中看到:

exports.config = {
   directConnect: true,

   // Framework to use. Jasmine is recommended.
   framework: 'jasmine',

這裡我們使用的是 'jasmine' 測試框架。

原始檔宣告引數

此引數用於指定原始檔宣告的名稱。它可以在 conf.js 檔案的以下程式碼塊中看到:

exports.config = {
   directConnect: true,
   // Spec patterns are relative to the current working 
   directory when protractor is called.
   specs: ['TsetSpecification.js'],

如上所示,這裡給出的原始檔宣告名稱是 ‘TestSpecification.js’。這是因為,對於此示例,我們建立的規範檔案名為 TestSpecification.js

執行程式碼

由於我們已經對執行 Protractor 所需的檔案及其編碼有了基本的瞭解,讓我們嘗試執行該示例。我們可以按照以下步驟執行此示例:

  • 步驟 1 - 首先,開啟命令提示符。

  • 步驟 2 - 接下來,我們需要轉到儲存我們檔案的目錄,即 config.js 和 TestSpecification.js

  • 步驟 3 - 現在,透過執行命令 Protrcator config.js 執行 config.js 檔案。

下面顯示的螢幕截圖將解釋執行示例的上述步驟:

Executing code

從螢幕截圖中可以看出,測試已透過。

現在,假設如果我們正在測試非 Angular 網站並且沒有將 ignoreSynchronization 標籤設定為 true,那麼在執行程式碼後,我們將收到錯誤“Angular could not be found on the page”。

它可以在以下螢幕截圖中看到:

Ignore Synchronization

報告生成

到目前為止,我們已經討論了執行測試用例所需的檔案及其編碼。Protractor 也能夠為測試用例生成報告。為此,它支援 Jasmine。JunitXMLReporter 可用於自動生成測試執行報告。

但在那之前,我們需要使用以下命令安裝 Jasmine 報告程式:

npm install -g jasmine-reporters

如您所見,安裝 Jasmine Reporters 時使用了 -g 選項,這是因為我們已全域性安裝了 Protractor,使用 -g 選項。

成功安裝 jasmine-reporters 後,我們需要將以下程式碼新增到我們之前使用的 config.js 檔案中:

onPrepare: function(){ //configure junit xml report

   var jasmineReporters = require('jasmine-reporters');
   jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
      consolidateAll: true,
      filePrefix: 'guitest-xmloutput',
      savePath: 'test/reports'
   }));

現在,我們的新 config.js 檔案如下所示:

// An example configuration file.
exports.config = {
   directConnect: true,

   // Capabilities to be passed to the webdriver instance.
   capabilities: {
      'browserName': 'chrome'
   },

   // Framework to use. Jasmine is recommended.
   framework: 'jasmine',

   // Spec patterns are relative to the current working directory when
   // protractor is called.
   specs: ['TestSpecification.js'],
   //framework: "jasmine2", //must set it if you use JUnitXmlReporter
   onPrepare: function(){ //configure junit xml report
      var jasmineReporters = require('jasmine-reporters');
      jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
         consolidateAll: true,
         filePrefix: 'guitest-xmloutput',
         savePath: 'reports'
      }));
   },
};

以與之前相同的方式執行上述配置檔案後,它將在根目錄下的 reports 資料夾中生成一個包含報告的 XML 檔案。如果測試成功,則報告將如下所示:

Report Generation

但是,如果測試失敗,則報告將如下所示:

Report Generation failed

Protractor - 核心 API

本章讓您瞭解各種核心 API,這些 API 是 Protractor 執行的關鍵。

Protractor API 的重要性

Protractor 為我們提供了廣泛的 API,這些 API 對於執行以下操作以獲取網站的當前狀態非常重要:

  • 獲取我們將要測試的網頁的 DOM 元素。
  • 與 DOM 元素互動。
  • 為它們分配操作。
  • 與它們共享資訊。

為了執行上述任務,瞭解 Protractor API 非常重要。

各種 Protractor API

眾所周知,Protractor 是 Selenium-WebDriver 的一個包裝器,Selenium-WebDriver 是 Node.js 的 WebDriver 繫結。Protractor 具有以下 API:

瀏覽器

它是 WebDriver 例項的一個包裝器,用於處理瀏覽器級別的命令,例如導航、頁面範圍的資訊等。例如,browser.get 方法載入頁面。

元素

它用於搜尋和互動我們正在測試的頁面上的 DOM 元素。為此,它需要一個引數來定位元素。

定位器 (by)

它是一組元素定位器策略。例如,可以透過 CSS 選擇器、ID 或任何其他它們與 ng-model 繫結的屬性來查詢元素。

接下來,我們將詳細討論這些 API 及其功能。

瀏覽器 API

如上所述,它是 WebDriver 例項的一個包裝器,用於處理瀏覽器級別的命令。它執行各種功能,如下所示:

函式及其描述

ProtractorBrowser API 的函式如下:

browser.angularAppRoot

此 Browser API 函式設定我們將要查詢 Angular 的元素的 CSS 選擇器。通常,此函式位於 'body' 中,但如果我們的 ng-app 位於頁面的子部分,則它也可能是一個子元素。

browser.waitForAngularEnabled

此 Browser API 函式可以設定為 true 或 false。顧名思義,如果此函式設定為 false,則 Protractor 不會等待 Angular $http 和 $timeout 任務完成,然後再與瀏覽器互動。我們也可以在不更改當前狀態的情況下透過呼叫 waitForAngularEnabled() 而不傳遞值來讀取當前狀態。

browser.getProcessedConfig

藉助此 browser API 函式,我們可以獲取當前正在執行的已處理配置物件,包括規範和功能。

browser.forkNewDriverInstance

顧名思義,此函式將派生另一個瀏覽器例項,用於互動式測試。它可以在啟用和停用控制流的情況下執行。以下分別給出兩種情況的示例:

示例 1

執行 browser.forkNewDriverInstance() 且控制流已啟用:

var fork = browser.forkNewDriverInstance();
fork.get(‘page1’);

示例 2

執行 browser.forkNewDriverInstance() 且控制流已停用:

var fork = await browser.forkNewDriverInstance().ready;
await forked.get(‘page1’);

browser.restart

顧名思義,它將透過關閉瀏覽器例項並建立新例項來重新啟動瀏覽器。它也可以在啟用和停用控制流的情況下執行。以下分別給出兩種情況的示例:

示例 1 - 執行 browser.restart() 且控制流已啟用:

browser.get(‘page1’);
browser.restart();
browser.get(‘page2’);

示例 2 - 執行 browser.forkNewDriverInstance() 且控制流已停用:

await browser.get(‘page1’);
await browser.restart();
await browser.get(‘page2’);

browser.restartSync

它類似於 browser.restart() 函式。唯一的區別在於它直接返回新的瀏覽器例項,而不是返回一個解析為新瀏覽器例項的 Promise。它只能在啟用控制流時執行。

示例 - 執行 browser.restartSync() 且控制流已啟用:

browser.get(‘page1’);
browser.restartSync();
browser.get(‘page2’);

browser.useAllAngular2AppRoots

顧名思義,它僅與 Angular2 相容。在查詢元素或等待穩定性時,它將搜尋頁面上所有可用的 Angular 應用程式。

browser.waitForAngular

此 browser API 函式指示 WebDriver 等待 Angular 完成渲染並且沒有未完成的 $http 或 $timeout 呼叫,然後再繼續。

browser.findElement

顧名思義,此 browser API 函式會在搜尋元素之前等待 Angular 完成渲染。

browser.isElementPresent

顧名思義,此 browser API 函式將測試元素是否存在於頁面上。

browser.addMockModule

它將在每次呼叫 Protractor.get 方法時新增一個模組以在 Angular 之前載入。

示例

browser.addMockModule('modName', function() {
   angular.module('modName', []).value('foo', 'bar');
});

browser.clearMockModules

與 browser.addMockModule 不同,它將清除已註冊的模擬模組列表。

browser.removeMockModule

顧名思義,它將刪除註冊的模擬模組。例如:browser.removeMockModule('modName');

browser.getRegisteredMockModules

與 browser.clearMockModule 相反,它將獲取已註冊的模擬模組列表。

browser.get

我們可以使用 browser.get() 將瀏覽器導航到特定網路地址,並在 Angular 載入之前為該頁面載入模擬模組。

示例

browser.get(url);
browser.get('https://:3000'); 
// This will navigate to the localhost:3000 and will load mock module if needed

browser.refresh

顧名思義,這將重新載入當前頁面並在 Angular 之前載入模擬模組。

browser.navigate

顧名思義,它用於將導航方法混合到導航物件中,以便像以前一樣呼叫它們。例如:driver.navigate().refresh()。

browser.setLocation

它用於使用頁面內導航瀏覽到另一個頁面。

示例

browser.get('url/ABC');
browser.setLocation('DEF');
expect(browser.getCurrentUrl())
   .toBe('url/DEF');

它將從 ABC 頁面導航到 DEF 頁面。

browser.debugger

顧名思義,這必須與 protractor debug 一起使用。此函式基本上向控制流新增一項任務,以暫停測試並將輔助函式注入瀏覽器,以便可以在瀏覽器控制檯中進行除錯。

browser.pause

它用於除錯 WebDriver 測試。我們可以在測試中使用 browser.pause() 以從控制流中的該點進入 protractor 偵錯程式。

示例

element(by.id('foo')).click();
browser.pause();
// Execution will stop before the next click action.
element(by.id('bar')).click();

browser.controlFlowEnabled

它用於確定控制流是否已啟用。

Protractor - 核心 API(續…)

在本章中,讓我們學習更多 Protractor 的核心 API。

元素 API

Element 是 Protractor 公開的全域性函式之一。此函式接受一個定位器並返回以下內容:

  • ElementFinder,它根據定位器查詢單個元素。
  • ElementArrayFinder,它根據定位器查詢元素陣列。

以上兩者都支援如下所述的鏈式方法。

ElementArrayFinder 的鏈式函式及其描述

以下是 ElementArrayFinder 的函式 -

element.all(locator).clone

顧名思義,此函式將建立元素陣列(即 ElementArrayFinder)的淺複製。

element.all(locator).all(locator)

此函式基本上返回一個新的 ElementArrayFinder,它可能為空或包含子元素。它可以用於如下選擇多個元素作為陣列

示例

element.all(locator).all(locator)
elementArr.all(by.css(‘.childselector’));
// it will return another ElementFindArray as child element based on child locator.

element.all(locator).filter(filterFn)

顧名思義,在將過濾器函式應用於 ElementArrayFinder 中的每個元素後,它將返回一個新的 ElementArrayFinder,其中包含透過過濾器函式的所有元素。它基本上有兩個引數,第一個是 ElementFinder,第二個是索引。它也可以在頁面物件中使用。

示例

檢視

<ul class = "items">
   <li class = "one">First</li>
   <li class = "two">Second</li>
   <li class = "three">Third</li>
</ul>

程式碼

element.all(by.css('.items li')).filter(function(elem, index) {
   return elem.getText().then(function(text) {
      return text === 'Third';
   });
}).first().click();

element.all(locator).get(index)

藉助它,我們可以透過索引獲取 ElementArrayFinder 中的元素。請注意,索引從 0 開始,負索引會被包裝。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let list = element.all(by.css('.items li'));
expect(list.get(0).getText()).toBe('First');
expect(list.get(1).getText()).toBe('Second');

element.all(locator).first()

顧名思義,這將獲取 ElementArrayFinder 的第一個元素。它不會檢索底層元素。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let first = element.all(by.css('.items li')).first();
expect(first.getText()).toBe('First');

element.all(locator).last()

顧名思義,這將獲取 ElementArrayFinder 的最後一個元素。它不會檢索底層元素。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let first = element.all(by.css('.items li')).last();
expect(last.getText()).toBe('Third');

element.all(locator).all(selector)

當呼叫 $$ 時可能需要連結時,它用於查詢父級內的元素陣列。

示例

檢視

<div class = "parent">
   <ul>
      <li class = "one">First</li>
      <li class = "two">Second</li>
      <li class = "three">Third</li>
   </ul>
</div>

程式碼

let items = element(by.css('.parent')).$$('li');

element.all(locator).count()

顧名思義,這將計算 ElementArrayFinder 表示的元素數量。它不會檢索底層元素。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let list = element.all(by.css('.items li'));
expect(list.count()).toBe(3);

element.all(locator).isPresent()

它將使用查詢器匹配元素。它可以返回 true 或 false。如果存在任何與查詢器匹配的元素,則為 true,否則為 false。

示例

expect($('.item').isPresent()).toBeTruthy();

element.all(locator).locator

顧名思義,它將返回最相關的定位器。

示例

$('#ID1').locator();
// returns by.css('#ID1')
$('#ID1').$('#ID2').locator();
// returns by.css('#ID2')
$$('#ID1').filter(filterFn).get(0).click().locator();
// returns by.css('#ID1')

element.all(locator).then(thenFunction)

它將檢索 ElementArrayFinder 表示的元素。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

element.all(by.css('.items li')).then(function(arr) {
   expect(arr.length).toEqual(3);
});

element.all(locator).each(eachFunction)

顧名思義,它將對 ElementArrayFinder 表示的每個 ElementFinder 呼叫輸入函式。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

element.all(by.css('.items li')).each(function(element, index) {
   // It will print First 0, Second 1 and Third 2.
   element.getText().then(function (text) {
      console.log(index, text);
   });
});

element.all(locator).map(mapFunction)

顧名思義,它將在 ElementArrayFinder 中的每個元素上應用對映函式。它有兩個引數。第一個是 ElementFinder,第二個是索引。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let items = element.all(by.css('.items li')).map(function(elm, index) {
   return {
      index: index,
      text: elm.getText(),
      class: elm.getAttribute('class')
   };
});
expect(items).toEqual([
   {index: 0, text: 'First', class: 'one'},
   {index: 1, text: 'Second', class: 'two'},
   {index: 2, text: 'Third', class: 'three'}
]);

element.all(locator).reduce(reduceFn)

顧名思義,它將對累加器和使用定位器找到的每個元素應用歸約函式。此函式將每個元素歸約為單個值。

示例

檢視

<ul class = "items">
   <li>First</li>
   <li>Second</li>
   <li>Third</li>
</ul>

程式碼

let value = element.all(by.css('.items li')).reduce(function(acc, elem) {
   return elem.getText().then(function(text) {
      return acc + text + ' ';
   });
}, '');

expect(value).toEqual('First Second Third ');

element.all(locator).evaluate

顧名思義,它將評估輸入是否在當前底層元素的範圍內。

示例

檢視

<span class = "foo">{{letiableInScope}}</span>

程式碼

let value = 
element.all(by.css('.foo')).evaluate('letiableInScope');

element.all(locator).allowAnimations

顧名思義,它將確定當前底層元素是否允許動畫。

示例

element(by.css('body')).allowAnimations(false);

ElementFinder 的鏈式函式及其描述

ElementFinder 的鏈式函式及其描述 -

element(locator).clone

顧名思義,此函式將建立 ElementFinder 的淺複製。

element(locator).getWebElement()

它將返回此 ElementFinder 表示的 WebElement,如果元素不存在,則會丟擲 WebDriver 錯誤。

示例

檢視

<div class="parent">
   some text
</div>

程式碼

// All the four following expressions are equivalent.
$('.parent').getWebElement();
element(by.css('.parent')).getWebElement();
browser.driver.findElement(by.css('.parent'));
browser.findElement(by.css('.parent'));

element(locator).all(locator)

它將在父級內查詢元素陣列。

示例

檢視

<div class = "parent">
   <ul>
      <li class = "one">First</li>
      <li class = "two">Second</li>
      <li class = "three">Third</li>
   </ul>
</div>

程式碼

let items = element(by.css('.parent')).all(by.tagName('li'));

element(locator).element(locator)

它將在父級內查詢元素。

示例

檢視

<div class = "parent">
   <div class = "child">
      Child text
      <div>{{person.phone}}</div>
   </div>
</div>

程式碼

// Calls Chain 2 element.
let child = element(by.css('.parent')).
   element(by.css('.child'));
expect(child.getText()).toBe('Child text\n981-000-568');

// Calls Chain 3 element.
let triple = element(by.css('.parent')).
   element(by.css('.child')).
   element(by.binding('person.phone'));
expect(triple.getText()).toBe('981-000-568');

element(locator).all(selector)

當呼叫 $$ 時可能需要連結時,它用於查詢父級內的元素陣列。

示例

檢視

<div class = "parent">
   <ul>
      <li class = "one">First</li>
      <li class = "two">Second</li>
      <li class = "three">Third</li>
   </ul>
</div>

程式碼

let items = element(by.css('.parent')).$$('li'));

element(locator).$(locator)

當呼叫 $ 時可能需要連結時,它將在父級內查詢元素。

示例

檢視

<div class = "parent">
   <div class = "child">
      Child text
      <div>{{person.phone}}</div>
  </div>
</div>

程式碼

// Calls Chain 2 element.
let child = element(by.css('.parent')).
   $('.child'));
expect(child.getText()).toBe('Child text\n981-000-568');

// Calls Chain 3 element.
let triple = element(by.css('.parent')).
   $('.child')).
   element(by.binding('person.phone'));
expect(triple.getText()).toBe('981-000-568');

element(locator).isPresent()

它將確定元素是否顯示在頁面上。

示例

檢視

<span>{{person.name}}</span>

程式碼

expect(element(by.binding('person.name')).isPresent()).toBe(true);
// will check for the existence of element

expect(element(by.binding('notPresent')).isPresent()).toBe(false); 
// will check for the non-existence of element

element(locator).isElementPresent()

它與 element(locator).isPresent() 相同。唯一的區別是它將檢查由子定位器標識的元素是否存在,而不是當前元素查詢器。

element.all(locator).evaluate

顧名思義,它將評估輸入是否在當前底層元素的範圍內。

示例

檢視

<span id = "foo">{{letiableInScope}}</span>

程式碼

let value = element(by.id('.foo')).evaluate('letiableInScope');

element(locator).allowAnimations

顧名思義,它將確定當前底層元素是否允許動畫。

示例

element(by.css('body')).allowAnimations(false);

element(locator).equals

顧名思義,它將比較元素是否相等。

定位器 (by) API

它基本上是元素定位器策略的集合,透過繫結、模型等提供在 Angular 應用程式中查詢元素的方法。

函式及其描述

ProtractorLocators API 的函式如下 -

by.addLocator(locatorName,fuctionOrScript)

它將向此 ProtrcatorBy 例項新增一個定位器,該定位器可以進一步與 element(by.locatorName(args)) 一起使用。

示例

檢視

<button ng-click = "doAddition()">Go!</button>

程式碼

// Adding the custom locator.
by.addLocator('buttonTextSimple',
      function(buttonText, opt_parentElement, opt_rootSelector) {

      var using = opt_parentElement || document,
         buttons = using.querySelectorAll('button');

      return Array.prototype.filter.call(buttons, function(button) {
      return button.textContent === buttonText;
   });
});
element(by.buttonTextSimple('Go!')).click();// Using the custom locator.

by.binding

顧名思義,它將透過文字繫結查詢元素。將進行部分匹配,以便返回繫結到包含輸入字串的變數的任何元素。

示例

檢視

<span>{{person.name}}</span>
<span ng-bind = "person.email"></span>

程式碼

var span1 = element(by.binding('person.name'));
expect(span1.getText()).toBe('Foo');

var span2 = element(by.binding('person.email'));
expect(span2.getText()).toBe('foo@bar.com');

by.exactbinding

顧名思義,它將透過精確繫結查詢元素。

示例

檢視

<spangt;{{ person.name }}</spangt;
<span ng-bind = "person-email"gt;</spangt;
<spangt;{{person_phone|uppercase}}</span>

程式碼

expect(element(by.exactBinding('person.name')).isPresent()).toBe(true);
expect(element(by.exactBinding('person-email')).isPresent()).toBe(true);
expect(element(by.exactBinding('person')).isPresent()).toBe(false);
expect(element(by.exactBinding('person_phone')).isPresent()).toBe(true);
expect(element(by.exactBinding('person_phone|uppercase')).isPresent()).toBe(true);
expect(element(by.exactBinding('phone')).isPresent()).toBe(false);

by.model(modelName)

顧名思義,它將透過 ng-model 表示式查詢元素。

示例

檢視

<input type = "text" ng-model = "person.name">

程式碼

var input = element(by.model('person.name'));
input.sendKeys('123');
expect(input.getAttribute('value')).toBe('Foo123');

by.buttonText

顧名思義,它將透過文字查詢按鈕。

示例

檢視

<button>Save</button>

程式碼

element(by.buttonText('Save'));

by.partialButtonText

顧名思義,它將透過部分文字查詢按鈕。

示例

檢視

<button>Save my file</button>

程式碼

element(by.partialButtonText('Save'));

by.repeater

顧名思義,它將在 ng-repeat 內查詢元素。

示例

檢視

<div ng-repeat = "cat in pets">
   <span>{{cat.name}}</span>
   <span>{{cat.age}}</span>
<</div>
<div class = "book-img" ng-repeat-start="book in library">
   <span>{{$index}}</span>
</div>
<div class = "book-info" ng-repeat-end>
   <h4>{{book.name}}</h4>
   <p>{{book.blurb}}</p>
</div>

程式碼

var secondCat = element(by.repeater('cat in 
pets').row(1)); // It will return the DIV for the second cat.
var firstCatName = element(by.repeater('cat in pets').
   row(0).column('cat.name')); // It will return the SPAN for the first cat's name.

by.exactRepeater

顧名思義,它將透過精確重複查詢元素。

示例

檢視

<li ng-repeat = "person in peopleWithRedHair"></li>
<li ng-repeat = "car in cars | orderBy:year"></li>

程式碼

expect(element(by.exactRepeater('person in
peopleWithRedHair')).isPresent())
   .toBe(true);
expect(element(by.exactRepeater('person in
people')).isPresent()).toBe(false);
expect(element(by.exactRepeater('car in cars')).isPresent()).toBe(true);

by.cssContainingText

顧名思義,它將透過 CSS 查詢包含精確字串的元素。

示例

檢視

<ul>
<li class = "pet">Dog</li>
<li class = "pet">Cat</li>
</ul>

程式碼

var dog = element(by.cssContainingText('.pet', 'Dog')); 
// It will return the li for the dog, but not for the cat.

by.options(optionsDescriptor)

顧名思義,它將透過 ng-options 表示式查詢元素。

示例

檢視

<select ng-model = "color" ng-options = "c for c in colors">
   <option value = "0" selected = "selected">red</option>
   <option value = "1">green</option>
</select>

程式碼

var allOptions = element.all(by.options('c for c in colors'));
expect(allOptions.count()).toEqual(2);
var firstOption = allOptions.first();
expect(firstOption.getText()).toEqual('red');

by.deepCSS(selector)

顧名思義,它將透過影子 DOM 中的 CSS 選擇器查詢元素。

示例

檢視

<div>
   <span id = "outerspan">
      <"shadow tree">
         <span id = "span1"></span>
      <"shadow tree">
      <span id = "span2"></span>
   </>
   </>
</div>

程式碼

var spans = element.all(by.deepCss('span'));
expect(spans.count()).toEqual(3);

量角器 - 物件

本章詳細討論了 Protractor 中的物件。

什麼是頁面物件?

頁面物件是一種設計模式,它已成為編寫 e2e 測試的流行方法,以增強測試維護並減少程式碼重複。它可以定義為一個面向物件的類,充當 AUT(被測應用程式)頁面的介面。但是,在深入研究頁面物件之前,我們必須瞭解自動化 UI 測試的挑戰以及處理這些挑戰的方法。

自動化 UI 測試的挑戰

以下是自動化 UI 測試的一些常見挑戰 -

UI 更改

在使用 UI 測試時,非常常見的問題是 UI 中發生的更改。例如,大多數情況下按鈕或文字框等通常會發生更改,併為 UI 測試帶來問題。

缺乏 DSL(領域特定語言)支援

UI 測試的另一個問題是缺乏 DSL 支援。由於此問題,很難理解正在測試的內容。

大量重複/程式碼重複

UI 測試中的下一個常見問題是存在大量重複或程式碼重複。可以透過以下幾行程式碼來理解 -

element(by.model(‘event.name’)).sendKeys(‘An Event’);
element(by.model(‘event.name’)).sendKeys(‘Module 3’);
element(by.model(‘event.name’));

維護困難

由於上述挑戰,維護變得令人頭疼。這是因為我們必須找到所有例項,替換為新名稱、選擇器和其他程式碼。我們還需要花費大量時間來使測試與重構保持一致。

測試中斷

UI 測試中的另一個挑戰是測試中發生大量故障。

處理挑戰的方法

我們已經看到了一些 UI 測試的常見挑戰。處理此類挑戰的一些方法如下 -

手動更新引用

處理上述挑戰的第一個選項是手動更新引用。此選項的問題在於我們必須在程式碼以及測試中進行手動更改。當您有一個或兩個測試檔案時,這可以做到,但是如果您的專案中有數百個測試檔案怎麼辦?

使用頁面物件

處理上述挑戰的另一個選項是使用頁面物件。頁面物件基本上是一個純 JavaScript,它封裝了 Angular 模板的屬性。例如,以下規範檔案是在沒有和有頁面物件的情況下編寫的,以瞭解差異 -

無頁面物件

describe('angularjs homepage', function() {
   it('should greet the named user', function() {
      browser.get('http://www.angularjs.org');
      element(by.model('yourName')).sendKeys('Julie');
      var greeting = element(by.binding('yourName'));
      expect(greeting.getText()).toEqual('Hello Julie!');
   });
});

有頁面物件

要使用頁面物件編寫程式碼,我們需要做的第一件事是建立一個頁面物件。因此,上述示例的頁面物件可能如下所示 -

var AngularHomepage = function() {
   var nameInput = element(by.model('yourName'));
   var greeting = element(by.binding('yourName'));

   this.get = function() {
      browser.get('http://www.angularjs.org');
   };

   this.setName = function(name) {
      nameInput.sendKeys(name);
   };
   
   this.getGreetingText = function() {
      return greeting.getText();
   };
};
module.exports = new AngularHomepage();

使用頁面物件組織測試

我們在上面的示例中已經看到了頁面物件的使用,以處理 UI 測試的挑戰。接下來,我們將討論如何使用它們來組織測試。為此,我們需要修改測試指令碼,而無需修改測試指令碼的功能。

示例

為了理解這個概念,我們使用帶有頁面物件的上述配置檔案。我們需要修改測試指令碼如下 -

var angularHomepage = require('./AngularHomepage');
describe('angularjs homepage', function() {
   it('should greet the named user', function() {
      angularHomepage.get();

      angularHomepage.setName('Julie');
   
      expect(angularHomepage.getGreetingText()).toEqual
      ('Hello Julie!');
   });
});

這裡,請注意頁面物件的路徑相對於您的規範。

同樣,我們還可以將測試套件分離成各種測試套件。然後配置檔案可以更改如下

exports.config = {
   // The address of a running selenium server.
   seleniumAddress: 'https://:4444/wd/hub',

   // Capabilities to be passed to the webdriver instance.
   capabilities: {
      'browserName': 'chrome'
   },
   // Spec patterns are relative to the location of the spec file. They may
   // include glob patterns.
   suites: {
      homepage: 'tests/e2e/homepage/**/*Spec.js',
      search: ['tests/e2e/contact_search/**/*Spec.js',
         'tests/e2e/venue_search/**/*Spec.js']
   },

   // Options to be passed to Jasmine-node.
   jasmineNodeOpts: {
      showColors: true, // Use colors in the command line report.
   }
};

現在,我們可以輕鬆地在執行一個或另一個測試套件之間切換。以下命令將僅執行測試的主頁部分 -

protractor protractor.conf.js --suite homepage

類似地,我們可以使用以下命令執行特定的測試套件 -

protractor protractor.conf.js --suite homepage,search

量角器 - 除錯

現在我們已經瞭解了前幾章中 Protractor 的所有概念,讓我們在本章中詳細瞭解除錯概念。

介紹

端到端 (e2e) 測試非常難以除錯,因為它們依賴於該應用程式的整個生態系統。我們已經看到它們依賴於各種操作,或者更準確地說,依賴於先前的操作,例如登入,有時它們還依賴於許可權。除錯 e2e 測試的另一個困難是它依賴於 WebDriver,因為它在不同的作業系統和瀏覽器上表現不同。最後,除錯 e2e 測試還會生成冗長的錯誤訊息,並且難以區分與瀏覽器相關的錯誤和測試過程錯誤。

故障型別

測試套件失敗可能有多種原因,以下是一些眾所周知的故障型別 -

WebDriver 故障

當命令無法完成時,WebDriver 會丟擲錯誤。例如,瀏覽器無法獲取定義的地址,或者元素未按預期找到。

WebDriver 意外故障

當 WebDriver 管理器無法更新時,會發生意外的瀏覽器和作業系統相關的故障。

Protractor Angular 故障

當 Protractor 未能按預期在庫中找到 Angular 時,就會發生 Protractor Angular 故障。

Protractor Angular2 故障

在這種型別的故障中,當配置檔案中找不到 useAllAngular2AppRoots 引數時,Protractor 會失敗。發生這種情況是因為,如果沒有它,測試過程將檢視單個根元素,而期望在過程中有多個元素。

Protractor 超時故障

當測試規範遇到迴圈或長時間池並無法及時返回資料時,就會發生這種型別的故障。

期望失敗

最常見的測試失敗之一,它顯示了正常的期望失敗是什麼樣子。

為什麼在 Protractor 中除錯很重要?

假設,如果您編寫了測試用例並且它們失敗了,那麼瞭解如何除錯這些測試用例非常重要,因為找到錯誤發生的確切位置將非常困難。在使用 Protractor 時,您將在命令列中看到一些紅色字型顯示的長錯誤。

暫停和除錯測試

以下是 Protractor 中除錯的方法 &miuns;

暫停方法

使用暫停方法除錯 Protractor 中的測試用例是最簡單的方法之一。我們可以在想要暫停測試程式碼的位置鍵入以下命令 &miuns;

browser.pause();

當執行的程式碼遇到上述命令時,它將暫停該點的程式執行。之後,我們可以根據自己的喜好執行以下命令:

鍵入 C 以繼續執行

每當一個命令執行完畢後,我們必須鍵入 C 以繼續執行。如果您不輸入 C,測試將無法執行完整程式碼,並且由於 Jasmine 超時錯誤而失敗。

鍵入 repl 以進入互動模式

互動模式的好處是可以向瀏覽器傳送 WebDriver 命令。如果要進入互動模式,請鍵入repl

鍵入 Ctrl-C 以退出並繼續測試

要從暫停狀態退出測試並從停止處繼續測試,我們需要鍵入 Ctrl-C。

示例

在這個例子中,我們有一個名為example_debug.js的規範檔案,Protractor 嘗試使用定位符by.binding('mmmm') 識別一個元素,但是 URL(https://angularjs.org/)頁面沒有指定定位符的元素。

describe('Suite for protractor debugger',function(){
   it('Failing spec',function(){
      browser.get("http://angularjs.org");
      element(by.model('yourName')).sendKeys('Vijay');
         //Element doesn't exist
         var welcomeText = 
         element(by.binding('mmmm')).getText();
         expect('Hello '+welcomeText+'!').toEqual('Hello Ram!')
   });
});

現在,要執行上述測試,我們需要在上述規範檔案中新增 `browser.pause()` 程式碼,您希望在該處暫停測試。它將如下所示:

describe('Suite for protractor debugger',function(){
   it('Failing spec',function(){
      browser.get("http://angularjs.org");
      browser.pause();
      element(by.model('yourName')).sendKeys('Vijay');
      //Element doesn't exist
      var welcomeText = 
      element(by.binding('mmmm')).getText();
      expect('Hello '+welcomeText+'!').toEqual('Hello Ram!')
   });
});

但在執行之前,我們還需要對配置檔案進行一些更改。我們將在前面章節中使用的名為example_configuration.js的配置檔案中進行以下更改:

// An example configuration file.
exports.config = {
   directConnect: true,

   // Capabilities to be passed to the webdriver instance.
   capabilities: {
      'browserName': 'chrome'
   },

   // Framework to use. Jasmine is recommended.
   framework: 'jasmine',

   // Spec patterns are relative to the current working directory when

   // protractor is called.
   specs: ['example_debug.js'],
      allScriptsTimeout: 999999,
      jasmineNodeOpts: {
      defaultTimeoutInterval: 999999
   },
   onPrepare: function () {
      browser.manage().window().maximize();
      browser.manage().timeouts().implicitlyWait(5000);
   }
};

現在,執行以下命令:

protractor example_configuration.js

執行以上命令後,偵錯程式將啟動。

偵錯程式方法

使用暫停方法除錯 Protractor 中的測試用例是一種稍微高階的方法。我們可以在想要中斷測試程式碼的位置鍵入以下命令:

browser.debugger();

它使用 Node 偵錯程式來除錯測試程式碼。要執行上述命令,我們必須在從測試專案位置開啟的單獨的命令提示符中鍵入以下命令:

protractor debug protractor.conf.js

在這種方法中,我們也需要在終端中鍵入 C 以繼續測試程式碼。但與暫停方法相反,在這種方法中,只需要鍵入一次。

示例

在這個例子中,我們使用的是與上面相同的規範檔案,名為example_debug.js。唯一的區別是,我們需要使用browser.debugger()代替browser.pause()來中斷測試程式碼。它將如下所示:

describe('Suite for protractor debugger',function(){
   it('Failing spec',function(){
      browser.get("http://angularjs.org");
      browser.debugger();
      element(by.model('yourName')).sendKeys('Vijay');
      //Element doesn't exist
      var welcomeText = element(by.binding('mmmm')).getText();
      expect('Hello '+welcomeText+'!').toEqual('Hello Ram!')
   });
});

我們使用的是與上面示例中相同的配置檔案example_configuration.js

現在,使用以下除錯命令列選項執行 Protractor 測試

protractor debug example_configuration.js

執行以上命令後,偵錯程式將啟動。

Protractor - Protractor 風格指南

在本章中,讓我們詳細瞭解 Protractor 的風格指南。

介紹

該風格指南由兩位軟體工程師建立,分別是 ING 的前端工程師Carmen Popoviciu和 Google 的軟體工程師Andres Dominguez。因此,此風格指南也被稱為 Carmen Popoviciu 和 Google 的 Protractor 風格指南。

此風格指南可以分為以下五個要點:

  • 通用規則
  • 專案結構
  • 定位器策略
  • 頁面物件
  • 測試套件

通用規則

以下是一些在使用 Protractor 進行測試時必須注意的通用規則:

不要對已經進行單元測試的內容進行端到端測試

這是 Carmen 和 Andres 給出的第一個通用規則。他們建議我們不要對已經進行單元測試的程式碼執行端到端測試。其主要原因是單元測試比端到端測試快得多。另一個原因是我們必須避免重複測試(不要同時執行單元測試和端到端測試)以節省時間。

僅使用一個配置檔案

另一個重要的建議是,我們必須只使用一個配置檔案。不要為每個測試環境建立配置檔案。您可以使用grunt-protractor-coverage來設定不同的環境。

避免在測試中使用邏輯

我們必須避免在測試用例中使用 IF 語句或 FOR 迴圈,因為如果我們這樣做,測試可能會在不進行任何測試的情況下透過,或者執行速度非常慢。

使測試在檔案級別獨立

當啟用共享時,Protractor 可以並行執行測試。然後,這些檔案會在不同的瀏覽器中按可用情況執行。Carmen 和 Andres 建議至少在檔案級別使測試獨立,因為 Protractor 執行它們的順序是不確定的,而且隔離執行測試非常容易。

專案結構

關於 Protractor 風格指南的另一個重要要點是專案的結構。以下是關於專案結構的建議:

以合理的結構對端到端測試進行分組

Carmen 和 Andres 建議我們必須以對專案結構有意義的方式對端到端測試進行分組。此建議背後的原因是查詢檔案將變得容易,並且資料夾結構將更易讀。此步驟還將端到端測試與單元測試分開。他們建議應避免以下型別的結構:

|-- project-folder
   |-- app
      |-- css
      |-- img
      |-- partials
         home.html
         profile.html
         contacts.html
      |-- js
         |-- controllers
         |-- directives
         |-- services
         app.js
         ...
      index.html
   |-- test
      |-- unit
      |-- e2e
         home-page.js
         home-spec.js
         profile-page.js
         profile-spec.js
         contacts-page.js
         contacts-spec.js

另一方面,他們建議使用以下型別的結構:

|-- project-folder
   |-- app
      |-- css
      |-- img
      |-- partials
         home.html
         profile.html
         contacts.html
      |-- js
         |-- controllers
         |-- directives
         |-- services
         app.js
         ...
      index.html
   |-- test
      |-- unit
      |-- e2e
         |-- page-objects
            home-page.js
            profile-page.js
            contacts-page.js
         home-spec.js
         profile-spec.js
         contacts-spec.js

定位器策略

以下是一些在使用 Protractor 進行測試時必須注意的定位器策略:

切勿使用 XPath

這是 Protractor 風格指南中推薦的第一個定位器策略。其背後的原因是 XPath 需要大量維護,因為標記很容易發生變化。此外,XPath 表示式速度最慢且難以除錯。

始終優先使用 Protractor 特定的定位器,例如 by.model 和 by.binding

Protractor 特定的定位器(例如 by.model 和 by.binding)簡短、具體且易於閱讀。藉助它們,編寫我們的定位器也變得非常容易。

示例

檢視

<ul class = "red">
   <li>{{color.name}}</li>
   <li>{{color.shade}}</li>
   <li>{{color.code}}</li>
</ul>

<div class = "details">
   <div class = "personal">
      <input ng-model = "person.name">
   </div>
</div>

對於上面的程式碼,建議避免以下內容:

var nameElement = element.all(by.css('.red li')).get(0);
var personName = element(by.css('.details .personal input'));

另一方面,建議使用以下內容:

var nameElement = element.all(by.css('.red li')).get(0);
var personName = element(by.css('.details .personal input'));
var nameElement = element(by.binding('color.name'));
var personName = element(by.model('person.name'));

當沒有可用的 Protractor 定位器時,建議優先使用 by.id 和 by.css。

始終避免對頻繁更改的文字使用文字定位器

我們必須避免使用基於文字的定位器,例如 by.linkText、by.buttonText 和 by.cssContaningText,因為按鈕、連結和標籤的文字會隨著時間的推移而頻繁更改。

頁面物件

如前所述,頁面物件封裝了有關應用程式頁面上元素的資訊,並因此幫助我們編寫更清晰的測試用例。頁面物件的一個非常有用的優點是它們可以在多個測試中重複使用,如果我們的應用程式模板已更改,我們只需要更新頁面物件即可。以下是一些在使用 Protractor 進行測試時必須注意的頁面物件的建議:

要與被測頁面互動,請使用頁面物件

建議使用頁面物件與被測頁面互動,因為它們可以封裝有關被測頁面上元素的資訊,並且也可以重複使用。

始終為每個檔案宣告一個頁面物件

我們應該在自己的檔案中定義每個頁面物件,因為它可以使程式碼保持整潔,並且查詢內容變得更容易。

在頁面物件檔案末尾始終使用單個 module.exports

建議每個頁面物件都應該宣告一個類,以便我們只需要匯出一個類。例如,應避免以下物件檔案的使用:

var UserProfilePage = function() {};
var UserSettingsPage = function() {};
module.exports = UserPropertiesPage;
module.exports = UserSettingsPage;

但另一方面,建議使用以下方法:

/** @constructor */
var UserPropertiesPage = function() {};

module.exports = UserPropertiesPage;

在頂部宣告所有必需的模組

我們應該在頁面物件的頂部宣告所有必需的模組,因為它使模組依賴項變得清晰且易於查詢。

在測試套件的開頭例項化所有頁面物件

建議在測試套件的開頭例項化所有頁面物件,因為這將使依賴項與測試程式碼分離,並使所有套件規範都可以使用這些依賴項。

不要在頁面物件中使用 expect()

我們不應該在頁面物件中使用 expect(),即我們不應該在頁面物件中進行任何斷言,因為所有斷言都必須在測試用例中完成。

另一個原因是,測試的閱讀者應該能夠僅透過閱讀測試用例來了解應用程式的行為。

廣告

© . All rights reserved.