Nightwatch101 #11:BDD Assert

Nightwatch.js

Nightwatch 斷言庫(Assertion Library)除了提供上一篇提到的 Expect 外,還有 Assert 和 Verify。在 Nightwatch 中,.assert.verify 的 library 基本上是做相同的事情,差別只在於斷言失敗時的處理方式。

像是這樣…

.assert:斷言失敗時,測試結束,忽略剩餘未執行的部份。

BDD Assert

.verify:斷言失敗時,會把測試失敗記綠下來,然後繼續剩餘未執行的部份。

BDD Verify

本篇先來看 Assert。

♡(´∀`)人(´∀`)♡

本系列文章皆使用這個專案,可以拉下來玩玩;有什麼問題都可以提出 issue


語法介紹

.attributeContains()

檢視屬性(attribute)是否 包含 指定的值,若否則顯示客製化訊息(optional)。

例如:檢視 .link 這個 DOM element 的屬性 href 的值是否包含 sample.com,若否則顯示客製化訊息「連結錯誤」。

browser.assert.attributeContains('.link', 'href', 'sample.com', '連結錯誤');

回顧 Expect 的語法。

browser.expect.element('.link').to.have.attribute('href').which.contains('sample.com');

.attributeEquals()

檢視屬性(attribute)是否 等於 指定的值,若否則顯示客製化訊息(optional)。

例如:檢視 .link 這個 DOM element 的屬性 href 的值是否為 sample.com,若否則顯示客製化訊息「連結錯誤」。

browser.assert.attributeEquals('.link', 'href', 'sample.com', '連結錯誤');

回顧 Expect 的語法。

browser.expect.element('.link').to.have.attribute('href').equals('sample.com');

.containsText()

檢視 DOM element 的 inner text 是否包含某字串,若否則顯示客製化訊息(optional)。

例如:檢視 #text 這個 DOM element 的 inner text 的值是否為 Hello World!

browser.assert.containsText('#text', 'Hello World!');

回顧 Expect 的語法。

browser.expect.element('#text').text.to.contain('Hello World!');

.cssClassPresent()

檢視 DOM element 是否含有某個 CSS class,若否則顯示客製化訊息(optional)。

例如:檢視 #wrap 這個 DOM element 的 CSS class 是否含有 .container 這個 class,若否則顯示客製化錯誤訊息「使用的 class 有誤」。

browser.assert.cssClassPresent('#wrap', 'container', '使用的 class 有誤');

回顧 Expect 的語法。

browser.expect.element('#wrap').to.have.attribute('class').which.contains('container');

.cssClassNotPresent()

檢視 DOM element 是否沒有某個 CSS class,與 cssClassPresent 相反。

例如:檢視 #wrap 這個 DOM element 的 CSS class 不是否含有 .container 這個 class。

browser.assert.cssClassNotPresent('#wrap', 'container');

回顧 Expect 的語法。

browser.expect.element('#wrap').not.to.have.attribute('class').which.contains('container');

.cssProperty()

檢視 DOM element 的 CSS 屬性是否含有某個值,若否則顯示客製化訊息(optional)。

例如:檢視 #text 這個 DOM element 的 CSS 屬性 display 的值是否為 block

browser.assert.cssProperty('#text', 'display', 'block');

回顧 Expect 的語法。

browser.expect.element('#text').to.have.css('display').which.equals('block');

.elementPresent()

檢視 DOM element 是否存在,若否則顯示客製化訊息(optional)。

例如:檢視 #text 這個 DOM element 是否存在。

browser.assert.elementPresent('#text');

回顧 Expect 的語法。

browser.expect.element('#text').to.be.present;

.elementNotPresent()

檢視 DOM element 是否不存在,與 elementPresent 相反。

例如:檢視 #text 這個 DOM element 是否不存在。

browser.assert.elementNotPresent('#text');

回顧 Expect 的語法。

browser.expect.element('#text').to.not.be.present;

.visible()

檢視 DOM element 是否可見,若否則顯示客製化訊息(optional)。與 hidden 相反。

例如:檢視 .this_element_should_be_visible 這個 DOM element 是否存在。

browser.assert.visible('.this_element_should_be_visible');

回顧 Expect 的語法。

browser.expect.element('.this_element_should_be_visible').to.be.visible;

.hidden()

檢視 DOM element 是否隱藏(即不可見),若否則顯示客製化訊息(optional)。與 visible 相反。

例如:檢視 .this_element_hould_not_be_visible 這個 DOM element 是否隱藏。

browser.assert.hidden('.this_element_hould_not_be_visible');

回顧 Expect 的語法。

browser.expect.element('.this_element_should_be_visible').not.to.be.visible;

.title()

檢視當頁的 title 是否等於特定字串,若否則顯示客製化訊息(optional)。

例如:檢視當頁的 title 是否為「露天拍賣」。

browser.assert.title('露天拍賣');

.urlContains()

檢視目前網址是否 包含 特定字串,若否則顯示客製化訊息(optional)。

例如:檢視目前網址是否包含「s000.php」。

browser.assert.urlContains('s000.php');

.urlEquals()

檢視目前網址是否 等於 特定字串,若否則顯示客製化訊息(optional)。

例如:檢視目前網址是否等於「s000.php」。

browser.assert.urlEquals('s000.php');

.value()

檢視目前表單元件的值是否 等於 預期的值,若否則顯示客製化訊息(optional)。

例如:檢視 .account 這個 DOM element 的值是否等於「hello_kitty」。

browser.assert.value('.account', 'hello_kitty');

回顧 Expect 的語法。

browser.expect.element('.account').to.have.value.that.equals('hello_kitty');

.valueContains()

檢視目前表單元件的值是否 包含 預期的值,若否則顯示客製化訊息(optional)。

例如:檢視 .account 這個 DOM element 的值是否包含「hello_kitty」。

browser.assert.valueContains('.account', 'hello_kitty');

回顧 Expect 的語法。

browser.expect.element('.account').to.have.value.that.contains('hello_kitty');

範例

檢視露天拍賣的頂層分類頁,會檢視當前網址、標題、特定元素的屬性值、元素是否存在、元素是否可見、表單元件輸入值等。

範例程式碼。

module.exports = {
  'Assert Categoty Advertisements': browser => {
    browser
      // 打開指定網址
      .url('http://class.ruten.com.tw/category/main?0008')

      // 等待 `<body>` 這個元素出現
      .waitForElementVisible('body')

      // 檢視網頁標題是否為「相機、攝影機 - 露天拍賣」
      .assert.title('相機、攝影機 - 露天拍賣')

      // 檢視 .rt-header 這個 DOM element 的屬性 data-module 其值是否包含 rt-header
      .assert.attributeEquals('.rt-header', 'data-module', 'rt-header')

      // 檢視 .rt-wrap-search 這個 DOM element 的屬性 data-module 其值是否包含 page-class-main
      .assert.attributeEquals('.rt-wrap-search', 'data-module', 'page-class-main')

      // 檢視 .rt-ad-text-only .rt-ad-item:nth-child(1) 這個 DOM element 是否存在
      .assert.elementPresent('.rt-ad-text-only .rt-ad-item:nth-child(1)')

      // 檢視 .promo-bar 這個 DOM element 是否存在
      .assert.elementPresent('.promo-bar')

      // 檢視 .rt-subcategory-list .rt-subcategory-item 這個 DOM element 是否存在
      .assert.elementPresent('.rt-subcategory-list .rt-subcategory-item')

      // 檢視 #ad-flash 這個 DOM element 是否可見
      .assert.visible('#ad-flash')

      // 檢視 #ad-flash .rt-ad-item 這個 DOM element 是否存在
      .assert.elementPresent('#ad-flash .rt-ad-item')

      // 檢視 .rt-flagship 這個 DOM element 是否存在
      .assert.elementPresent('.rt-flagship')

      // 檢視 .rt-flagship 這個 DOM element 的屬性 data-module 其值是否等於 class-main-flagship
      .assert.attributeEquals('.rt-flagship', 'data-module', 'class-main-flagship')

      // 檢視 .rt-flagship .rt-ad-item 這個 DOM element 是否存在
      .assert.elementPresent('.rt-flagship .rt-ad-item')

      // 檢視 .rt-flagship .rt-ad-item:nth-child(1) 這個 DOM element 是否存在
      .assert.elementPresent('.rt-flagship .rt-ad-item:nth-child(1)')

      // 檢視 #ad-promote .promoted-item:nth-child(4) 這個 DOM element 是否存在
      .assert.elementPresent('#ad-promote .promoted-item:nth-child(4)')

      // 檢視 #ad-special .special-item:nth-child(3) 這個 DOM element 是否存在
      .assert.elementPresent('#ad-special .special-item:nth-child(3)')

      // 檢視 .hot-sale-gallery 這個 DOM element 的屬性 data-module 其值是否等於 class-main-slideshow
      .assert.attributeEquals('#ad-slideshow', 'data-module', 'class-main-slideshow')

      // 檢視 .hot-sale-gallery 這個 DOM element 的屬性 data-module 其值是否等於 class-main-gallery
      .assert.attributeEquals('.hot-sale-gallery', 'data-module', 'class-main-gallery')

      // 檢視 .hot-sale-item:nth-child(2) 這個 DOM element 是否存在
      .assert.elementPresent('.hot-sale-item:nth-child(2)')

      // 檢視 #ad-gallery .hot-sale-gallery-item 這個 DOM element 是否存在
      .assert.elementPresent('#ad-gallery .hot-sale-gallery-item')

      // 檢視 .good-shops 這個 DOM element 是否可見
      .assert.visible('.good-shops')

      // 檢視 .good-shops .rt-ad-control-item:nth-child(1) .rt-ad-control-link 這個 DOM element 的屬性 href 其值是否包含指定字串,在這裡是放網址
      .assert.attributeContains('.good-shops .rt-ad-control-item:nth-child(1) .rt-ad-control-link', 'href', 'https://point.ruten.com.tw/ADI/ap2AD/more.php?CM08')

      // 檢視 .good-shops .rt-ad-control-item:nth-child(2) .rt-ad-control-link 這個 DOM element 的屬性 href 其值是否包含指定字串,在這裡是放網址
      .assert.attributeContains('.good-shops .rt-ad-control-item:nth-child(2) .rt-ad-control-link', 'href', 'http://pub.ruten.com.tw/adb/ad01_intro.html')

      // 檢視 .good-shops .rt-ad-item 這個 DOM element 是否可見
      .assert.visible('.good-shops .rt-ad-item')

      // 檢視 #ad-featured-list .rt-ad-item 這個 DOM element 是否可見
      .assert.visible('#ad-featured-list .rt-ad-item')

      // 檢視 .top-sell 這個 DOM element 是否存在
      .assert.elementPresent('.top-sell')

      // 檢視 .top-sell .rt-ad-item 這個 DOM element 是否存在
      .assert.elementPresent('.top-sell .rt-ad-item')

      // 檢視 .shopping-mall 這個 DOM element 是否存在
      .assert.elementPresent('.shopping-mall')

      // 檢視 .shopping-mall .rt-ad-ite 這個 DOM element 是否存在
      .assert.elementPresent('.shopping-mall .rt-ad-item')

      // 檢視 .shopping-mall .rt-ad-control-item:nth-child(1) .rt-ad-control-link 這個 DOM element 的屬性 href 其值是否包含指定字串,在這裡是放網址
      .assert.attributeContains('.shopping-mall .rt-ad-control-item:nth-child(1) .rt-ad-control-link', 'href', 'https://point.ruten.com.tw/ADI/ap2AD/more.php?CS08')

      // 檢視 .shopping-mall .rt-ad-control-item:nth-child(2) .rt-ad-control-link 這個 DOM element 的屬性 href 其值是否包含指定字串,在這裡是放網址
      .assert.attributeContains('.shopping-mall .rt-ad-control-item:nth-child(2) .rt-ad-control-link', 'href', 'pub.ruten.com.tw/adb/ad02_intro.html')

      // 檢視 .rt-ad-search-keyword 這個 DOM element 是否存在
      .assert.elementPresent('.rt-ad-search-keyword')

      // 檢視 .rt-ad-search-keyword .rt-ad-item 這個 DOM element 是否存在
      .assert.elementPresent('.rt-ad-search-keyword .rt-ad-item')

      // 檢視 #search_input 這個 DOM element 其值是否為空字串
      .assert.value('#search_input', '')

      // 對 #search_input 這個 DOM element 鍵入字串「蛋糕」
      .setValue('#search_input', '蛋糕', () => {
        // 點擊送出按鈕 .rt-site-search-submit
        browser.click('.rt-site-search-submit', () => {
          // 檢視網址是否包含 s000.php
          browser.assert.urlContains('s000.php');
        });
      })

      // 結束 session,關閉瀏覽器
      .end();
  }
};

看完整範例

這裡為了檢視元素個數而去檢查最後一個元素是否存在或可見,之後會改用客製化斷言指令,針對抓到的元素個數來判斷是否等於預期個數。

// 檢視 #ad-promote .promoted-item:nth-child(4) 這個 DOM element 是否存在
.assert.elementPresent('#ad-promote .promoted-item:nth-child(4)')

執行範例。

nightwatch test/e2e/class/testMainCategoryAssert.js

BDD Assert

話說工程師都愛模組化,不要擔心亂糟糟,待之後使用 Page Objects 來改寫 (*´∀`)~♥

Expect vs Assert

基本上 Expect 和 Assert 都可做到相同的事情,對我而言最大的差異就是可否串起來使用。

各位大大如果有任何建議回饋都歡迎提供 (✪ω✪)

備註

Node.js Assert Module

由於 Nightwatch 是使用 Node.js Assert Module,因此不限於此 Nightwatch 文件中提到的指令,只要 Node.js Assert Module 上可用的 method 都可以使用在撰寫測試程式上面喔!

自動重試失敗的斷言

使用 retryAssertionTimeout 設定自動重試失敗斷言的時間,單位是 ms(millisecond)。例如:在 nightwatch.conf.js 中 可以這樣設定…

"test_settings": {
  "default": {
    "globals": {
      "retryAssertionTimeout ": 2000 // 自動重試失敗的斷言的時間
    }
  }
}

下一篇來看 BDD Verify。


2018 鐵人賽網址


Nightwatch Nightwatch101 Selenium End-to-End Testing 端對端測試 鐵人賽 2018鐵人賽 自動化測試 Chai BDD Nightwatch101 2018 iT 邦幫忙鐵人賽 系列文