﻿# 如何將 Protractor 設定檔 (protractor.conf.js) 加入 TypeScript 型別檢查

現在日常的 JavaScript 已經完全離不開 [TypeScript](https://www.typescriptlang.org/) 了，在強型別的保護下，不但不容易寫出錯誤的代碼，還能透過開發工具提供的 IntelliSense 提高開發速度。但是如果手邊的程式碼只能使用 JS 來撰寫，那該怎麼辦呢？像是 [Protractor](https://www.protractortest.org/) 的設定檔 `protractor.conf.js` 就是一個典型的例子，本篇文章我將分享如何在現有 JavaScript 檔案下使用 TypeScript 的型別檢查提示。

### 從一個最簡單的 Angular CLI 專案開始

我先示範 Protractor 手動執行的過程：

1.  建立全新 Angular 專案
    
    ```bash
    ng new demo1 --routing --style css
    cd demo1
    ```
    
2.  先更新 WebDriver 的驅動程式
    
    ```bash
    npx webdriver-manager update
    ```
    
3.  啟動 Angular Live Development Server 開發伺服器
    
    ```bash
    npm start
    ```
    
4.  最後則是啟動 Protractor 程式，執行預設的 E2E 測試
    
    ```bash
    npx protractor .\e2e\protractor.conf.js
    ```
    

上述步驟，除了第一步是建立 Angular 專案外，其餘的 2, 3, 4 步，其實可以透過一個命令來完成，那就是：`npm run e2e` 或 `ng e2e` 即可包含以上最後三個步驟。如果可以自動完成 3 個步驟，為什麼還要寫出手動執行的步驟呢？原因只有一個：**效率**！

你可以想想，如果每次執行 `ng e2e` 都要等他 Build/Update/Run 不是很浪費時間嗎？所以建議你調整一下 `package.json` 的 `scripts` 設定，將 `e2e` 項目內容從 `ng e2e` 改為 `ng e2e --dev-server-target= --webdriver-update=false`，如此一來，下次你要執行 `npm run e2e` 的時候，就不會自動啟動 `npm start` 與更新 WebDriver 驅動程式了！(不過前提是你要自己先手動更新一遍)

```bash
ng e2e --dev-server-target= --webdriver-update=false
```

### 關於 `protractor.conf.js` 設定檔

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/55b1161f3628619a606a-image_3184.png)

我們從上述手動步驟就可以看出 Protractor 其實會先載入一個 `protractor.conf.js` 設定檔，然後依據設定檔的內容，決定 Protractor 要載入哪些 `*.e2e-spec.ts` 檔案？執行哪個瀏覽器？使用什麼測試框架？等等！

透過 Angular CLI 建立的專案中，所有 Protractor 的 E2E 測試檔 (`./src/**/*.e2e-spec.ts`)，都已經調整為 TypeScript 檔案了，所以撰寫 E2E 測試的過程，可以享用 TypeScript 帶來的好處。即便如此，唯讀一個 JS 檔案沒有用到 TypeScript 型別輔助，那就是 `protractor.conf.js` 設定檔自己！

上網查找資料，你會看見有人硬把 `protractor.conf.js` 設定檔改為 `protractor.conf.ts` (只改副檔名)，所以在執行 Protractor 之前，要先透過 `tsc protractor.conf.js` 才能開始跑 Protractor，我覺得這個解決方案太髒了，不太理想，我則是選用另一種途徑。

### 對現有的 JavaScript 檔案進行型別檢查

我們都知道學習 TypeScript 是有一點進入門檻的，畢竟從一個「弱型別」的 JavaScript，換成一個「強型別」的 TypeScript，除了要學習一些新語法之外，還要熟悉一些相關工具，否則就會變得綁走綁腳，覺得不實用。

因此，TypeScript 設計出一種機制，可以讓你不改變 JavaScript 原有程式的情況下，立刻開始使用「強型別」來開發程式！

> 你可以從 [Type Checking JavaScript Files](https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files) 看見完整的說明！

以下是將 `protractor.conf.js` 設定檔調整為「部分強型別」的步驟：

1.  請在該檔案的第一行加入以下註解，加入後該檔案就進入了 TypeScript 的世界！
    
    ```javascript
    // @ts-check
    ```
    
    設定完成之後，只要有型別錯誤出現，在 VSCode 中就會立刻發現！如果有**未知的型別**，也會自動標示為 `any` 任意型別，所以對開發上完全沒影響，也不會因為缺乏型別定義而顯示過多的錯誤訊息！
    
    我認為這種狀態應該是最適合初學者入門的設定方法！
    
2.  接著將 `exports.config` 這個變數宣告為 `Config` 型別
    
    一樣是透過加入註解的方式進行宣告，因此不影響原有程式的任何行為！
    
    ```typescript
    /**
     * @type { import("protractor").Config }
     */
    exports.config = {
    ```
    
    此時，整份檔案的設定已經是完全強型別了，所有已知的屬性，都能享有 **型別檢查** (Type Checking) 與 **IntelliSense** 功能。
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5ead0d0de6a9be1f77cb-image_3185.png)
    
    除此之外，當你把滑鼠移動到任何一個 **屬性** (Property) 上面，還會出現完整的文件，實在相當便利！
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f74e7d548db2806fab23-image_3186.png)
    
3.  在 `onPrepare()` 加入 `browser.waitForAngularEnabled(false);`
    
    因為 Protractor 是為 Angular 量身打造的工具之一，預設的情況下，當你進入某個頁面後，Protractor 會先尋找該頁面是否有 AngularJS 或 Angular 應用程式在執行中，如果有找到的話，才會繼續執行。
    
    不過，當你想測試的網站/網頁不是用 Angular 所開發的時候，就必須加入這段程式碼。如果你想要測試的網站完全不是 Angular 所開發的，那你很有可能就會在 `onPrepare()` 加入 `browser.waitForAngularEnabled(false);` 程式碼！但加入之後，就會立刻出現 TypeScript 的型別提示：
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/47fec81ec3c1e47119f7-image_3187.png)
    
    就一個相當基本的常識，尚未宣告的變數 `browser` 直接使用，原本就是程式碼的壞味道，這時出現提示其實也是相當合理的。但是對 Protractor 來說，他是跑在 Node.js 底下的，這裡的 `browser` 其實是一個全域變數 (其實是 `global` 全域變數下的一個 `browser` 屬性)。
    
    這個時候，你就必須在相同的函式範圍內，額外用 `var` / `let` 或 `const` 宣告一個 `browser` 區域變數。不過，直接宣告變數是不行的，因為宣告下去之後，在此範圍內存取該變數，就會得到 `undefined` 的值。因此，你應該用以下程式碼片段來宣告，才是正確用法：
    
    ```typescript
    const browser = global['browser'];
    ```
    
    加入之後的變數，其型別為 `any`：
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2e0eab36ae4342e6f72b-image_3188.png)
    
    比較好的做法，當然是把 browser 變成具有型別的變數，我們可以用以下程式碼片段來宣告正確的型別：
    
    ```typescript
    /**
     * @type { import("protractor").ProtractorBrowser }
     */
    const browser = global['browser'];
    ```
    
    如此一來，整份 `protractor.conf.js` 設定檔就都是具有型別的變數了，是不是很棒呢！
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/73e7d1155f39ab0f407e-image_3189.png)
    

### 相關連結

-   [Type Checking JavaScript Files · Microsoft/TypeScript Wiki](https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files)
-   [Type Safe JavaScript with JSDoc – TruckJS – Medium](https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76)
-   [How to define types in protractor.conf.js with @ts-check enabled - Stack Overflow](https://stackoverflow.com/questions/55200176/how-to-define-types-in-protractor-conf-js-with-ts-check-enabled)
-   [JSDoc support in JavaScript · Microsoft/TypeScript Wiki](https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript)
-   [protractor/exampleTypescript at master · angular/protractor](https://github.com/angular/protractor/tree/master/exampleTypescript)
-   [How to define types in protractor.conf.js with @ts-check enabled - Stack Overflow](https://stackoverflow.com/questions/55200176/how-to-define-types-in-protractor-conf-js-with-ts-check-enabled)
-   **[https://github.com/angular/protractor/blob/master/lib/config.ts](https://github.com/angular/protractor/blob/master/lib/config.ts)**
-   **[feat(@schematics/angular): add type checking for protractor.conf.js file #13957](https://github.com/angular/angular-cli/pull/13957)**
