[測試] e2e 的測試方法 Day 1

上一篇解釋了虛擬碼的概念,這次直接進入撰寫測試的方法。當然,需要一點工具來輔助我們,這裡我們使用 nightwatch.js 來當作測試的框架。

如果不是使用 nightwatch.js 的人,可以左轉沒關係。


環境

萬事起頭難,建立環境這件事情最難! 雖然我們使用 nightwatch.js 來當做測試框架,但是我們還是需要具備一些基礎知識,好方便我們操作。首先,我們需要的有,

  1. 可以運行 NodeJS 的環境
  2. 可能需要安裝 Java SDK
  3. selenium-server-standalone 獨立執行檔 (*1)
  4. Firefox driver

接著,我們這邊可以來建立一個相對簡易的測試環境,我們需要的目錄與檔案結構如下,

  1. specs
  2. bin
  3. package.json
  4. nightwatch.conf.js
  5. runner.js

我們可以先下載上述的獨立執行檔,把他放到 bin 目錄底下備用。另外,如果你需要使用 Firefox 來運行測試環境,也請把 Firefox driver 放到 bin 目錄底下。

底下的設定檔都可以照抄沒關係,更詳細的設定可以察看 nightwatch.js,或是上網 Google 也可以(欸

首先 package.json 會寫入我們所需要安裝的一些套件,

{
  "scripts": {
    "test": "node runner.js"
  },
  "dependencies": {
    "babel-register": "^6.22.0",
    "chromedriver": "^2.27.2",
    "cross-env": "^3.1.4",
    "cross-spawn": "^5.0.1",
    "lodash": "^4.17.4",
    "mocha": "^3.2.0",
    "nightwatch": "^0.9.12",
    "selenium-server": "^3.0.1"
  },
  "engines": {
    "node": ">= 4.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

接著 runner.js 提供啟動測試的程序,

process.env.NODE_ENV = 'staging'

var opts = process.argv.slice(2)

if (opts.indexOf('--config') === -1) {
  opts = opts.concat(['--config', 'nightwatch.conf.js'])
}

if (opts.indexOf('--env') === -1) {
  opts = opts.concat(['--env', 'chrome'])
}

var spawn = require('cross-spawn')
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })

runner.on('exit', function (code) {
  process.exit(code)
})

runner.on('error', function (err) {
  throw err
})

暫停,我們先在終端機執行一些套件的安裝,

npm run install

安裝好之後,我們來編輯一下 nightwatch.conf.js 這個設定檔,可以參考官方的 簡單範例 來進行一些修改,

{
  "src_folders" : ["specs"],
  "output_folder" : "reports",
  "custom_commands_path" : "",
  "custom_assertions_path" : "",
  "page_objects_path" : "",
  "globals_path" : "",

  "selenium" : {
    "start_process" : true,
    // "server_path" : require('selenium-server').path,
    "server_path" : "./bin/selenium-server-standalone-3.8.1.jar",
    "log_path" : "",
    "port" : 4444,
    "cli_args" : {
      "webdriver.chrome.driver" : require('chromedriver').path,
      "webdriver.gecko.driver" : "./bin/geckodriver"
    }
  },

  "test_settings" : {
    "default" : {
      "launch_url" : "http://localhost",
      "selenium_port"  : 4444,
      "selenium_host"  : "localhost",
      "silent": true,
      "screenshots" : {
        "enabled" : false,
        "path" : ""
      }
    },

    "chrome" : {
      "desiredCapabilities": {
        "browserName": "chrome"
      }
    },
    
    "firefox" : {
      "desiredCapabilities": {
        "browserName": "firefox"
      }
    }
  }
}

然後我們就能進入 specs 當中開始寫測試了。

*1 獨立執行檔僅只是官方建議,其實也可以使用 package.json 當中的 selenium-server 套件來執行(例如上述註解掉的那一段)。

開始寫測試之前

畢竟是叫做 End-to-End 測試,所以必須還是要有一些基本的能力,

  1. 理解 HTML / DOM
  2. 理解 CSS 選擇器
  3. 理解 JavaScript
  4. 會一些簡易的 Chrome DevTools 操作
  5. 翻閱大量的 nightwatch.js 的 API 官方文件

所以,在這個大前提之下,你必須對於 DOM 有一定的認知,才能知道你要怎麼做。我們先回到上一次所說的虛擬碼,要把虛擬碼轉換成測試用的程式碼之前,先思考一下該怎麼認識 畫面上的東西

這次就不說販賣機了,就說 Google 的首頁好了。假設我們有一段虛擬碼是這個樣子的,

  1. 打開 https://google.com
  2. 輸入搜尋的關鍵字「閃光洽」
  3. 按下「搜尋」按鈕
  4. 找到第一筆搜尋結果
  5. 第一筆搜尋結果要包含「閃光洽」的字樣

以上的動作都是透過滑鼠與鍵盤,在你的瀏覽器中操作的步驟。那麼,我們該怎麼透過測試方法,自動幫我操作?我們可以理解的方式是,

  1. 叫同事幫我操作(不是

由於網頁是由一大堆 HTML 標籤所組成,他組合出來的叫做 DOM (文件物件模型,Document Object Model)。所以,我們需要的能力是 查找 DOM 來找到我們想要的 物件

以上述的例子來說,輸入關鍵字 這件事情,就會有一下的步驟,

  1. 先在 DOM 找到 可以輸入關鍵字 的地方
  2. 然後輸入關鍵字「閃光洽」

那麼,我們要怎麼找到 可以輸入關鍵字 的地方?首先,你需要複習一點 CSS 選擇器,例如,

  1. #E ID 選擇器
  2. .E 類別選擇器
  3. E F 子嗣選擇器
  4. [E="F"] 屬性選擇器

那麼,我們可以利用 Chrome DevTools 來簡易的驗證選擇器,與確認我們是否有 選到 我們想要選的東西。我們分成幾個步驟來說明 DevTools 的操作,

  1. 打開 Chrome 後,打開 https://google.com
  2. 在搜尋的輸入框的地方,按下滑鼠右鍵,點選「檢視」

google-dev-tools-1

  1. 這個時候會開出 DevTools 的工具,會出現剛剛你「檢視」的元件

google-dev-tools-2

  1. 我們可以在上面那個區塊任一點一下,按下 Command + F,會出現搜尋框

google-dev-tools-3

  1. 這個地方我們可以用來驗證我們的選擇器,是否可以選我們想要的元件,例如說,我們輸入 input[type="text"]

google-dev-tools-4

  1. 就這麼剛好選到我們要的東西了呢(啾咪

接著我們先略過輸入關鍵字這件事情,假設我們輸入好了,要去按搜尋的按鈕。所以,我們就要找到那個按鈕。我們一樣可以使用 DevTools 來找到那顆按鈕,一樣是在按鈕上「檢視」,來察看那顆按鈕有什麼樣的屬性可以供我們使用。

google-dev-tools-5

以上,我們使用了 CSS 選擇器,並透過 DevTools 來輔助我們選到想要的元件。這樣,對於選擇元件這件事情,在這裡就有一個基本的認識。會選擇元件之後,我們就可以來寫第一次的簡單測試了。

nightwatch.js 簡易測試

specs 資料夾中,我們可以建立一個檔案叫做 test.js 用來寫我們的第一個測試。這個檔案有一個規則,就是必須被包含在 module.exports = { } 裡面,至於為什麼?不要問,很可怕!

module.exports = {
    '我的第一個測試': function (browser) {
        browser
            .url('https://google.com')
            .waitForElementVisible('input[type="text"]', 1000)
            .setValue('input[type="text"]', '閃光洽')
            .click('input[jsaction="sf.chk"]')
            .pause(1000)
            .waitForElementVisible('#rso .g:first-child', 1000)
            .assert.containsText('#rso .g:first-child', '閃光洽')
            .end();
    }
}

我們逐步來說明上述的測試代碼做了什麼事情,

  1. browser 在 nightwatch.js 當中,會回傳一個參數,這個參數就是測試用的瀏覽器物件,所有的動作都會基於這個瀏覽器物件來做操作。
  2. .url('https://google.com') 請這個瀏覽器打開 https://google.com 這個網址
  3. .waitForElementVisible 是 nightwatch.js 提供的一個方法,用意是,等待你指定的選擇器 input[type="text"] 所選到的 元件 出現在畫面上,最久等待 1000 毫秒。
  4. .setValue 顧名思義,是將某個數值填入選擇器,通常會用於 input 類型的標籤,這個方法可以指定一個值給這個元件。例如我們要輸入搜尋的關鍵字,就會使用這個方法。
  5. .click 模擬滑鼠點擊選擇器 input[jsaction="sf.chk"] 所選到的元件 (*2)
  6. .pause 暫停操作 1000 毫秒。為何會需要暫停?因為送出關鍵字之後,要等待回應,等待的時間我們就可以先暫停操作。
  7. .assert.containsText 這是屬於 assert 的一組方法,用意是檢查指定選擇器 #rso .g:first-child所有 文字內容,是否有包含你所指定的文字。
  8. .end() 測試做完了,關閉測試用的瀏覽器。

以上是簡易的測試步驟,所有的相關文件後續會有比較詳細的介紹。當然測試當中可能還是會遇到一些 nightwatch.js 奇怪的雷,我會慢慢找時間分享給大家。

*2 點擊這個動作有比較特別的條件,之後會另外有篇幅來講講這個雷。

小結

不覺得,用 nightwatch.js 來搶票好像不錯嗎(欸

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei