淺談網站自動化測試
以 WebdriverIO 框架為例
劉艾霖 (Alin)
遠距工作者
alincode
NodeJs developer ( 1 year)
SDET ( 8 month)
Java developer ( 5 years )
RD ---> SDET
2014/04 ~ 2014/12
1. 撰寫自動化測試程式
2. 建立測試架構
Jenkins - CI / CD
CI / CD
email test report
SpiraTest
真的這麼順利嗎?
write jenkins plugin
RD ---> SDET --> RD
曾經擔任過 SDET 帶給我的轉變
測試的相關資料不多
Official documents
Test Case
Youtube video
是你最好的好朋友
but now...
Summery
網站自動化測試
Webdriver.IO 介紹
Getting Started
工具介紹
建構測試環境
快速建立前端測試 & Live demo
關於導入的建議
Summary
網站自動化測試
-
透過自動化的方式,去控制流程
-
比較實際結果與預期結果之間的差異
-
可反覆執行
導致的問題是?
前端測試框架
挑選準則
資源:功能、工具、文件
擴充:架構容不容易擴充
永續性:PR 多不多
開發者面向:容不容易debug
nightwatch
protractor
webdriver.io
PR
webdriver.io
WebdriverIO
!=
WebdriverJS
Webdriver
架構
selenium-server restful api
高度模組化
Extendable
custom reporter
var util = require('util'),
events = require('events');
var CustomReporter = function(options) {
};
// Inherit from EventEmitter
util.inherits(CustomReporter, events.EventEmitter);
// Expose Custom Reporter
exports = module.exports = CustomReporter;
// wdio.config
var CustomReporter = require('./reporter/my.custom.reporter');
exports.config = {
reporters: [CustomReporter],
};
custom commands
browser.addCommand('doExternalJob', function async (params) {
return externalLib.command(params);
});
it('execute external library in a sync way', function() {
browser.url('...');
browser.doExternalJob('someParam');
console.log(browser.getTitle());
});
Plugin
webdrivercss
Getting Started
框架幫我們做了哪些事
簡化 API
client
.url('http://google.com')
.setValue('#q', 'webdriver')
.click('#btnG')
// Chain Selectors
WebdriverJS
driver.get('http://www.google.com');
driver.findElement(webdriver.By.id('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.id('btnG')).click();
WebdriverIO
lib/commands/click.js
import { RuntimeError } from '../utils/ErrorHandler'
let click = function (selector) {
return this.element(selector).then((elem) => {
// lib/helpers/findElementStrategy.js
if (!elem.value) throw new RuntimeError(7);
return this.elementIdClick(elem.value.ELEMENT)
})
}
export default click
selectors
- CSS Query Selector (建議使用)
- Link Text
- Partial Link Text
- Element with certain text
- Tag Name
- Name Attribute
- xPath (不建議使用)
browser.click('h2.subheading a');
API 介紹
撰寫測試的三步驟
1. 操作行為
2. 取得值
3. 判斷結果
從類別來看
-
Action
-
Property
-
Protocol
-
State
-
Utility
-
Mobile (略)
Action
// click
browser.click('#myButton');
// setValue
var input = browser.element('.input');
input.setValue('test123');
// submitForm
browser.submitForm('#loginForm');
Property
// title
browser.getTitle();
// get value
browser.getValue('.input');
// get url
browser.getUrl();
Protocol
// single element
browser.element('.h2');
// Collection of elements
browser.elements('li');
// url
browser.url('http://webdriver.io');
State
browser.isEnabled(selector);
browser.isExisting(selector);
browser.isSelected(selector);
Utility
browser.element('.notification').waitForExist(5000);
browser.pause(5000);
browser.debug();
browser.saveScreenshot('front_page.png');
Utility
// ends session and close browser
client
.init()
.url('http://google.com')
.end();
browser.addCommand();
管理 test case
Mode
-
Standalone Mode
-
The WDIO Testrunner
WDIO
a test runner that helps you to build a reliable test suite that is easy to read and maintain.
BDD / TDD framework
var assert = require('assert');
describe('blog front page', function() {
it('should have the right title', function() {
browser.url('http://alincode.github.io/blog/');
var title = browser.getTitle();
assert.equal(title, 'alincode');
});
});
with Mocha
var webdriverio = require('webdriverio');
var options = { desiredCapabilities: { browserName: 'chrome' } };
var client = webdriverio.remote(options);
client
.init()
.url('https://duckduckgo.com/')
.setValue('#search_form_input_homepage', 'WebdriverIO')
.click('#search_button_homepage')
.getTitle().then(function(title) {
console.log('Title is: ' + title);
// outputs: "Title is: WebdriverIO (Software) at DuckDuckGo"
})
.end();
Standalone Mode
Test Specs Group
// wdio.conf.js
exports.config = {
// define all tests
specs: ['./test/specs/**/*.spec.js'],
// define specific suites
suites: {
login: [
'./test/specs/login.success.spec.js',
'./test/specs/login.failure.spec.js'
],
otherFeature: []
}
}
wdio wdio.conf.js --suite login
wdio wdio.conf.js --suite login,otherFeature
支援Promised
client
.init()
.url('https://webdriver.io/')
.getTitle().then(function(title) {
console.log(title);
// outputs: "WebdriverIO - Selenium 2.0 javascript bindings for nodejs"
})
.end();
default promised
var client = require('webdriverio').remote({
desiredCapabilities: {
browserName: 'chrome'
}
});
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.Should();
chai.use(chaiAsPromised);
chaiAsPromised.transferPromiseness = client.transferPromiseness;
Transfer promises
describe('my app', function() {
before(function () {
return client.init();
});
it('should contain a certain text after clicking', function() {
return client
.click('button=Send')
.isVisible('#status_message').should.eventually.be.true
.getText('#status_message').should.eventually.be.equal('Message sent!');
});
});
Transfer promises
browser object
-
webdriver instance
-
globel object
- 取得設定檔內的自訂變數
browser.options.bio
// wdio.conf.js
exports.config = {
// ...
bio: 'hello world',
// ...
}
自訂變數
async vs sync
sync: true
wdio.conf.js
synchronize
describe('DuckDuckGo search', function() {
it('searches for WebdriverIO', function() {
browser.url('https://duckduckgo.com/');
browser.setValue('#search_form_input_homepage', 'WebdriverIO');
browser.click('#search_button_homepage');
var title = browser.getTitle();
console.log('Title is: ' + title);
});
});
asynchronous 可以搭配 await
Reporter
Dot
Spec
and more...
Teamcity
Allure
JSON
Page Object Pattern
function Page() {}
Page.prototype.open = function(path) {
browser.url('/' + path)
}
module.exports = new Page();
// login.page.js
var Page = require('./page')
var LoginPage = Object.create(Page, {
// define elements
username: { get: function () { return browser.element('#username'); } },
password: { get: function () { return browser.element('#password'); } },
form: { get: function () { return browser.element('#login'); } },
flash: { get: function () { return browser.element('#flash'); } },
// define or overwrite page methods
open: { value: function() {
Page.open.call(this, 'login');
} },
submit: { value: function() {
this.form.submitForm();
} }
});
module.exports = LoginPage
// login.spec.js
var expect = require('chai').expect;
var LoginPage = require('../pageobjects/login.page');
describe('login form', function () {
it('登入失敗', function () {
LoginPage.open();
LoginPage.username.setValue('foo');
LoginPage.password.setValue('bar');
LoginPage.submit();
expect(LoginPage.flash.getText()).to.contain('Your username is invalid!');
});
it('登入成功', function () {
LoginPage.open();
LoginPage.username.setValue('tomsmith');
LoginPage.password.setValue('SuperSecretPassword!');
LoginPage.submit();
expect(LoginPage.flash.getText()).to.contain('login success');
});
});
除此之外
connectionRetryTimeout: 90000,
connectionRetryCount: 3,
工具介紹
generate config
browser driver manager
連接 e2e 跟 selenium-server
java -jar selenium-server-standalone-2.42.2.jar
chimp
chimp --mocha --watch --path=test
const assert = require('assert');
describe('Google search', function() {
it('case 1: @watch', function() {
browser.url('http://www.google.com/ncr');
browser.setValue('[name=q]', 'alincode blog');
browser.click('[name=btnG]');
assert.equal(browser.getTitle(), 'Google');
});
});
@focus,@dev,@watch
execute
建構測試環境
cloud hub
local hub
cloud hub
Sauce Labs
Browserstack
TestingBot
build it by youself
build it by youself
-
CI server
-
seleinum hub
-
vm node and driver
but
Not recommended
Automated
但其實不要建Hub也可以
只是缺點是...
快速建立前端測試
-
初始化專案
-
建立設定檔
-
撰寫測試程式
-
執行
初始化專案
mkdir jsdc-webdriverio-sandbox
cd jsdc-webdriverio-sandbox
npm init -y
// install package
npm i webdriverio -D
建立設定檔
./node_modules/webdriverio/bin/wdio
多選,請按空白鍵
vi wdio.conf.js
capabilities: [{
browserName: 'chrome'
}],
mochaOpts: {
timeout: 60000
},
init folder
mkdir -p ./test/specs/
mkdir -p ./errorShots/
vi test/specs/test.js
var assert = require('assert');
describe('page', function() {
it('blog', function() {
browser.url('http://alincode.github.io/blog');
var title = browser.getTitle();
assert.equal(title, 'alincode');
});
});
execute
./node_modules/webdriverio/bin/wdio wdio.conf.js
npm test
"scripts": {
"test": "wdio wdio.conf.js"
}
Live Demo
關於導入的建議
Question:
網站自動化測試,在一個已經上線的網站是否有建議從什麼地方開始做起,新功能或舊功能?
10 %
20 %
70 %
從最重要的功能開始
Question:
但如果不補單元測試,直接開始寫前端測試會有什麼後果?
-
不好維護的測試程式
-
難以判斷測試結果
導入是一個組合性的問題
先請RD來建構整個架構
找曾經踩過雷的人
~~~緣分
Summary
使用 NodeJS 寫前端測試
是很幸福的
full-time remote
backend engineer
JSDC 2016 - getting started webdriverIO
By alincode
JSDC 2016 - getting started webdriverIO
- 4,316