Webdriver.IO 起手式
by alincode
Java(5 years)
SDET
English
NodeJS
freelancer
freelancer / remote
full-time
alincode
5 years
8 months
7 months
8 months
框架介紹
快速開始測試專案
Getting Started
Live Demo
經驗分享
Learning Resources
Q and A
框架介紹
What
End to End
Testing Framework
WebdriverIO
!=
WebdriverJS
Protractor
NightwatchJS
WebdriverIO
Geb
Robot
NodeJS
底層架構
Web
Selenium restful API
Mobile
Appium (base on selenium)
Module is King
npm install wdio-junit-reporter --save-dev
// wdio.conf.js
module.exports = {
// ...
reporters: ['dot', 'junit'],
reporterOptions: {
junit: {
outputDir: './'
}
},
// ...
};
install module
setting config
BDD / TDD framework
Cloud service
Task Runner plugin
Reporter
Dot reporter
Spec reporter
JUnit reporter
Customer reporter
Teamcity reporter
sync and async
exports.config = {
sync: true
};
Async (Promises based)
Sync
Promises based
describe('getTitle', () => {
it('should return the url of the current webpage', async function () {
(await this.client.getUrl()).should.be.equal(conf.testPage.start)
})
})
client.url('http://webdriver.io')
.getUrl().then(function(url) {
console.log(url);
});
ES2015
Promise
WebdriverIO
- selenium restful api
- anything is module
- more familiar api
- config setting tool
Protractor
- selenium-webdriver
- good for angularJs project
- but...
Protractor
<div class="ng-binding">Hi my name is {{name}}</div>
var greeting = element(by.binding('name'));
無縫接軌 angular data bind
angularjs website
it('should have learn link.', function(done) {
browser.get('http://www.angularjs.org');
var myElement = element(by.css('.learn-link'))
expect(myElement.getText()).to.eventually.equal(
'Learn Angular in your browser for free!');
done();
});
no angularjs website
it('no angularjs website', function(done) {
browser.driver.get('http://webdriver.io/');
var myElement = browser.driver.findElement(by.css('h2'));
expect(myElement.getText()).to.eventually.equal(
'Selenium 2.0 bindings for NodeJS');
done();
});
好像可以在 config 調整,but...
What we can do?
自動化測試
爬蟲
自動化截圖
快速開始測試專案
初始化專案
建立設定檔
測試可被執行
create project
mkdir webdriverio-sandbox
cd webdriverio-sandbox
npm init -y
install package
npm i webdriverio -D
generate config tool
./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('mokayo page', function() {
it('should have the right title', function() {
browser.url('http://blog.mokayo.com');
var title = browser.getTitle();
assert.equal(title, '教你所想學的,用眼樂讀 - blog.mokayo.com');
});
});
執行
./node_modules/webdriverio/bin/wdio wdio.conf.js
npm run e2e-test
"scripts": {
"e2e-test": "wdio wdio.conf.js"
}
OR
Getting Started
Mode
-
Standalone Mode
-
The WDIO Testrunner
Standalone Mode
The WDIO Testrunner
Config
exports.config = {
specs: [
'test/spec/**'
],
capabilities: [{
browserName: 'chrome'
}, {
maxInstances: 5,
browserName: 'firefox',
specs: [
'test/ffOnly/*'
]
},{
browserName: 'phantomjs',
exclude: [
'test/spec/alert.js'
]
}],
sync: true,
screenshotPath: 'shots',
baseUrl: 'http://localhost:8080',
waitforTimeout: 1000, // Default timeout for all waitForXXX commands.
};
Config
exports.config = {
// WebdriverCSS: https://github.com/webdriverio/webdrivercss
// WebdriverRTC: https://github.com/webdriverio/webdriverrtc
// Browserevent: https://github.com/webdriverio/browserevent
plugins: {
webdrivercss: {
screenshotRoot: 'my-shots',
failedComparisonsRoot: 'diffs',
misMatchTolerance: 0.05,
screenWidth: [320,480,640,1024]
},
webdriverrtc: {},
browserevent: {}
},
framework: 'mocha'
reporters: ['dot', 'allure'],
// Hooks
onPrepare: function (config, capabilities) {
}
};
Group Test Specs
// 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
Browser Object
-
webdriver instance
-
global object
selectors
browser.click('h2.subheading a');
- CSS Query Selector (建議使用)
- Link Text
- Partial Link Text
- Element with certain text
- Tag Name
- Name Attribute
- xPath (不建議使用)
Collection of elements
Single element
browser.element('h2');
browser.elements('h2');
API
- 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
getTitle
browser.getTitle();
getValue
browser.element('.input').getValue();
submitForm
browser.submitForm('#loginForm');
Protocol
element
browser.element('.h2');
elements
browser.elements('li');
url
browser.url('http://webdriver.io');
State
isEnabled
browser.isEnabled(selector);
isExisting
browser.isExisting(selector);
isSelected
browser.isSelected(selector);
Utility
waitForExist
browser.element('.notification').waitForExist(5000);
saveScreenshot
browser.saveScreenshot('front_page.png');
end
client
.init()
.url('http://google.com')
.end();
// ends session and close browser
Utility
pause
browser.pause(5000);
debug
browser.addCommand();
addCommand
browser.debug();
Page Object
霰彈式修改
(Shotgun Surgery)
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');
});
});
Live Demo
-
first test
-
site map
-
auth
-
add page test case
經驗分享
by google testing blog
too slow
not reliable
duplicate
測試範圍如何切割
如何開始寫前端測試
測試計畫
測試規格書
撰寫測試程式
測試計畫
# 正常登入流程
使用者可從首頁點擊登入轉至登入頁,並可輸入帳號密碼,密碼驗證過後,頁面顯示登入成功。
# 正常登入流程
使用者輸入帳號密碼驗證後,發現密碼輸入錯誤,停留在登入頁,顯示登入錯誤原因。
# 正常登出流程
使用者可點擊登出按鈕,畢竟狀態真正為登出。
# 檢查所有從首頁,可正常連連結至分支頁
登入頁、分類頁、個人資訊頁、聯繫客服頁
# etc...
測試規格書
不要用 xPath
好讀
易維護
/html/body/section/div/section[2]/article/a[1]
$('.icon-search')