SlideShare una empresa de Scribd logo
1 de 66
Descargar para leer sin conexión
Nightwatch101
手牽手一起來學 Nightwatch!
Agenda
● Nightwatch 與 Selenium Webdriver
● 環境建置
● 設定檔
● 定位網頁元素:CSS Selector 與 Xpath
● Nightwatch Commands
● 斷言:BDD Expect、Assert、Verify
Agenda (cont.)
● Test Hooks
● Nightwatch Test Runner:分組、標籤、禁跑特定測試
● Page Objects
● 客製化指令與斷言
● 客製化測試報告
● 總結
露天拍賣。前端工程師
cythilya@gmail.com
https://cythilya.github.io
@cythilya
Summer
網站又壞了?
結帳折扣金
額有誤?
上架頁
不能改規格
首頁廣告
無法顯示
搜尋結果
出不來 XD
人工測試太辛苦了,
用 Nightwatch 自動處理吧
Nightwatch 簡介
● Nightwatch 與 Selenium Webdriver
● 環境建置
● 設定檔
Nightwatch
Nightwatch 是什麼?
● 網頁專用的自動化測試框架
End-to-End Testing 是什麼?
● 模擬使用者對瀏覽器進行操作,例如:瀏覽網址、輸入文字、點擊按鈕
● 可做 UI 測試、整合測試
Nightwatch 與 Selenium Webdriver
環境建置
● 安裝 Java Development Kit(JDK),版本 7+
● 安裝 Nightwatch
● 下載專案 https://goo.gl/mFHJ2c
● 使用 Test Runner 進行測試
○ nightwatch ./test/e2e/testDemo.js
Nightwatch Test Runner 設定檔
Nightwatch 提供了 Command-line Test Runner,用來跑各種類型的測試
例如:指定測試環境、依群組或標籤或個別檔案
● 設定檔在專案根目錄下
● 預設檔名是 nightwatch.json 或 nightwatch.conf.js(優先)
● 設定檔分為三個部分:基本設定(Page Objects、客製化指令和斷言的位置)、
Selenium Server 相關、測試環境相關
● External Globals:放置複雜的運算或 Plugin
範例
Nightwatch 指令與斷言
● 定位網頁元素:CSS Selector 與 Xpath
● 常用指令
● 斷言:BDD Expect、Assert、Verify
定位網頁元素
定位網頁元素有兩種方法
● CSS Selector
● Xpath
使用 CSS Selector 定位網頁元素
.waitForElementVisible('body', 1000) //等待 1 秒,確認 <body> 是否出現
.isVisible('.header') //確認 ".header" 是否可見
.getText('.link') //取得 ".link" 內的文字
.setValue('.input', 'Pusheen') //".input" 欄位輸入 "Pusheen"
使用 Xpath 定位網頁元素
● 設定檔 use_xpath: true 可設定使用 Xpath 為預設選取策略
● 切換使用 CSS Selector 或 Xpath
.useXpath() //使用 Xpath 來抓取網頁元素
.useCss() //使用 CSS Selector 來抓取網頁元素
//在帳號欄位輸入字串 nightwatch101
.setValue('//input[@id="userid"]', 'nightwatch101')
//在密碼欄位輸入字串 nightwatch101
.setValue('//input[@id="userpass"]', 'nightwatch101')
Nightwatch Commands
url, waitForElementPresent, setValue, click, pause, end
'Ruten Desktop Login': browser => {
browser
.url('https://member.ruten.com.tw/user/login.htm') // 打開指定網址
.waitForElementPresent('body') // 存在? 確認 DOM Element 載入完成
.setValue('#userid', 'nightwatch101') // 對 input text 鍵入字串
.setValue('#userpass', '*************')
.click('#btnLogin') // 點擊送出按鈕
.pause(1000) // 暫停測試程式,可指定暫停時間( ms)
.end(); // 結束 session,關閉瀏覽器
}
nightwatch test/e2e/member/testDesktopLogin.js
Nightwatch Commands (cont.)
waitForElementVisible, getTitle, isVisible
'Demo Ruten SubCategory Page': browser => {
browser
.url('http://class.ruten.com.tw/category/sub00.php?c=00080001')
.waitForElementVisible('body') // <body> 可見?
.getTitle(function(title) { // 取得網頁標題
// 標題為"DC數位相機 - 露天拍賣"?
this.assert.equal(title, 'DC數位相機 - 露天拍賣');
})
.isVisible('.header') // 確認 .header 可見?
Nightwatch Commands (cont.)
getAttribute, getTagName, getText
.getAttribute('#search_input', 'name', function(result) {
// 取得元素 #search_input 的屬性 name 的資料,並比對其值是否為 "k"
this.assert.equal(result.value, 'k');
})
.getTagName('#search_input', function(result) {
// 取得元素 #search_input 的 tag name 是否為 "input"
this.assert.equal(result.value, 'input');
})
.getText('.button', result => {
// 取得元素 .button 的文字,並比對是否為 "搜尋"
this.assert.equal(result.value, '搜尋')
})
Nightwatch Commands (cont.)
getCssProperty, getElementSize
.getCssProperty('#search_input', 'line-height', function(result) {
// 取得 #search_input 的 CSS line-height 的值,並比對是否為 "27px"
this.assert.equal(result.value, '27px');
})
.getElementSize('.submit', result => {
// 取得元素 .submit 的寬高
// 比對其寬是否為 75px,其高是否為 27px
this.assert.equal(result.value.width, 75);
this.assert.equal(result.value.height, 27);
})
Nightwatch Commands (cont.)
clearValue, getValue
.clearValue('#search_input') // 清除 #search_input 的值
.setValue('#search_input', 'Pusheen') // 輸入 "Pusheen"
.getValue("#search_input", function(result) {
// 取得 #search_input 欄位值,並比對其值是否為 "Pusheen"
this.assert.equal(result.value, 'Pusheen');
})
.click('.submit') // 點擊送出按鈕
.end() // 結束 session,關閉瀏覽器
}
nightwatch test/e2e/class/testSubCategory.js
Nightwatch Commands (cont.)
saveScreenshot
'Test Save Screenshot': browser => {
browser
.rtUrl('www')
.maximizeWindow() //展開到螢幕到最大寬度
.saveScreenshot('./screenshots/index.png') //儲存螢幕截圖
.end()
}
nightwatch test/e2e/testSaveScreenshot.js
BDD
BDD(Behavior-Driven Development,行為驅動開發)意即在開發前先撰寫測試程
式,以確保程式碼品質符合驗收規格。除了實作前先寫測試外,還要寫一份「可以
執行的規格」。白話文就是使用者想看到什麼、打開什麼、點到什麼,就這麼寫在測
試程式裡面。
● 進入首頁即可看到一個紅色的按鈕(O)
● 依序點擊按鈕「1」、「+」、「2」、「=」,輸入框裡的值為「3」(O)
● 函式 add(1, 2) 回傳得到 3(X,並非以使用者可執行的角度撰寫規格)
BDD vs TDD
BDD 其實是一種 TDD,最大的差異在於
● BDD:從使用者的角度去思考驗收規格
● TDD:從測試結果去思考程式該如何實作
斷言
● 判斷預期和實際的狀況,不如預期就報錯
● Nightwatch 的斷言有兩種
○ Expect
○ Assert / Verify
Expect
● Nightwatch 的 BDD Expect 是源自於 Chai 的 Expect API
○ expect 比 assert 更有彈性和口語化
○ 只能用於網頁元素的比對
○ 缺點:不能串起來(chain)使用
● Chainable Getters:連接元素和斷言
○ to、be、been、is、that、which、and、has、have、with、at、does、of
browser.expect.element('.heading').text.to.equal('露天旗艦店');
browser.expect.element('.heading').text.be.equal('露天旗艦店');
Expect
text, equals, contains, matches, not
'Test Main Category Page 1': browser => {
browser.url('http://class.ruten.com.tw/category/main?0008');
// 文字內容不為 Hello World?
browser.expect.element('.breadcrumb').text.to.not.equals('Hello World');
// 文字內容包含「攝影機」?
browser.expect.element('.breadcrumb').text.to.contains('攝影機');
// 文字內容不含數字?
browser.expect.element('.breadcrumb').text.to.matches(/^([^0-9]*)$/);
browser.end();
}
nightwatch test/e2e/class/testMainCategorySimpleExpect1.js
Expect
a / an, css, attribute, present, visible
'Test Main Category Page 2': browser => {
browser.url('http://class.ruten.com.tw/category/main?0008');
// #search 是一個 input?
browser.expect.element('#search').to.be.an('input', '#search 必須為 input');
// CSS 屬性 display 的值是 inline-block?
browser.expect.element('.ad-item').css('display').to.equals('inline-block');
browser.expect.element('.rt-ad-link').attribute('href'); // 含有屬性 href?
browser.expect.element('#ad-flash').to.be.visible; // 可見?
browser.expect.element('.shopping-mall').to.be.present; // 存在?
browser.end();
}
nightwatch test/e2e/class/testMainCategorySimpleExpect2.js
Expect
enabled, selected, value
'Test Find Pusheen Page': browser => {
browser.url('https://find.ruten.com.tw...');
browser.expect.element('.payment').to.be.selected; // 選取?
browser.expect.element('.input').to.have.value
.that.equals('pusheen'); // 值為 pusheen?
browser.expect.element('.button').to.be.enabled; // 啟用?
browser.end();
}
nightwatch test/e2e/find/testFindPusheenSimpleExpect.js
Assert
title, containsText, value, valueContains
'Test Main Category Page': browser => {
browser
.url('http://class.ruten.com.tw/category/main?0008')
.assert.title('相機、攝影機 - 露天拍賣') // title 等於特定字串?
.setValue('#search_input', '好吃的蛋糕')
.assert.value('#search_input', '好吃的蛋糕') // 表單元件的值等於「好吃的蛋糕」?
.assert.valueContains('#search_input', '蛋糕') // 表單元件的值包含「蛋糕」?
.assert.containsText('.submit', '再搜尋') // 文字節點內容包含「再搜尋」?
.end();
}
nightwatch test/e2e/class/testMainCategoryAssertSimpleExample1.js
Assert (cont.)
urlEquals, urlContains, visible, elementPresent
'Test Main Category Page': browser => {
browser
.url('http://class.ruten.com.tw/category/main?0008')
// 目前網址等於特定字串?
.assert.urlEquals('http://class.ruten.com.tw/category/main?0008')
.assert.urlContains('/category/main') // 目前網址包含特定字串?
.assert.visible('#ad-flash') // 可見?
.assert.elementPresent('.top-sell') // 存在?
.end();
}
nightwatch test/e2e/class/testMainCategorySimpleExpect2.js
Assert (cont.)
attributeEquals, attributeContains, cssProperty, cssClassPresent
'Test Main Category Page': browser => {
browser
.url('http://class.ruten.com.tw/category/main?0008')
// 屬性 type 為 submit?
.assert.attributeEquals('.submit', 'type', 'submit')
// 檢視 class 包含 button?
.assert.attributeContains('.submit', 'class', 'button')
// CSS 屬性等於指定的值?
.assert.cssProperty('.submit', 'min-height', '24px')
.assert.cssClassPresent('.submit', 'button') // 含有 CSS class?
.end();
}
nightwatch test/e2e/class/testMainCategorySimpleExpect3.js
Visible vs Present
DOM Element 的可見與存在
● visible:可見,必為存在
● hidden:隱藏(display: none / opacity: 0)
● elementPresent:存在
● elementNotPresent:不存在
猜猜看!
'Guess visible or present?': browser => {
browser
.rtUrl('class', 'category/main?0023')
.verify.visible('.header')
.verify.elementPresent('.header')
.verify.hidden('.block')
.verify.elementPresent('.block')
.verify.elementNotPresent('.abcdef')
.end()
}
.header
.block (display: none)
猜猜看!(cont.)
'Guess visible or present?': browser => {
browser
.rtUrl('class', 'category/main?0023')
.verify.visible('.header') // (O) 可見
.verify.elementPresent('.header') // (O) 可見,必為存在
.verify.hidden('.block') // (O) 不可見,隱藏
.verify.elementPresent('.block') // (O) 存在,但隱藏
.verify.elementNotPresent('.abcdef') // (O) 不存在
.end()
}
nightwatch test/e2e/class/testVisibleOrPresent.js
Assert vs Verify
斷言失敗時的處理方式
Assert:忽略剩餘未執行的部份 Verify:繼續未執行的部份
Test Hooks
Test Hooks
before: browser => {
// Test Suite 開始前執行
},
after: browser => {
// Test Suite 結束後執行
},
beforeEach: browser => {
// 在 Test Case 開始前執行
},
afterEach: (browser, done) => {
// 在 Test Case 結束後執行
done();
}
會印出什麼?
module.exports = {
before: browser => {
console.log('abc');
},
after: browser => {
console.log('def');
},
beforeEach: browser => {
console.log('xyz');
},
afterEach: (browser, done) => {
console.log('012');
done();
},
'Test 123': browser => {
// ...
},
'Test 456': browser => {
// ...
}
}
會印出什麼?答案是...
執行這個 Test Suite,結果如下
abc
xyz
012
xyz
012
def
Nightwatch Test Runner
● 分組、標籤、禁跑特定測試
分組測試
分組的方式就是將測試程式碼放進同一個資料夾,群組名稱即資料夾名稱
● 依照分類跑測試
○ nightwatch --group [group_name1,group_name2]
○ 簡寫 nightwatch -g [group_name1,group_name2]
○ EX: nightwatch --group www
● 依照分類忽略測試
○ nightwatch --skipgroup [group_name1,group_name2]
分組測試 (cont.)
tests/e2e
├── class
| ├── testMainCategory.js (O,執行)
| └── testSubCategory.js (O,執行)
├── point
| ├── test1111.js (O,執行)
| └── testHotTopics.js (O,執行)
└── testDemo.js (X,不執行)
執行 nightwatch --group class,point,只會執行 4 個檔案
依標籤測試
Nightwatch 允許開發者使用標籤(tag)標記測試程式
● 依照標籤跑測試
○ nightwatch --tag [tag_name_1] --tag [tag_name_2]
● 依照標籤忽略測試
○ nightwatch --skiptags [tag_name_1,tag_name_2]
標籤的好處是有彈性。
一個 Test Suite 可有多個不同的標籤,不必受限於分類的唯一和垂直特性
依標籤測試 (cont.)
module.exports = {
'@tags': ['campaign', 'point'], // Test Suite 可設定標籤
'Demo Ruten Campaign 1111 Page': browser => {
browser
.url('http://pub.ruten.com.tw/20171111/index.html')
.end()
}
}
依標籤測試 (cont.)
tests/e2e
├── class
| ├── testMainCategory.js (class) (X,不執行)
| └── testSubCategory.js (class) (X,不執行)
├── point
| ├── test1111.js (campaign, point) (O,執行)
| └── testHotTopics.js (point) (O,執行)
└── testDemo.js (index) (X,不執行)
執行 nightwatch --tag point,只會執行 2 個檔案
禁跑特定測試
禁跑特定 Test Suite:設定 @disabled 為 true
module.exports = {
'@disabled': true,
'Demo Google Page': browser => {
browser
.url('https://www.google.com.tw/')
.end()
}
}
禁跑特定測試
禁跑特定 Test Case:在 Test Case 前加上一個空字串
module.exports = {
'sample test': function (client) { // 會跑這個 Test Case
// ...
},
'other sample test': '' + function (client) {
// 加上空字串,禁跑特定 Test Case
}
};
測試程式的模組化
● Page Object
● 客製化指令
● 客製化斷言
Page Objects
const commandList = {
submit: function() { /* ... */ }
}
module.exports = {
url: 'http://sample.com.tw',
commands: [commandList], // 指令
sections: { // 區塊, 作為 Namespacing
someSection: {
selector: '.some-selection',
elements: { // 元素
someElement: {
selector: '.some-element'
}
}
}
}
}
Page Objects (cont.)
module.exports = {
'Find Pusheen': browser => {
const findPage = browser.page.findPage(); // 使用 page object
findPage.navigate()
.assert.title('搜尋結果 : Pusheen - 露天拍賣')
.setValue('@searchbox', 'Pusheen')
.click('@submit');
browser.end();
}
};
nightwatch test/e2e/find/findPusheen.js
客製化指令(Custom Commands)
● 設定檔案路徑
○ 在 nightwatch.conf.js 的 custom_commands_path 設定檔案路徑
● 檔名即指令名稱
● 範例:登入露天桌機版網站
○ nightwatch test/e2e/member/testRutenLogin.js
客製化指令 (cont.)
'Login Ruten Desktop Website': browser => {
browser
.url('https://member.ruten.com.tw/user/login.htm')
.setValue('.user', 'nightwatch101')
.setValue('.password', '*****')
.click('.submit')
.end();
}
客製化指令 (cont.)
exports.command = function(user = 'nightwatch101') {
const accounts = require('../settings.js').accounts;
this
.rtUrl('member', 'login.php')
.setValue('.user', user)
.setValue('.password', accounts[user].password)
.click('.submit')
return this;
};
客製化指令 (cont.)
'Login Ruten Desktop Website': browser => {
browser
.rtLogin() // 乾淨易懂可重用!
.end();
}
客製化斷言(Custom Assertions)
● 用於擴充 Assert 和 Verify
● 檔案路徑:在 nightwatch.conf.js 的 custom_assertions_path 設定檔案路徑
● 檔名即指令名稱
● 格式
○ message:測試報告顯示的訊息( ✓ Testing if the element <div> has count: 13)
○ expected:期待比對的值
○ pass:實際進行斷言的地方
○ value:實際狀況的值,會被 pass 當參數傳入使用
○ command:執行瀏覽器指令的地方,執行結果會當成參數回傳給 value,再執行 pass
客製化斷言 (cont.)
範例:判斷網頁元素數目是否等於預期數量
exports.assertion = function (selector, count) {
this.message = `Testing if the element <${selector}> has count: ${count}`;
this.expected = count;
this.pass = function(val) { return val === this.expected; }
this.value = function(res) { return res; }
this.command = function(cb) {
return this.api.execute(function(selector) {
return document.querySelectorAll(selector).length;
}, [selector], function(res) { cb.call(this, res.value); }.bind(this));
}
}
測試報告 - 難以閱讀的 XML 格式
<?xml version="1.0" encoding="UTF-8" ?>
<testsuites errors="0" failures="0" tests="1">
<testsuite name="class.testMainCategory" errors="0" failures="0" hostname=""
id="" package="class" skipped="0" tests="1" time="20.28" timestamp="Fri, 01 Dec
2017 12:06:52 GMT">
<testcase name="Demo Ruten MainCategory Page"
classname="class.testMainCategory" time="20.28" assertions="0"></testcase>
</testsuite>
</testsuites>
客製化測試報告 - nightwatch-html-reporter
客製化測試報告 - nightwatch-html-reporter
客製化測試報告 (cont.)
HtmlReporter 可設定的選項
● openBrowser:跑完測試後所產生的報告是否使用瀏覽器打開
● reportsDirectory:測試報告的所在路徑
● reportFilename:測試報告的檔名,預設是 generatedReport.html
● uniqueFilename:測試報告是否要加上 timestamp
● separateReportPerSuite:測試報告是否要加上 test suite 的名稱
● themeName:測試報告所使用的主題名稱
● hideSuccess:是否隱藏成功的測試案例,測試報告只顯示錯誤的部
● timestamprelativeScreenshots:是否將截圖的路徑設為相對路徑
範例 - 露天拍賣-桌機版購物車
登入 -> 購買商品 -> 購物車 -> 結帳
● 檢視是否登入露天拍賣,若沒有登入就登入
● 商品頁:點擊按鈕「馬上購買」將商品加入購物車
● 購車頁:點擊按鈕「確定購買」
● 結帳頁:選擇運送方式「面交取貨」、填寫取件人資料
● 回首頁,登出
nightwatch test/e2e/mybid/testShoppingCart.js
希望露天拍賣網站功能都能完善!
總結
● End-to-End Testing vs Unit Testing
● 小叮嚀
● QnA
End-to-End Testing vs Unit Testing
● Unit Testing 的主體是程式碼,是測試程式碼的自身行為,也就是驗證輸入與
輸出是否符合預期。
● End-to-End Testing 的主體是使用者,是一種模擬用戶行為的 UI 測試或進行
流程上的整合測試。
小叮嚀
● Test Suite 無特定執行順序,彼此不可相依
● 不要使用 pause 做等待,改用 waitForElementVisible
○ 因為等待時間可能會因網路速度延遲而導致無法固定等待時間
● 有驗證才知道成功或失敗
○ 單純使用指令 isVisible 而不搭配斷言,無法知道成功或失敗
● 如何優化測試程式?
○ 將常用的功能抽出來成為模組,成為客製化指令和斷言
○ 使用 Page Object 封裝網頁片段,增加重用和可讀性,減少維護的複雜度
QnA
● 寫測試是否會增加額外工時?
個人經驗是增加一倍。
● 除了程式碼的品質保證外,還有什麼好處?
記錄規格、方便估時程。
○ 測試案例如同告知開發者規格的細節和範例,再也不怕同事離職,無人可問。
○ 魔鬼藏在細節裡,測試程式會告訴我們功能有多細;而且網站久了很容易有地雷,這讓我們
記得把踩雷時間估進去 (〒︿〒)

Más contenido relacionado

La actualidad más candente

[社内勉強会]Gradleを使おう
[社内勉強会]Gradleを使おう[社内勉強会]Gradleを使おう
[社内勉強会]Gradleを使おうhirooooo
 
測試是什麼
測試是什麼測試是什麼
測試是什麼Yvonne Yu
 
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化Flutterで単体テストを行う方法とGitHub Actionsを使った自動化
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化Shinnosuke Tokuda
 
決済サービスのSpring Bootのバージョンを2系に上げた話
決済サービスのSpring Bootのバージョンを2系に上げた話決済サービスのSpring Bootのバージョンを2系に上げた話
決済サービスのSpring Bootのバージョンを2系に上げた話Ryosuke Uchitate
 
ClassLoader Leak Patterns
ClassLoader Leak PatternsClassLoader Leak Patterns
ClassLoader Leak Patternsnekop
 
Integration Group - Robot Framework
Integration Group - Robot Framework Integration Group - Robot Framework
Integration Group - Robot Framework OpenDaylight
 
Jvm言語とJava、切っても切れないその関係
Jvm言語とJava、切っても切れないその関係Jvm言語とJava、切っても切れないその関係
Jvm言語とJava、切っても切れないその関係yy yank
 
Chrome Developer Toolsを使いこなそう!
Chrome Developer Toolsを使いこなそう!Chrome Developer Toolsを使いこなそう!
Chrome Developer Toolsを使いこなそう!yoshikawa_t
 
C#でわかる こわくないMonad
C#でわかる こわくないMonadC#でわかる こわくないMonad
C#でわかる こわくないMonadKouji Matsui
 
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるDSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるAtsushi KOMIYA
 
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil Tayar
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil TayarCypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil Tayar
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil TayarApplitools
 
Testing with JUnit 5 and Spring
Testing with JUnit 5 and SpringTesting with JUnit 5 and Spring
Testing with JUnit 5 and SpringVMware Tanzu
 
Web automation using selenium.ppt
Web automation using selenium.pptWeb automation using selenium.ppt
Web automation using selenium.pptAna Sarbescu
 
Tomcatの実装から学ぶクラスローダリーク #渋谷Java
Tomcatの実装から学ぶクラスローダリーク #渋谷JavaTomcatの実装から学ぶクラスローダリーク #渋谷Java
Tomcatの実装から学ぶクラスローダリーク #渋谷JavaNorito Agetsuma
 
Test automation using selenium
Test automation using seleniumTest automation using selenium
Test automation using seleniumshreyas JC
 
ELFの動的リンク
ELFの動的リンクELFの動的リンク
ELFの動的リンク7shi
 
Automation Framework Design
Automation Framework DesignAutomation Framework Design
Automation Framework DesignKunal Saxena
 

La actualidad más candente (20)

[社内勉強会]Gradleを使おう
[社内勉強会]Gradleを使おう[社内勉強会]Gradleを使おう
[社内勉強会]Gradleを使おう
 
測試是什麼
測試是什麼測試是什麼
測試是什麼
 
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化Flutterで単体テストを行う方法とGitHub Actionsを使った自動化
Flutterで単体テストを行う方法とGitHub Actionsを使った自動化
 
決済サービスのSpring Bootのバージョンを2系に上げた話
決済サービスのSpring Bootのバージョンを2系に上げた話決済サービスのSpring Bootのバージョンを2系に上げた話
決済サービスのSpring Bootのバージョンを2系に上げた話
 
ClassLoader Leak Patterns
ClassLoader Leak PatternsClassLoader Leak Patterns
ClassLoader Leak Patterns
 
Integration Group - Robot Framework
Integration Group - Robot Framework Integration Group - Robot Framework
Integration Group - Robot Framework
 
Jvm言語とJava、切っても切れないその関係
Jvm言語とJava、切っても切れないその関係Jvm言語とJava、切っても切れないその関係
Jvm言語とJava、切っても切れないその関係
 
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。 【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
【BS4】時は来たれり。今こそ .NET 6 へ移行する時。
 
Chrome Developer Toolsを使いこなそう!
Chrome Developer Toolsを使いこなそう!Chrome Developer Toolsを使いこなそう!
Chrome Developer Toolsを使いこなそう!
 
C#でわかる こわくないMonad
C#でわかる こわくないMonadC#でわかる こわくないMonad
C#でわかる こわくないMonad
 
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみるDSIRNLP #3 LZ4 の速さの秘密に迫ってみる
DSIRNLP #3 LZ4 の速さの秘密に迫ってみる
 
Introduction to Robot Framework
Introduction to Robot FrameworkIntroduction to Robot Framework
Introduction to Robot Framework
 
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil Tayar
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil TayarCypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil Tayar
Cypress vs Selenium WebDriver: Better, Or Just Different? -- by Gil Tayar
 
Testing with JUnit 5 and Spring
Testing with JUnit 5 and SpringTesting with JUnit 5 and Spring
Testing with JUnit 5 and Spring
 
Web automation using selenium.ppt
Web automation using selenium.pptWeb automation using selenium.ppt
Web automation using selenium.ppt
 
Tomcatの実装から学ぶクラスローダリーク #渋谷Java
Tomcatの実装から学ぶクラスローダリーク #渋谷JavaTomcatの実装から学ぶクラスローダリーク #渋谷Java
Tomcatの実装から学ぶクラスローダリーク #渋谷Java
 
Test automation using selenium
Test automation using seleniumTest automation using selenium
Test automation using selenium
 
Robot framework and selenium2 library
Robot framework and selenium2 libraryRobot framework and selenium2 library
Robot framework and selenium2 library
 
ELFの動的リンク
ELFの動的リンクELFの動的リンク
ELFの動的リンク
 
Automation Framework Design
Automation Framework DesignAutomation Framework Design
Automation Framework Design
 

Similar a Nightwatch101

前端MVC之backbone
前端MVC之backbone前端MVC之backbone
前端MVC之backboneJerry Xie
 
Html5和css3入门
Html5和css3入门Html5和css3入门
Html5和css3入门Xiujun Ma
 
Kind editor设计思路
Kind editor设计思路Kind editor设计思路
Kind editor设计思路taobao.com
 
香港六合彩
香港六合彩香港六合彩
香港六合彩aaveow
 
Kindeditor 设计思路
Kindeditor 设计思路Kindeditor 设计思路
Kindeditor 设计思路luolonghao
 
Asp.net mvc 培训
Asp.net mvc 培训Asp.net mvc 培训
Asp.net mvc 培训lotusprince
 
Script with engine
Script with engineScript with engine
Script with engineWebrebuild
 
建站大业,实战ASP.NET 4
建站大业,实战ASP.NET 4建站大业,实战ASP.NET 4
建站大业,实战ASP.NET 4Cat Chen
 
Kindeditor设计思路v2
Kindeditor设计思路v2Kindeditor设计思路v2
Kindeditor设计思路v2luolonghao
 
HTML5 介绍
HTML5 介绍HTML5 介绍
HTML5 介绍S S
 
Dive into kissy
Dive into kissyDive into kissy
Dive into kissyjay li
 
Backbone js and requirejs
Backbone js and requirejsBackbone js and requirejs
Backbone js and requirejsChi-wen Sun
 
第十期 阿甘Javascript开发思想(入门篇)
第十期 阿甘Javascript开发思想(入门篇)第十期 阿甘Javascript开发思想(入门篇)
第十期 阿甘Javascript开发思想(入门篇)9scss
 
HTML5概览
HTML5概览HTML5概览
HTML5概览Adam Lu
 
JQuery 学习
JQuery 学习JQuery 学习
JQuery 学习cssrain
 
旺铺前端设计和实现
旺铺前端设计和实现旺铺前端设计和实现
旺铺前端设计和实现hua qiu
 

Similar a Nightwatch101 (20)

前端MVC之backbone
前端MVC之backbone前端MVC之backbone
前端MVC之backbone
 
Mvc
MvcMvc
Mvc
 
Html5和css3入门
Html5和css3入门Html5和css3入门
Html5和css3入门
 
Kind editor设计思路
Kind editor设计思路Kind editor设计思路
Kind editor设计思路
 
香港六合彩
香港六合彩香港六合彩
香港六合彩
 
ev2oik
ev2oikev2oik
ev2oik
 
香港六合彩
香港六合彩香港六合彩
香港六合彩
 
Kindeditor 设计思路
Kindeditor 设计思路Kindeditor 设计思路
Kindeditor 设计思路
 
Asp.net mvc 培训
Asp.net mvc 培训Asp.net mvc 培训
Asp.net mvc 培训
 
Script with engine
Script with engineScript with engine
Script with engine
 
建站大业,实战ASP.NET 4
建站大业,实战ASP.NET 4建站大业,实战ASP.NET 4
建站大业,实战ASP.NET 4
 
Kindeditor设计思路v2
Kindeditor设计思路v2Kindeditor设计思路v2
Kindeditor设计思路v2
 
HTML5 介绍
HTML5 介绍HTML5 介绍
HTML5 介绍
 
Dive into kissy
Dive into kissyDive into kissy
Dive into kissy
 
Backbone js and requirejs
Backbone js and requirejsBackbone js and requirejs
Backbone js and requirejs
 
第十期 阿甘Javascript开发思想(入门篇)
第十期 阿甘Javascript开发思想(入门篇)第十期 阿甘Javascript开发思想(入门篇)
第十期 阿甘Javascript开发思想(入门篇)
 
HTML5概览
HTML5概览HTML5概览
HTML5概览
 
Vue.js
Vue.jsVue.js
Vue.js
 
JQuery 学习
JQuery 学习JQuery 学习
JQuery 学习
 
旺铺前端设计和实现
旺铺前端设计和实现旺铺前端设计和实现
旺铺前端设计和实现
 

Más de Hsin-Hao Tang

Start your app the better way with Styled System
Start your app the better way with Styled SystemStart your app the better way with Styled System
Start your app the better way with Styled SystemHsin-Hao Tang
 
Build a better UI component library with Styled System
Build a better UI component library with Styled SystemBuild a better UI component library with Styled System
Build a better UI component library with Styled SystemHsin-Hao Tang
 
單元測試:Mocha、Chai 和 Sinon
單元測試:Mocha、Chai 和 Sinon單元測試:Mocha、Chai 和 Sinon
單元測試:Mocha、Chai 和 SinonHsin-Hao Tang
 
利用 JavaScript 實作瀏覽器推播通知
利用 JavaScript 實作瀏覽器推播通知利用 JavaScript 實作瀏覽器推播通知
利用 JavaScript 實作瀏覽器推播通知Hsin-Hao Tang
 

Más de Hsin-Hao Tang (6)

Lighthouse
LighthouseLighthouse
Lighthouse
 
Start your app the better way with Styled System
Start your app the better way with Styled SystemStart your app the better way with Styled System
Start your app the better way with Styled System
 
Build a better UI component library with Styled System
Build a better UI component library with Styled SystemBuild a better UI component library with Styled System
Build a better UI component library with Styled System
 
單元測試:Mocha、Chai 和 Sinon
單元測試:Mocha、Chai 和 Sinon單元測試:Mocha、Chai 和 Sinon
單元測試:Mocha、Chai 和 Sinon
 
利用 JavaScript 實作瀏覽器推播通知
利用 JavaScript 實作瀏覽器推播通知利用 JavaScript 實作瀏覽器推播通知
利用 JavaScript 實作瀏覽器推播通知
 
SEO Basics
SEO BasicsSEO Basics
SEO Basics
 

Nightwatch101

  • 2. Agenda ● Nightwatch 與 Selenium Webdriver ● 環境建置 ● 設定檔 ● 定位網頁元素:CSS Selector 與 Xpath ● Nightwatch Commands ● 斷言:BDD Expect、Assert、Verify
  • 3. Agenda (cont.) ● Test Hooks ● Nightwatch Test Runner:分組、標籤、禁跑特定測試 ● Page Objects ● 客製化指令與斷言 ● 客製化測試報告 ● 總結
  • 7. Nightwatch 簡介 ● Nightwatch 與 Selenium Webdriver ● 環境建置 ● 設定檔
  • 8. Nightwatch Nightwatch 是什麼? ● 網頁專用的自動化測試框架 End-to-End Testing 是什麼? ● 模擬使用者對瀏覽器進行操作,例如:瀏覽網址、輸入文字、點擊按鈕 ● 可做 UI 測試、整合測試
  • 10. 環境建置 ● 安裝 Java Development Kit(JDK),版本 7+ ● 安裝 Nightwatch ● 下載專案 https://goo.gl/mFHJ2c ● 使用 Test Runner 進行測試 ○ nightwatch ./test/e2e/testDemo.js
  • 11. Nightwatch Test Runner 設定檔 Nightwatch 提供了 Command-line Test Runner,用來跑各種類型的測試 例如:指定測試環境、依群組或標籤或個別檔案 ● 設定檔在專案根目錄下 ● 預設檔名是 nightwatch.json 或 nightwatch.conf.js(優先) ● 設定檔分為三個部分:基本設定(Page Objects、客製化指令和斷言的位置)、 Selenium Server 相關、測試環境相關 ● External Globals:放置複雜的運算或 Plugin 範例
  • 12. Nightwatch 指令與斷言 ● 定位網頁元素:CSS Selector 與 Xpath ● 常用指令 ● 斷言:BDD Expect、Assert、Verify
  • 14. 使用 CSS Selector 定位網頁元素 .waitForElementVisible('body', 1000) //等待 1 秒,確認 <body> 是否出現 .isVisible('.header') //確認 ".header" 是否可見 .getText('.link') //取得 ".link" 內的文字 .setValue('.input', 'Pusheen') //".input" 欄位輸入 "Pusheen"
  • 15. 使用 Xpath 定位網頁元素 ● 設定檔 use_xpath: true 可設定使用 Xpath 為預設選取策略 ● 切換使用 CSS Selector 或 Xpath .useXpath() //使用 Xpath 來抓取網頁元素 .useCss() //使用 CSS Selector 來抓取網頁元素 //在帳號欄位輸入字串 nightwatch101 .setValue('//input[@id="userid"]', 'nightwatch101') //在密碼欄位輸入字串 nightwatch101 .setValue('//input[@id="userpass"]', 'nightwatch101')
  • 16. Nightwatch Commands url, waitForElementPresent, setValue, click, pause, end 'Ruten Desktop Login': browser => { browser .url('https://member.ruten.com.tw/user/login.htm') // 打開指定網址 .waitForElementPresent('body') // 存在? 確認 DOM Element 載入完成 .setValue('#userid', 'nightwatch101') // 對 input text 鍵入字串 .setValue('#userpass', '*************') .click('#btnLogin') // 點擊送出按鈕 .pause(1000) // 暫停測試程式,可指定暫停時間( ms) .end(); // 結束 session,關閉瀏覽器 } nightwatch test/e2e/member/testDesktopLogin.js
  • 17. Nightwatch Commands (cont.) waitForElementVisible, getTitle, isVisible 'Demo Ruten SubCategory Page': browser => { browser .url('http://class.ruten.com.tw/category/sub00.php?c=00080001') .waitForElementVisible('body') // <body> 可見? .getTitle(function(title) { // 取得網頁標題 // 標題為"DC數位相機 - 露天拍賣"? this.assert.equal(title, 'DC數位相機 - 露天拍賣'); }) .isVisible('.header') // 確認 .header 可見?
  • 18. Nightwatch Commands (cont.) getAttribute, getTagName, getText .getAttribute('#search_input', 'name', function(result) { // 取得元素 #search_input 的屬性 name 的資料,並比對其值是否為 "k" this.assert.equal(result.value, 'k'); }) .getTagName('#search_input', function(result) { // 取得元素 #search_input 的 tag name 是否為 "input" this.assert.equal(result.value, 'input'); }) .getText('.button', result => { // 取得元素 .button 的文字,並比對是否為 "搜尋" this.assert.equal(result.value, '搜尋') })
  • 19. Nightwatch Commands (cont.) getCssProperty, getElementSize .getCssProperty('#search_input', 'line-height', function(result) { // 取得 #search_input 的 CSS line-height 的值,並比對是否為 "27px" this.assert.equal(result.value, '27px'); }) .getElementSize('.submit', result => { // 取得元素 .submit 的寬高 // 比對其寬是否為 75px,其高是否為 27px this.assert.equal(result.value.width, 75); this.assert.equal(result.value.height, 27); })
  • 20. Nightwatch Commands (cont.) clearValue, getValue .clearValue('#search_input') // 清除 #search_input 的值 .setValue('#search_input', 'Pusheen') // 輸入 "Pusheen" .getValue("#search_input", function(result) { // 取得 #search_input 欄位值,並比對其值是否為 "Pusheen" this.assert.equal(result.value, 'Pusheen'); }) .click('.submit') // 點擊送出按鈕 .end() // 結束 session,關閉瀏覽器 } nightwatch test/e2e/class/testSubCategory.js
  • 21. Nightwatch Commands (cont.) saveScreenshot 'Test Save Screenshot': browser => { browser .rtUrl('www') .maximizeWindow() //展開到螢幕到最大寬度 .saveScreenshot('./screenshots/index.png') //儲存螢幕截圖 .end() } nightwatch test/e2e/testSaveScreenshot.js
  • 23. BDD vs TDD BDD 其實是一種 TDD,最大的差異在於 ● BDD:從使用者的角度去思考驗收規格 ● TDD:從測試結果去思考程式該如何實作
  • 24. 斷言 ● 判斷預期和實際的狀況,不如預期就報錯 ● Nightwatch 的斷言有兩種 ○ Expect ○ Assert / Verify
  • 25. Expect ● Nightwatch 的 BDD Expect 是源自於 Chai 的 Expect API ○ expect 比 assert 更有彈性和口語化 ○ 只能用於網頁元素的比對 ○ 缺點:不能串起來(chain)使用 ● Chainable Getters:連接元素和斷言 ○ to、be、been、is、that、which、and、has、have、with、at、does、of browser.expect.element('.heading').text.to.equal('露天旗艦店'); browser.expect.element('.heading').text.be.equal('露天旗艦店');
  • 26. Expect text, equals, contains, matches, not 'Test Main Category Page 1': browser => { browser.url('http://class.ruten.com.tw/category/main?0008'); // 文字內容不為 Hello World? browser.expect.element('.breadcrumb').text.to.not.equals('Hello World'); // 文字內容包含「攝影機」? browser.expect.element('.breadcrumb').text.to.contains('攝影機'); // 文字內容不含數字? browser.expect.element('.breadcrumb').text.to.matches(/^([^0-9]*)$/); browser.end(); } nightwatch test/e2e/class/testMainCategorySimpleExpect1.js
  • 27. Expect a / an, css, attribute, present, visible 'Test Main Category Page 2': browser => { browser.url('http://class.ruten.com.tw/category/main?0008'); // #search 是一個 input? browser.expect.element('#search').to.be.an('input', '#search 必須為 input'); // CSS 屬性 display 的值是 inline-block? browser.expect.element('.ad-item').css('display').to.equals('inline-block'); browser.expect.element('.rt-ad-link').attribute('href'); // 含有屬性 href? browser.expect.element('#ad-flash').to.be.visible; // 可見? browser.expect.element('.shopping-mall').to.be.present; // 存在? browser.end(); } nightwatch test/e2e/class/testMainCategorySimpleExpect2.js
  • 28. Expect enabled, selected, value 'Test Find Pusheen Page': browser => { browser.url('https://find.ruten.com.tw...'); browser.expect.element('.payment').to.be.selected; // 選取? browser.expect.element('.input').to.have.value .that.equals('pusheen'); // 值為 pusheen? browser.expect.element('.button').to.be.enabled; // 啟用? browser.end(); } nightwatch test/e2e/find/testFindPusheenSimpleExpect.js
  • 29. Assert title, containsText, value, valueContains 'Test Main Category Page': browser => { browser .url('http://class.ruten.com.tw/category/main?0008') .assert.title('相機、攝影機 - 露天拍賣') // title 等於特定字串? .setValue('#search_input', '好吃的蛋糕') .assert.value('#search_input', '好吃的蛋糕') // 表單元件的值等於「好吃的蛋糕」? .assert.valueContains('#search_input', '蛋糕') // 表單元件的值包含「蛋糕」? .assert.containsText('.submit', '再搜尋') // 文字節點內容包含「再搜尋」? .end(); } nightwatch test/e2e/class/testMainCategoryAssertSimpleExample1.js
  • 30. Assert (cont.) urlEquals, urlContains, visible, elementPresent 'Test Main Category Page': browser => { browser .url('http://class.ruten.com.tw/category/main?0008') // 目前網址等於特定字串? .assert.urlEquals('http://class.ruten.com.tw/category/main?0008') .assert.urlContains('/category/main') // 目前網址包含特定字串? .assert.visible('#ad-flash') // 可見? .assert.elementPresent('.top-sell') // 存在? .end(); } nightwatch test/e2e/class/testMainCategorySimpleExpect2.js
  • 31. Assert (cont.) attributeEquals, attributeContains, cssProperty, cssClassPresent 'Test Main Category Page': browser => { browser .url('http://class.ruten.com.tw/category/main?0008') // 屬性 type 為 submit? .assert.attributeEquals('.submit', 'type', 'submit') // 檢視 class 包含 button? .assert.attributeContains('.submit', 'class', 'button') // CSS 屬性等於指定的值? .assert.cssProperty('.submit', 'min-height', '24px') .assert.cssClassPresent('.submit', 'button') // 含有 CSS class? .end(); } nightwatch test/e2e/class/testMainCategorySimpleExpect3.js
  • 32. Visible vs Present DOM Element 的可見與存在 ● visible:可見,必為存在 ● hidden:隱藏(display: none / opacity: 0) ● elementPresent:存在 ● elementNotPresent:不存在
  • 33. 猜猜看! 'Guess visible or present?': browser => { browser .rtUrl('class', 'category/main?0023') .verify.visible('.header') .verify.elementPresent('.header') .verify.hidden('.block') .verify.elementPresent('.block') .verify.elementNotPresent('.abcdef') .end() } .header .block (display: none)
  • 34. 猜猜看!(cont.) 'Guess visible or present?': browser => { browser .rtUrl('class', 'category/main?0023') .verify.visible('.header') // (O) 可見 .verify.elementPresent('.header') // (O) 可見,必為存在 .verify.hidden('.block') // (O) 不可見,隱藏 .verify.elementPresent('.block') // (O) 存在,但隱藏 .verify.elementNotPresent('.abcdef') // (O) 不存在 .end() } nightwatch test/e2e/class/testVisibleOrPresent.js
  • 37. Test Hooks before: browser => { // Test Suite 開始前執行 }, after: browser => { // Test Suite 結束後執行 }, beforeEach: browser => { // 在 Test Case 開始前執行 }, afterEach: (browser, done) => { // 在 Test Case 結束後執行 done(); }
  • 38. 會印出什麼? module.exports = { before: browser => { console.log('abc'); }, after: browser => { console.log('def'); }, beforeEach: browser => { console.log('xyz'); }, afterEach: (browser, done) => { console.log('012'); done(); }, 'Test 123': browser => { // ... }, 'Test 456': browser => { // ... } }
  • 40. Nightwatch Test Runner ● 分組、標籤、禁跑特定測試
  • 41. 分組測試 分組的方式就是將測試程式碼放進同一個資料夾,群組名稱即資料夾名稱 ● 依照分類跑測試 ○ nightwatch --group [group_name1,group_name2] ○ 簡寫 nightwatch -g [group_name1,group_name2] ○ EX: nightwatch --group www ● 依照分類忽略測試 ○ nightwatch --skipgroup [group_name1,group_name2]
  • 42. 分組測試 (cont.) tests/e2e ├── class | ├── testMainCategory.js (O,執行) | └── testSubCategory.js (O,執行) ├── point | ├── test1111.js (O,執行) | └── testHotTopics.js (O,執行) └── testDemo.js (X,不執行) 執行 nightwatch --group class,point,只會執行 4 個檔案
  • 43. 依標籤測試 Nightwatch 允許開發者使用標籤(tag)標記測試程式 ● 依照標籤跑測試 ○ nightwatch --tag [tag_name_1] --tag [tag_name_2] ● 依照標籤忽略測試 ○ nightwatch --skiptags [tag_name_1,tag_name_2] 標籤的好處是有彈性。 一個 Test Suite 可有多個不同的標籤,不必受限於分類的唯一和垂直特性
  • 44. 依標籤測試 (cont.) module.exports = { '@tags': ['campaign', 'point'], // Test Suite 可設定標籤 'Demo Ruten Campaign 1111 Page': browser => { browser .url('http://pub.ruten.com.tw/20171111/index.html') .end() } }
  • 45. 依標籤測試 (cont.) tests/e2e ├── class | ├── testMainCategory.js (class) (X,不執行) | └── testSubCategory.js (class) (X,不執行) ├── point | ├── test1111.js (campaign, point) (O,執行) | └── testHotTopics.js (point) (O,執行) └── testDemo.js (index) (X,不執行) 執行 nightwatch --tag point,只會執行 2 個檔案
  • 46. 禁跑特定測試 禁跑特定 Test Suite:設定 @disabled 為 true module.exports = { '@disabled': true, 'Demo Google Page': browser => { browser .url('https://www.google.com.tw/') .end() } }
  • 47. 禁跑特定測試 禁跑特定 Test Case:在 Test Case 前加上一個空字串 module.exports = { 'sample test': function (client) { // 會跑這個 Test Case // ... }, 'other sample test': '' + function (client) { // 加上空字串,禁跑特定 Test Case } };
  • 48. 測試程式的模組化 ● Page Object ● 客製化指令 ● 客製化斷言
  • 49. Page Objects const commandList = { submit: function() { /* ... */ } } module.exports = { url: 'http://sample.com.tw', commands: [commandList], // 指令 sections: { // 區塊, 作為 Namespacing someSection: { selector: '.some-selection', elements: { // 元素 someElement: { selector: '.some-element' } } } } }
  • 50. Page Objects (cont.) module.exports = { 'Find Pusheen': browser => { const findPage = browser.page.findPage(); // 使用 page object findPage.navigate() .assert.title('搜尋結果 : Pusheen - 露天拍賣') .setValue('@searchbox', 'Pusheen') .click('@submit'); browser.end(); } }; nightwatch test/e2e/find/findPusheen.js
  • 51. 客製化指令(Custom Commands) ● 設定檔案路徑 ○ 在 nightwatch.conf.js 的 custom_commands_path 設定檔案路徑 ● 檔名即指令名稱 ● 範例:登入露天桌機版網站 ○ nightwatch test/e2e/member/testRutenLogin.js
  • 52. 客製化指令 (cont.) 'Login Ruten Desktop Website': browser => { browser .url('https://member.ruten.com.tw/user/login.htm') .setValue('.user', 'nightwatch101') .setValue('.password', '*****') .click('.submit') .end(); }
  • 53. 客製化指令 (cont.) exports.command = function(user = 'nightwatch101') { const accounts = require('../settings.js').accounts; this .rtUrl('member', 'login.php') .setValue('.user', user) .setValue('.password', accounts[user].password) .click('.submit') return this; };
  • 54. 客製化指令 (cont.) 'Login Ruten Desktop Website': browser => { browser .rtLogin() // 乾淨易懂可重用! .end(); }
  • 55. 客製化斷言(Custom Assertions) ● 用於擴充 Assert 和 Verify ● 檔案路徑:在 nightwatch.conf.js 的 custom_assertions_path 設定檔案路徑 ● 檔名即指令名稱 ● 格式 ○ message:測試報告顯示的訊息( ✓ Testing if the element <div> has count: 13) ○ expected:期待比對的值 ○ pass:實際進行斷言的地方 ○ value:實際狀況的值,會被 pass 當參數傳入使用 ○ command:執行瀏覽器指令的地方,執行結果會當成參數回傳給 value,再執行 pass
  • 56. 客製化斷言 (cont.) 範例:判斷網頁元素數目是否等於預期數量 exports.assertion = function (selector, count) { this.message = `Testing if the element <${selector}> has count: ${count}`; this.expected = count; this.pass = function(val) { return val === this.expected; } this.value = function(res) { return res; } this.command = function(cb) { return this.api.execute(function(selector) { return document.querySelectorAll(selector).length; }, [selector], function(res) { cb.call(this, res.value); }.bind(this)); } }
  • 57. 測試報告 - 難以閱讀的 XML 格式 <?xml version="1.0" encoding="UTF-8" ?> <testsuites errors="0" failures="0" tests="1"> <testsuite name="class.testMainCategory" errors="0" failures="0" hostname="" id="" package="class" skipped="0" tests="1" time="20.28" timestamp="Fri, 01 Dec 2017 12:06:52 GMT"> <testcase name="Demo Ruten MainCategory Page" classname="class.testMainCategory" time="20.28" assertions="0"></testcase> </testsuite> </testsuites>
  • 60. 客製化測試報告 (cont.) HtmlReporter 可設定的選項 ● openBrowser:跑完測試後所產生的報告是否使用瀏覽器打開 ● reportsDirectory:測試報告的所在路徑 ● reportFilename:測試報告的檔名,預設是 generatedReport.html ● uniqueFilename:測試報告是否要加上 timestamp ● separateReportPerSuite:測試報告是否要加上 test suite 的名稱 ● themeName:測試報告所使用的主題名稱 ● hideSuccess:是否隱藏成功的測試案例,測試報告只顯示錯誤的部 ● timestamprelativeScreenshots:是否將截圖的路徑設為相對路徑
  • 61. 範例 - 露天拍賣-桌機版購物車 登入 -> 購買商品 -> 購物車 -> 結帳 ● 檢視是否登入露天拍賣,若沒有登入就登入 ● 商品頁:點擊按鈕「馬上購買」將商品加入購物車 ● 購車頁:點擊按鈕「確定購買」 ● 結帳頁:選擇運送方式「面交取貨」、填寫取件人資料 ● 回首頁,登出 nightwatch test/e2e/mybid/testShoppingCart.js
  • 63. 總結 ● End-to-End Testing vs Unit Testing ● 小叮嚀 ● QnA
  • 64. End-to-End Testing vs Unit Testing ● Unit Testing 的主體是程式碼,是測試程式碼的自身行為,也就是驗證輸入與 輸出是否符合預期。 ● End-to-End Testing 的主體是使用者,是一種模擬用戶行為的 UI 測試或進行 流程上的整合測試。
  • 65. 小叮嚀 ● Test Suite 無特定執行順序,彼此不可相依 ● 不要使用 pause 做等待,改用 waitForElementVisible ○ 因為等待時間可能會因網路速度延遲而導致無法固定等待時間 ● 有驗證才知道成功或失敗 ○ 單純使用指令 isVisible 而不搭配斷言,無法知道成功或失敗 ● 如何優化測試程式? ○ 將常用的功能抽出來成為模組,成為客製化指令和斷言 ○ 使用 Page Object 封裝網頁片段,增加重用和可讀性,減少維護的複雜度
  • 66. QnA ● 寫測試是否會增加額外工時? 個人經驗是增加一倍。 ● 除了程式碼的品質保證外,還有什麼好處? 記錄規格、方便估時程。 ○ 測試案例如同告知開發者規格的細節和範例,再也不怕同事離職,無人可問。 ○ 魔鬼藏在細節裡,測試程式會告訴我們功能有多細;而且網站久了很容易有地雷,這讓我們 記得把踩雷時間估進去 (〒︿〒)