###### 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。
```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新增至現有的匯入宣告",就可以正常使用了。

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 測試觀念
單元測試
整合測試
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 介紹