本文章基于codeceptjs@1.0.1
codeceptjs介紹
codeceptjs是Codeception測(cè)試框架下的一個(gè)項(xiàng)目,是運(yùn)行在nodejs的UI測(cè)試框架, 能使場(chǎng)景驅(qū)動(dòng)的驗(yàn)收測(cè)試簡(jiǎn)單化。
"Modern Era Acceptance Testing Framework for NodeJS"是codeceptjs官網(wǎng)最顯著的標(biāo)語(yǔ),那作為一個(gè)UI測(cè)試框架,究竟有哪些特點(diǎn):
- 從用戶角度編寫(xiě)。codeceptjs提供的API具有declarative testing的特點(diǎn),從用戶行為的角度抽象出了許多與瀏覽器的交互動(dòng)作,測(cè)試代碼非常易讀易懂。codeceptjs提供的API由于其易讀性也可以當(dāng)成DSL來(lái)使用,這些DSL隱藏了后端webdriver服務(wù)的復(fù)雜性,可以讓使用者更加專注到測(cè)試場(chǎng)景的編寫(xiě)中
-
所有的交互動(dòng)作來(lái)自于對(duì)象
I
。對(duì)象I
的方法都被描述為用戶訪問(wèn)網(wǎng)站可能產(chǎn)生的行為,易讀易寫(xiě)易維護(hù),對(duì)非技術(shù)人員也很友好 -
對(duì)多個(gè)后端API兼容。codeceptjs支持多個(gè)webdriver實(shí)現(xiàn) i.e. webdriverio, protractor and phantomjs, 我們可以很簡(jiǎn)單在他們之間切換。這些不同的webdriver實(shí)現(xiàn)在codeceptjs里面叫做Helper,正是下面這些Helper給對(duì)象
I
提供了actions - 同步。我們不用關(guān)心js中的異步問(wèn)題 i.e. promises & callback。測(cè)試場(chǎng)景是線性的
- 基于Mocha測(cè)試框架。
- 可以編寫(xiě)出易讀易懂的場(chǎng)景驅(qū)動(dòng)的驗(yàn)收測(cè)試。可以寫(xiě)成BDD style
- Smart locator。Smart locators: use names, labels, matching text, CSS or XPath to locate elements
- 互動(dòng)式debug shell。可以在測(cè)試執(zhí)行時(shí)隨時(shí)暫停然后嘗試codeceptjs提供地不同的API
- 提供了方便的命令行工具幫助創(chuàng)建測(cè)試。可以通過(guò)命令一步創(chuàng)建tests, pageobjects, stepobjects
- 支持web和mobile端的測(cè)試
說(shuō)了這么多特點(diǎn),它長(zhǎng)什么樣子呢?一個(gè)簡(jiǎn)單的demo如下:
Feature('CodeceptJS demo');
Scenario('check Welcome page on site', (I) => {
I.amOnPage('/');
I.see('Welcome');
});
是不是可以基本當(dāng)plain text來(lái)讀?:)
codeceptjs安裝
環(huán)境檢查
在安裝codeceptjs之前,你要確保你的node版本是大于6.11的
$ node -v
v8.3.0
安裝codeceptjs
使用npm全局安裝codeceptjs,這樣的話我們就可以在shell里面直接使用codeceptjs
命令
[sudo] npm install -g codeceptjs
本地安裝, 這樣的話我們可以執(zhí)行./node_modules/.bin/codeceptjs
npm install --save-dev codeceptjs
安裝browser driver
全局安裝webdriverio
[sudo] npm install -g webdriverio
本地安裝webdriverio
npm install webdriverio --save-dev
全局安裝protractor
[sudo] npm install -g protractor
本地安裝protractor
npm install protractor --save-dev
全局安裝nightmare
[sudo] npm install -g nightmare nightmare-upload
本地安裝nightmare
npm install nightmare nightmare-upload --save-dev
全局安裝codeceptjs+webdriverio
[sudo] npm install -g codeceptjs-webdriverio
本地安裝codeceptjs+webdriverio
[sudo] npm install codeceptjs-webdriverio --save-dev
全局安裝codeceptjs+nightmare
[sudo] npm install -g codeceptjs-nightmare
本地安裝codeceptjs+nightmare
[sudo] npm install codeceptjs-nightmare --save-dev
全局安裝codeceptjs+protractor
[sudo] npm install -g codeceptjs-protractor
本地安裝codeceptjs+protractor
[sudo] npm install codeceptjs-protractor --save-dev
安裝完之后你可以通過(guò)codeceptjs
命令查看codeceptjs的版本和常用命令參數(shù)介紹
? codeceptjs-demo git:(master) ? codeceptjs
CodeceptJS v1.0.1
Usage: [options] [command]
Options:
-h, --help output usage information
Commands:
init [path] Creates dummy config in current dir or [path]
shell|sh [options] [path] Interactive shell
list|l [path] List all actions for I.
def [path] List all actions for I.
generate:test|gt [path] Generates an empty test
generate:pageobject|gpo [path] Generates an empty page object
generate:object|go [options] [path] Generates an empty support object (page/step/fragment)
generate:helper|gh [path] Generates a new helper
run [options] [test] Executes tests
run-multiple [options] [suites...] Executes tests multiple
其中參數(shù)list
可以列出對(duì)象I
所有的動(dòng)作,也就是我們可以利用的瀏覽器交互API
? codeceptjs-demo git:(master) ? codeceptjs list
List of test actions: --
WebDriverIO I.defineTimeout(timeouts)
WebDriverIO I.amOnPage(amOnPage)
WebDriverIO I.click(click)
WebDriverIO I.doubleClick(doubleClick)
WebDriverIO I.rightClick(rightClick)
..........
安裝selenium-standalone
因?yàn)檫@些helper i.e. webdriverio
, protractor
都是需要selenium作為后端與browser通信的服務(wù),所以我們也需要安裝selenium。
下面的命令會(huì)安裝selenium standalone server以及相應(yīng)的browser driver
npm install selenium-standalone@latest -g
selenium-standalone install
selenium-standalone start
詳細(xì)的selenium-standalone資料請(qǐng)參考這里
codeceptjs基礎(chǔ)知識(shí)
命令行使用
codeceptjs init
可以用在當(dāng)我們需要初始化一個(gè)測(cè)試項(xiàng)目的時(shí)候。這個(gè)命令會(huì)創(chuàng)建和配置一些文件來(lái)搭建我們的測(cè)試環(huán)境。
? codeceptjs-init: codeceptjs init
Welcome to CodeceptJS initialization tool
It will prepare and configure a test environment for you
Installing to /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init
? Where are your tests located? ./*_test.js
? What helpers do you want to use? WebDriverIO
? Where should logs, screenshots, and reports to be stored? ./output
? Would you like to extend I object with custom steps? Yes
? Do you want to choose localization for tests? English (no localization)
? Where would you like to place custom steps? ./steps_file.js
Configure helpers...
? [WebDriverIO] Base url of site to be tested http://localhost
? [WebDriverIO] Browser in which testing will be performed chrome
Steps file created at /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/steps_file.js
Config created at /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/codecept.json
Directory for temporary output files created at `_output`
Almost done! Create your first test by executing `codeceptjs gt` (generate test) command
? codeceptjs-init: ll
total 16
-rw-r--r-- 1 diyu staff 291B 8 27 22:39 codecept.json
drwxr-xr-x 2 diyu staff 68B 8 27 22:39 output
-rw-r--r-- 1 diyu staff 281B 8 27 22:39 steps_file.js
如上所示,一些測(cè)試文件、配置文件和輸出目錄都創(chuàng)建好了。具體到這些文件是干嘛的,里面內(nèi)容是什么,之后的學(xué)習(xí)筆記后提到。
codeceptjs gt
會(huì)創(chuàng)建測(cè)試文件
? codeceptjs-init: codeceptjs gt
Creating a new test...
----------------------
? Filename of a test first
? Feature which is being tested first demo
Test for first was created in /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/first_test.js
? codeceptjs-init: ll
total 24
-rw-r--r-- 1 diyu staff 291B 8 27 22:39 codecept.json
-rw-r--r-- 1 diyu staff 66B 8 27 23:50 first_test.js
drwxr-xr-x 2 diyu staff 68B 8 27 22:39 output
-rw-r--r-- 1 diyu staff 281B 8 27 22:39 steps_file.js
? codeceptjs-init: cat first_test.js
Feature('first demo');
Scenario('test something', (I) => {
});
first_test.js 就是被創(chuàng)建的測(cè)試文件
codeceptjs run
會(huì)運(yùn)行測(cè)試。加上參數(shù)--steps
會(huì)輸出詳細(xì)的執(zhí)行過(guò)程
? codeceptjs-init: codeceptjs run --steps
CodeceptJS v1.0.1
Using test root "/Users/diyu/workspace/codeceptjs_demo/codeceptjs-init"
first demo --
test something
? OK in 1ms
OK | 1 passed // 2s
codeceptjs gpo
會(huì)創(chuàng)建一個(gè)page object file。這個(gè)命令可以快速簡(jiǎn)單的幫助你實(shí)現(xiàn)page object設(shè)計(jì)模式
? codeceptjs-init codeceptjs gpo
Creating a new page object
--------------------------
? Name of a page object landingPage
? Where should it be stored ./pages/landingPage.js
Updating configuration file...
Page object for landingPage was created in /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/pages/landingPage.js
Use landingPagePage as parameter in test scenarios to access it
? codeceptjs-init ll
total 24
-rw-r--r-- 1 diyu staff 375B 8 29 21:30 codecept.json
-rw-r--r-- 1 diyu staff 66B 8 27 23:50 first_test.js
drwxr-xr-x 2 diyu staff 68B 8 27 22:39 output
drwxr-xr-x 3 diyu staff 102B 8 29 21:30 pages
-rw-r--r-- 1 diyu staff 281B 8 27 22:39 steps_file.js
landingPage.js被創(chuàng)建在pages目錄下面,這個(gè)文件里面會(huì)有l(wèi)andingPage的元素定位和基本操作。一個(gè)簡(jiǎn)單的demo如下
'use strict';
let I;
module.exports = {
_init() {
I = actor();
},
fields: {
email: '#user_basic_email',
password: '#user_basic_password'
},
submitButton: {css: '#new_user_basic input[type=submit]'},
sendForm(email, password) {
I.fillField(this.fields.email, email);
I.fillField(this.fields.password, password);
I.click(this.submitButton);
}
}
通過(guò)參數(shù)傳遞的方式,我們可以在測(cè)試文件里面注入頁(yè)面元素和操作
Feature('CodeceptJS Demonstration');
Before((I) => { // or Background
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
});
Scenario('test some forms', (I, docsPage) => {
docsPage.sendForm('hello@world.com','123456');
I.see('User is valid');
I.dontSeeInCurrentUrl('/documentation');
});
這樣的話,最簡(jiǎn)單的page object就可以用了
Actions and Assertions
下面通過(guò)一個(gè)簡(jiǎn)單的demo來(lái)看下codeceptjs測(cè)試文件長(zhǎng)什么樣子
Feature('CodeceptJS Demonstration');
Scenario('test some forms', (I) => {
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
I.fillField('Email', 'hello@world.com');
I.fillField('Password', '123456');
I.checkOption('Active');
I.checkOption('Male');
I.click('Create User');
I.see('User is valid');
I.dontSeeInCurrentUrl('/documentation');
});
可以看出
- 所有的頁(yè)面操作都由對(duì)象
I
調(diào)用執(zhí)行,而I
的方法都來(lái)自于Helper - see(),dontSee()方法是用來(lái)做驗(yàn)證的。可以在頁(yè)面上找到指定元素進(jìn)行驗(yàn)證.如果第二個(gè)參數(shù)提供,可以縮小元素查找范圍。
I.see('User is valid', '.alert-success');
- Feature和Scenario來(lái)源于mocha
- fillField(), checkOption()和click()這些方法工作原理類似,都是通過(guò)name, css和xpath找到元素并與之交互
Grabber
當(dāng)我們需要從頁(yè)面中抓取元素然后用到后續(xù)測(cè)試的時(shí)候,我們可以使用帶grab
前綴的方法。它的用法如下
var assert = require('assert');
Feature('CodeceptJS Demonstration');
Scenario('test page title', function*(I) {
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
var title = yield I.grabTitle();
assert.equal(title, 'Example application with SimpleForm and Twitter Bootstrap');
});
grad()要被使用在generator里面,所以帶有yield關(guān)鍵字
debug
codeceptjs可以通過(guò)兩種方式進(jìn)行測(cè)試代碼開(kāi)發(fā)中的調(diào)試
- 在測(cè)試文件中使用
pause()
方法
? codeceptjs-init codeceptjs run --steps
CodeceptJS v1.0.1
Using test root "/Users/diyu/workspace/codeceptjs_demo/codeceptjs-init"
CodeceptJS Demonstration --
test some forms
? I am on page "http://simple-form-bootstrap.plataformatec.com.br/documentation"
? I fill field "Email", "hello@world.com"
? I fill field "Password", "123456"
? I check option "Active"
Interative debug session started
Use JavaScript syntax to try steps in action
Press ENTER to continue
I.
Exiting interactive shell....
? I check option "Male"
? I click "Create User"
? I see "User is valid"
? I dont see in current url "/documentation"
? OK in 12581ms
注意到中間突然出現(xiàn)了交互式的debug session,這就是pause()的作用
- 下面這種
codeceptjs shell
來(lái)進(jìn)行調(diào)試
? codeceptjs-init codeceptjs shell
String interactive shell for current suite...
Interative debug session started
Use JavaScript syntax to try steps in action
Press ENTER to continue
I.amOnPage('www.hupu.com')
I.
通過(guò)這個(gè)互動(dòng)的debug session,我們可以一步步的輸入不同的action來(lái)觀察瀏覽器的行為
Before()/After()
codeceptjs也提供了測(cè)試框架必不可少的用作測(cè)試準(zhǔn)備setup和清理teardown的方法。Before()和Background()可以用于抽取公共方法或做測(cè)試準(zhǔn)備工作,After()可以用于測(cè)試收尾清理工作。
Before()和Background()會(huì)在所有Scenario()之前執(zhí)行一樣,After()會(huì)在所有Scenario()之后執(zhí)行。
下面的測(cè)試代碼片段中Before()會(huì)在所有Scenario()方法之前執(zhí)行。Before()也可以換成Background()。
Feature('CodeceptJS Demonstration');
Before((I) => { // or Background
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
});
After((I) => {
I.clearCookie();
});
Scenario('test some forms', (I) => {
I.click('Create User');
I.see('User is valid');
I.dontSeeInCurrentUrl('/documentation');
});
Scenario('test title', (I) => {
I.seeInTitle('Example application');
});
BeforeSuite()/AfterSuite()
當(dāng)你需要在所有的測(cè)試Scenario()之前進(jìn)行復(fù)雜的setup和teardown,BeforeSuite()/AfterSuite()會(huì)是好的選擇。BeforeSuite()會(huì)在所有的Scenario(), Before()之前調(diào)用運(yùn)行;同理,AfterSuite()會(huì)在所有的Scenario(), Before()之后調(diào)用運(yùn)行;這兩個(gè)方法有且只運(yùn)行一次。
由于BeforeSuite()/AfterSuite()有可能是在瀏覽器打開(kāi)之前運(yùn)行,所有BeforeSuite()/AfterSuite()只能訪問(wèn)到I
對(duì)象。BeforeSuite()/AfterSuite()只會(huì)在聲明的文件中生效,所以不同的文件就可以有不同的BeforeSuite()/AfterSuite()了。
BeforeSuite((I) => {
I.syncDown('testfolder');
});
AfterSuite((I) => {
I.syncUp('testfolder');
I.clearDir('testfolder');
});
within('section', ()=>{})
使用within('section', ()=>{})可以讓一系列I
對(duì)象的方法在頁(yè)面某個(gè)特定區(qū)域執(zhí)行.這樣可以鎖定具體的執(zhí)行范圍。
Feature('within demo')
Scenario('login github', (I)=>{
I.amOnPage('https://github.com');
within('.form-signup-home', () => {
I.fillField('user[login]', 'User');
I.fillField('user[email]', 'user@user.com');
I.fillField('user[password]', 'user@user.com');
I.click('button');
});
I.see('There were problems creating your account.');
});
Comments I.say()
給測(cè)試場(chǎng)景添加注釋,可以使用I.say()
。
Feature('within demo')
Scenario('login github', (I)=>{
I.amOnPage('http://www.baidu.com');
I.say('Above is the link for baidu search engine');
});
Skip test
使用xScenario()可以跳過(guò)測(cè)試的執(zhí)行。
只執(zhí)行一個(gè)測(cè)試場(chǎng)景
使用Scenario.only()可以只運(yùn)行該測(cè)試。
Reporter
之前提到codeceptjs是基于mocha的,所有mocha的--reporter也對(duì)codeceptjs有效。我們可以用下面命令指定report的格式
codeceptjs run --steps --reporter spec (這是default的report格式)
spec
可以替換為dot
, nyan
, tap
等。詳細(xì)內(nèi)容請(qǐng)參考這里。
XML report
使用mocha-junit-reporter可以產(chǎn)生一個(gè)xml報(bào)告,這對(duì)jenkins非常友好。
安裝 npm i mocha-junit-reporter
把報(bào)告放到output目錄下
"mocha": {
"reporterOptions": {
"mochaFile": "output/result.xml"
}
},
使用codeceptjs run --reporter mocha-junit-reporter
來(lái)執(zhí)行測(cè)試產(chǎn)生xml報(bào)告,最終的報(bào)告會(huì)被放在 output/result.xml。
HTML report
因?yàn)閏odeceptjs是基于mocha的,所以我們可以使用mochawesome來(lái)產(chǎn)生好看的HTML報(bào)告。
安裝 npm i mochawesome
把報(bào)告放到output目錄下
"mocha": {
"reporterOptions": {
"reportDir": "output"
}
},
使用codeceptjs run --reporter mochawesome
來(lái)執(zhí)行測(cè)試產(chǎn)生html報(bào)告,最終的報(bào)告會(huì)被放在 output/mochawesome.html。
對(duì)于失敗的測(cè)試,我們需要有截圖,我們可以使用下面的配置來(lái)實(shí)現(xiàn)
"helpers": {
"Mochawesome": {
"uniqueScreenshotNames": "true"
}
},
Test Options
Feature()和Scenario()的第二個(gè)參數(shù)可以是一對(duì)鍵值對(duì),這個(gè)參數(shù)的作用是為測(cè)試場(chǎng)景提供一些選項(xiàng)比如timeout和retry
Feature('My feature', {key: val});
Scenario('My scenario', {key: val}, (I) => {});
Timeout
默認(rèn)狀態(tài)下,測(cè)試場(chǎng)景是沒(méi)有timeout的,我們可以通過(guò)傳遞下面的鍵值對(duì)來(lái)設(shè)置feature級(jí)別或是scenario級(jí)別的timeout
// set timeout to 5s
Feature('Stop me', {timeout: 5000});
// set timeout to 1s
Scenario("Stop me faster", {timeout: 1000}, (I) => {});
// disable timeout for this scenario
Scenario("Don't stop me", {timeout: 0}, (I) => {});
Retries
UI測(cè)試是不穩(wěn)定的,有些時(shí)候我們需要rerun來(lái)讓相應(yīng)的測(cè)試場(chǎng)景通過(guò)。我們可以通過(guò)在Feature()和Scenario()中傳遞retries鍵值對(duì)來(lái)實(shí)現(xiàn)。
//所有scenario失敗后都會(huì)retry3次
Feature('Complex JS Stuff', {retries: 3})
//這個(gè)scenario失敗后只會(huì)retry1次
Scenario('Not that complex', {retries: 1}, (I) => {
// test goes here
});
//這個(gè)scenario失敗后會(huì)retry3次
Scenario('Really complex', (I) => {
// test goes here
});