###### tags: `Angular` `Protractor` `e2e` # 🌗[T]Protractor 自動化測試 https://wekan.github.io/ ------- ## 基本介紹 ### Protractor E2E測試觀念 #### 端對端測試E2E > 測試使用者操作與想要的結果是否符合規格。 端對端測試(E2E),是以使用者的角度(從開啟瀏覽業開始,一步一步操作)來進行測試,若測試程式是依照使用者需求正確撰寫的,且測試結果正確,表示產品是符合需求且正確的。 [一次搞懂單元測試、整合測試、端對端測試之間的差異](https://blog.miniasp.com/post/2019/02/18/Unit-testing-Integration-testing-e2e-testing) #### Protractor > 抓取瀏覽器上的資訊來做測試。 * Protractor是一套Angular推出的端對端(end-to-end)測試架構,測試時會開啟瀏覽器去執行測試的項目。 * Protractor建立在WebDriverJS之上,WebDriverJS使用本機事件與特定於瀏覽器的驅動程序來模擬使用者的操作。 * `describe`和`it`語法是來自Jasmine框架。 `browser`是來自Protractor創建的全域變數。ex.browser.get :::info 📝備註 不是只有Angular才可以使用Protractor,目前的前端架構基本上都可以使用。 ::: ## 環境設置 ### 安裝Protractor #### 安裝 ```npm npm install -g protractor ``` ```npm webdriver-manager update ``` ```npm webdriver-manager start ``` #### 測試是否安裝成功 ```npm npm protactor --version ``` #### 執行 ```npm Protractor protractor.conf.js ``` [Protractor 文件](https://www.protractortest.org/#/) [Protractor 快速指南](https://dwatow.github.io/2019/01-26-angularjs/angularjs-e2e-protractor/) ### Protractor.conf.js設定檔 ## 實作撰寫 ### 登入/輸入帳密/畫面轉跳 #### 程式碼 ```typescript= import { browser, element, by, protractor } from 'protractor'; describe('Ruby Login Test', () => { beforeEach(() => { }); it('登入頁面', () => { browser.waitForAngularEnabled(false); browser.get('/login').then(); }); it('輸入帳號密碼', () => { element(by.id('login')).clear(); element(by.id('login')).sendKeys('a1dev03'); element(by.id("password")).clear(); element(by.id("password")).sendKeys('a123456'); }); it('按下登入鈕', () => { element(by.id("btn")).click().then((x) => { // browser.sleep(10000); browser.wait(protractor.ExpectedConditions.urlIs("http://localhost:8080/#/basic"), 5000); expect(browser.getCurrentUrl()).toContain('/basic'); }); }); it('進銷存模組', () => { element(by.xpath("//img[@src='./images/product-icon/20200415/invoicing.png']")).click(); browser.wait(protractor.ExpectedConditions.urlIs("http://localhost:8080/#/main/home"), 5000); expect(browser.getCurrentUrl()).toContain('/main/home'); // it('登入失敗', async () => { // browser.waitForAngularEnabled(false); // element(by.id('login')).clear(); // element(by.id('login')).sendKeys('a1dev0'); // element(by.id("password")).clear(); // element(by.id("password")).sendKeys('a123456'); // element(by.xpath("//button[@id='btn']")).click(); // const errorMsg = await element(by.xpath("//span[@class='padding-left-5']")).getText(); // expect('帳號或密碼錯誤').toEqual(errorMsg); // }); }); ``` #### 🍙#1 一開始程式沒反應 > Protractor protractor.conf.js後感覺都沒執行程式 設定好protractor.conf.js,並嘗試寫了一小段程式後,執行Protroctor protractor.conf.js ```typescript= import { browser, element, by, protractor } from 'protractor'; describe('Ruby Login', () => { it('登入', () => { // browser.waitForAngularEnabled(false); browser.get('/login').then(); element(by.id('login')).clear(); element(by.id('login')).sendKeys('a1dev03'); element(by.id("password")).clear(); element(by.id("password")).sendKeys('a123456'); }); } ``` 發現它載入`/login`之後就完全不動作了,放著不動它一段時間跑出這錯誤訊息 ==⚡Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL==,花了一段時間找資料發現 * 加上browser.waitForAngularEnabled(false)就可以解決了。 #### 🍙#2 等待頁面完全載入 > 在點擊按鈕後,程式並沒有等待頁面完全載入,就繼續執行了。 一開始,我使用`browser.sleep(10000);`來等待頁面載入,但發現這樣不是很好 (因為只要當載入的時間,與設定的等待時間有誤,就會發生錯誤或多花時間等待),找到這篇[Protractor wait for the page to fully load](https://stackoverflow.com/questions/53466646/protractor-wait-for-the-page-to-fully-load),可以使用protractor.ExpectedConditions來解決這個問題。 ```typescript= it('按下登入鈕', () => { element(by.id("btn")).click().then((x) => { // browser.sleep(10000); browser.wait(protractor.ExpectedConditions.urlIs("http://localhost:8080/#/basic"), 5000); expect(browser.getCurrentUrl()).toContain('/basic'); }); }); ``` > Cannot read property 'urlIs' of undefined 在使用`protractor.ExpectedConditions.urlIs`的時候遇到==⚡Cannot read property 'urlIs' of undefined==,原因出在import的時候,我導入的路徑是proreactor/built/ptor 。 * 可以重新選擇"從protractor將protractor新增至現有的匯入宣告"。 ![protractor import](https://i.imgur.com/3epzWlL.png) * 也可以手動改成protractor。 ```typescript // import { protractor } from 'protractor/built/ptor'; import { protractor } from 'protractor'; ``` ### 新增/刪除/修改/查詢 程式碼 ```typescript= 目前沒有 ``` #### 🍙#1 取得API回傳資料 寫查詢測試時,想法是希望可以拿到api的資料來驗證與畫面上的值是否一致,因此先找看看是否有可以使用Http的方式來嘗試,參考: [Protractor : How to Call Rest API POST with header and body](https://stackoverflow.com/questions/30319610/how-to-make-http-getpost-request-in-protractor) 。 > 401驗證碼不存在的錯誤 ```typescript= // 嘗試失敗 #錯誤訊息401 function httpGet(siteUrl) { const http = require('http'); const defer = protractor.promise.defer(); http.get(siteUrl, function (response) { let bodyString = ''; response.setEncoding('utf8'); response.on("data", function (chunk) { bodyString += chunk; }); response.on('end', function () { defer.fulfill({ statusCode: response.statusCode, bodyString: bodyString }); }); }).on('error', function (e) { defer.reject("Got http.get error: " + e.message); }); return defer.promise; } ``` 看到401驗證碼錯誤訊息,大概知道是沒有給Token,後端的驗證機制將我們擋下來,所以呼叫失敗。 那我們該如何把Token給我們的http,讓他放在Request Header內? (關鍵字查詢 Protractor get Authorization) 參考: [Protractor : How to Call Rest API POST with header and body ](https://stackoverflow.com/questions/37864783/protractor-how-to-call-rest-api-post-with-header-and-body) :::success 使用前,記得要先 `npm i superagent`,使用方法可參考: [SuperAgent](https://visionmedia.github.io/superagent) ::: ```typescript= // 嘗試成功 #取得Response資料 function httpGet2(siteUrl) { const http = require('superagent'); http .get(siteUrl) .set('Authorization', '[Token]') .set('Content-Type', 'application/json') .query('q=eyJXaGVyZSI6W10sIk9yZGVyQnkiOltdfQ==') .end((err, res) => { console.log('ruby success', res.body); }); } ``` > 405 The requested resource does not support http method 'GET' 我在使用SuperAgent把Token放上去後,發生了這個錯誤,找了很久發現query不是url給了後 query就可以不用給,所以加上`.query('q=eyJXaGVyZSI6W10sIk9yZGVyQnkiOltdfQ==')`這問題就解決了。 ## 錯誤集 #### ==⚡Cannot find module== [暫時先看他的](https://github.com/angular/angular-cli/issues/9632) #### ==⚡ReferenceError: define is not defined== 不會 #### ==⚡Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL== 1. 我用錯誤訊息Async callback was not invoked within timeout specified jasmine.DEFAULT_TIMEOUT_INTERVAL找到的解答 [jasmine: Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL](https://stackoverflow.com/a/38964589) 但沒有用 2. 之後我用Protractor Timeout 找到 [Protractor Timeouts](https://www.protractortest.org/#/timeouts) 我在程式加上 `browser.waitForAngularEnabled(false)`,就解決ㄌ * Q: 為什麼加上waitForAngularEnabled(false)就可以了。 * A: Protractor會自動等待angular的$http $timeout事件,所以如果有使用poll 輪流詢問的方式api就必須使用,不然就會一直等到超時。[API文件](http://www.protractortest.org/#/api?view=ProtractorBrowser.prototype.waitForAngularEnabled) #### ==⚡Cannot read property 'urlIs' of undefined== 1. 我先印出protractor.ExpectedConditions,發現他是undefined,因此我就把上面的import刪掉,想重新導入。 2. 重新導入的時候發現我導入的路徑是proreactor/built/ptor,所以我就重新選擇"從protractor將protractor新增至現有的匯入宣告",就可以正常使用了。 ![protractor import](https://i.imgur.com/3epzWlL.png) 3. 也可以自己手動改成protractor。 ```typescript // import { protractor } from 'protractor/built/ptor'; import { protractor } from 'protractor'; ``` ## 參考資料 ### 基本概念 * 自動化測試程式架構需要符合 [Soild五原則](https://medium.com/jastzeonic/soild-%E4%BA%94%E5%8E%9F%E5%89%87%E9%82%A3%E4%B8%80%E5%85%A9%E4%BB%B6%E4%BA%8B%E6%83%85-4410b72e37f3) => ==理解、修改、維護== * [一次搞懂單元測試、整合測試、端對端測試之間的差異](https://blog.miniasp.com/post/2019/02/18/Unit-testing-Integration-testing-e2e-testing) [name=Will 保哥] ### 教學文章 * [Protractor 快速指南](https://dwatow.github.io/2019/01-26-angularjs/angularjs-e2e-protractor/) * [使用 Protractor 進行基本 Angular e2e Test](https://tpu.thinkpower.com.tw/tpu/articleDetails/1821) * [Protractor - 實戰篇 - Config File](https://zh-tw.facebook.com/notes/paul-li/protractor-%E5%AF%A6%E6%88%B0%E7%AF%87-config-file/10152950568012211/) ### 教學影片 * [Protractor Beginner Tutorial](https://www.youtube.com/watch?v=p8ENoeZENhk&list=PLhW3qG5bs-L_dgIr3hiOlnNIO8NGlXQnP&index=1) ### Protractor語法 * [讀書會-Jasmine&Protractor](https://www.youtube.com/watch?v=7PvkoPzOBks&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=42) * [IT幫幫-Jasmine&Protractor](https://ithelp.ithome.com.tw/articles/10219702) ### 課程教材大綱 ![Angular 自動化測試實戰:使用 Protractor 實現 E2E 測試](https://i.imgur.com/S8lnRn9.png) * 建立正確的 Angular 測試觀念 單元測試 整合測試 E2E 測試 * 認識前端測試框架與執行器 Karma 介紹 Jasmine 介紹 * Protractor 基本觀念 認識 Protractor 工具 簡介 Protractor 運作機制 * Protractor 快速上手 撰寫第一個 E2E 測試 認識 Control Flow 改用 async/await 非同步寫法 Protractor 背後的運作機制 認識 NgZone 與 Protractor * Protractor 實戰演練 認識 ElementFinder 與 Locator 各式 DOM 操作技巧 (表單、檔案上傳、對話方塊) 視窗之間的切換 測試流程注入 JavaScript 善用 ExpectedConditions APIs 畫面的呈現測試 * Protractor 偵錯技巧 使用 Chrome Inspector VS Code 的偵錯模式 例外訊息的判斷技巧 正確處裡各種 Timeout 情境 * Protractor config.js 的運用 瀏覽器的設定 微調 Chrome 啟動與執行設定 同時執行不同瀏覽器 (Chrome, Firefox, IE, Mobile) 正確的方式建立多個 config.js 分類測試 (建立 Suites) 設定 Selenium Server 截圖技巧 * Style Guide 與 Action Helper 實作 Page Object 設計樣式 Action Helper 介紹