E2E(end to end)測試是指端到端測試又叫功能測試,站在用戶視角,使用各類功能、各類交互,是用戶的真實使用場景的仿真。在產品高速迭代的如今,有個自動化測試,是重構、迭代的重要保障。對web前端來講,主要的測試就是,表單、動畫、頁面跳轉、dom渲染、Ajax等是否按照指望。javascript
重構代碼的目的是什麼?是爲了使代碼質量更高、性能更好、可讀性和拓展性更強。在重構時如何保證修改後正常功能不受影響?E2E測試正是保證功能的最高層測試,不關注代碼實現細節,專一於代碼可否實現對應的功能,相比於單元測試、集成測試更靈活,你能夠完全改變編碼的語法、架構甚至編程範式而不用從新寫測試用例。css
知道nightwatch是由於vue-cli工具安裝的時候會詢問是否須要安裝nightwatch。自己vue項目也是使用nightwatch來e2e測試的。nightwatch是一個使用selenium或者webdriver或者phantomjs的nodejs編寫的e2e自動測試框架,能夠很方便的寫出測試用例來模仿用戶的操做來自動驗證功能的實現。selenium是一個強大瀏覽器測試平臺,支持firefox、chrome、edge等瀏覽器的模擬測試,其原理是打開瀏覽器時,把本身的JavaScript文件嵌入網頁中。而後selenium的網頁經過frame嵌入目標網頁。這樣,就能夠使用selenium的JavaScript對象來控制目標網頁。html
經過npm安裝nightwatch。前端
$ npm install [-g] nightwatch
複製代碼
根據須要安裝Selenium-server或者其餘Webdriver,比手動去下載jar文件要方便不少。安裝哪些Webdriver取決於你想要測試哪些瀏覽器,若是隻測試Chrome甚至能夠不裝Selenium-servervue
$ npm install selenium-server
$ npm install chromedriver
複製代碼
nightwatch的使用很簡單,一個nightwatch.json或者nightwatch.config.js(後者優先級高)配置文件,使用runner會自動找同級的這兩個文件來獲取配置信息。也能夠手動使用--config來制定配置文件的相對路徑。java
{
"src_folders" : ["tests"],
"output_folder" : "reports",
"custom_commands_path" : "",
"custom_assertions_path" : "",
"page_objects_path" : "",
"globals_path" : "",
"selenium" : {
"start_process" : false,
"server_path" : "",
"log_path" : "",
"port" : 4444,
"cli_args" : {
"webdriver.chrome.driver" : "",
"webdriver.gecko.driver" : "",
"webdriver.edge.driver" : ""
}
},
"test_settings" : {
"default" : {
"launch_url" : "http://localhost",
"selenium_port" : 4444,
"selenium_host" : "localhost",
"silent": true,
"screenshots" : {
"enabled" : false,
"path" : ""
},
"desiredCapabilities": {
"browserName": "firefox",
"marionette": true
}
},
"chrome" : {
"desiredCapabilities": {
"browserName": "chrome"
}
},
"edge" : {
"desiredCapabilities": {
"browserName": "MicrosoftEdge"
}
}
}
}
複製代碼
json配置文件大概就是上面這樣,分爲基本配置、selenium配置和測試配置三個部分。基本配置依次爲測試用例源文件路徑、輸出路徑、基礎指令路徑、全局配置路徑等。selenium設置包括是否開啓、路徑、端口等,cli_args指定將要運行的webdriver。test_settings制定測試時各個環境的設置,默認是default,經過--env加環境名能夠指定配置的任意環境。只要把測試用例放在對應的文件夾使用module.exports暴露一個對象,其中key是測試名,value是一個接受browser實例的函數,在函數中進行斷言,nightwatch會自動依次調用文件夾中的測試用例。一個簡易的Chrome headless模式的nightwatch.conf.js配置以下:node
{
'src_folders': ['test/e2e/specs'],
'output_folder': 'test/e2e/reports',
'globals_path': 'test/e2e/global.js',
'selenium': {
'start_process': true,
'server_path': require('selenium-server').path,
'port': port,
'cli_args': {
'webdriver.chrome.driver': require('chromedriver').path
}
},
'test_settings': {
'default': {
'selenium_port': port,
'selenium_host': 'localhost',
'silent': true,
'globals': {
'productListUrl': 'http://localhost:' + 9003 + '/productlist.html',
}
},
'chrome': {
'desiredCapabilities': {
'browserName': 'chrome',
'javascriptEnabled': true,
'acceptSslCerts': true,
'chromeOptions': {
'args': [
'--headless',
'--disable-gpu'
],
'binary': '/opt/google/chrome/google-chrome'
}
}
},
'globals': {
'productListUrl': 'http://localhost:' + 9003 + '/productlist.html',
}
}
}
複製代碼
Nightwatch的API分爲四個部分git
在browser實例上以.expect.element開頭的BDD(行爲驅動測試)風格的接口,0.7及以上版本nightwatch可用。經過.element方法傳入一個selector(參考querySelector或者jq的語法)獲取到dom實例,經過.text、.value、.attribute等方法獲取到實例屬性。還有一些語意明確的修飾:github
.equal(value)/.contain(value)/.match(regex)
.selected
.present
複製代碼
還有時間修飾.before(ms)(表示一段時間以內)、.after(ms)(表示一段時間以後)。就像造句同樣:某某元素的某某屬性(在某某時間)(不)等於什麼值,這就是BDD風格的測試代碼。例如:web
this.demoTest = function (browser) {
browser.expect.element('body').to.have.attribute('data-attr');
browser.expect.element('body').to.not.have.attribute('data-attr');
browser.expect.element('body').to.not.have.attribute('data-attr', 'Testing if body does not have data-attr');
browser.expect.element('body').to.have.attribute('data-attr').before(100);
browser.expect.element('body').to.have.attribute('data-attr')
.equals('some attribute');
browser.expect.element('body').to.have.attribute('data-attr')
.not.equals('other attribute');
browser.expect.element('body').to.have.attribute('data-attr')
.which.contains('something');
browser.expect.element('body').to.have.attribute('data-attr')
.which.matches(/^something\ else/);
};
複製代碼
以.assert/.verify開頭的兩套相同的方法庫,區別是assert若是斷言失敗則退出整個測試用例全部步,verify則打印後繼續進行。
this.demoTest = function (browser) {
browser.verify.title("Nightwatch.js");
browser.assert.title("Nightwatch.js");
};
複製代碼
有以下判斷方法:
.attributeContains(selector, attribute, expected[, message])
檢查指定元素(selector)的指定屬性(attribute)是否包含有期待的值(expected)打印出指定信息(可選填的message)其餘方法講解相似,不一一贅述
.attributeEquals(selector, attribute, expected[, message])
檢查元素指定屬性是否等於預期
.containText(selector, expectedText[, message])
包含有指定的文本
.cssClassPresent(selector, className[, message])
檢查元素指定class是否存在
.cssClassNotPresent(selector, className[, message])
檢查元素指定class是否不存在
.cssProperty(selector, cssProperty, expected[, message])
檢查元素指定css屬性的值是否等於預期
.elementPresent(selector[, message)
檢查指定元素是否存在於DOM中
.elementNotPresent(selector[, message)
檢查指定元素是否不存在於DOM中
.hidden(selector[, message)
檢查指定元素是否不可見
.title(expected[, message])
檢查頁面標題是否等於預期
.urlContains(expectedText[, message])
檢查當前URL是否包含預期的值
.urlEquals(expected[, message])
檢查當前URL是否等於預期的值
.value(selector, expectedText[, message])
檢查指定元素的value是否等於預期
.valueContains(selector, expectedText[, message])
檢查指定元素的value是否包含預期的值
.visible(selector[, message)
檢查指定元素是否可見
複製代碼
不少命令的讀寫,能夠操做BOM、DOM對象:
.clearValue(selector[, message])
清空input、textarea的值
.click(selector[, callback])
callback爲執行完命令後須要執行的回調
.closeWindow([callback])
.deleteCookie(cookieName[, callback])
.deleteCookies([callback])
.end([callback])
結束會話(關閉窗口)
.getAttribute(selector, attribute, callback)
.getCookie(cookieName, callback)
.getCookies(callback)
.getCssProperty(selector, cssProperty, callback)
.getElementSize(selector, callback)
.getLocation(selector, callback)
.getLocationInView(selector, callback)
.getLog(typeString, callback)
獲取selenium的log,其中type爲string或者function
.getLogTypes(callback)
.getTagName(selector, callback)
.getText(selector, callback)
.getTitle(callback)
.getValue(selector, callback)
.init([url])
url方法的別名,若是不傳url則跳轉到配置中的launch_url
.injectScript(scriptUrl[, id, callback])
注入script
.isLogAvailable(typeString, callback)
typeString爲string或者function,用來測試log的type是否可用
.isVisible(selector, callback)
.maximizeWindow([callback])
最大化當前窗口
.moveToElement(selector, xoffset, yoffset[, callback])
移動鼠標到相對於指定元素的指定位置
.pause(ms[, callback])
暫停指定的時間,若是沒有時間,則無限暫停
.perform(callback)
一個簡單的命令,容許在回調中訪問api
.resizeWindow(width, height[, callback])
調整窗口的尺寸
.saveScreenshot(fileName, callback)
.setCookie(cookie[, callback])
.setValue(selector, inputValue[, callback])
.setWindowPosition(offsetX, offsetY[, callback])
.submitForm(selector[, callback])
.switchWindow(handleOrName[, callback])
.urlHash(hash)
.useCss()
設置當前選擇器模式爲CSS
.useXpath()
設置當前選擇器模式爲Xpath
.waitForElementNotPresent(selector, time[, abortOnFailure, callback, message])
指定元素指定時間內是否不存在
.waitForElementNotVisible(selector, time[, abortOnFailure, callback, message])
指定元素指定時間內是否不可見
.waitForElementPresent(selector, time[, abortOnFailure, callback, message])
.waitForElementVisible(selector, time[, abortOnFailure, callback, message])
複製代碼
簡單的例子:
this.demoTest = function (browser) {
browser.click("#main ul li a.first", function(response) {
this.assert.ok(browser === this, "Check if the context is right.");
this.assert.ok(typeof response == "object", "We got a response object.");
});
};
複製代碼
能夠操做一些更底層的東西,好比:
簡單的例子:
module.exports = {
'demo Test' : function(browser) {
browser.element('css selector', 'body', function(res) {
console.log(res)
});
}
};
複製代碼
也能夠單獨使用chromedriver等進行單一平臺測試,效率更高,測試更快。只須要npm安裝chromedriver或者其餘webdriver,不須要selenium,在selenium設置中把selenium進程設置爲false,測試環境配置中作出相應的改變。在golobal_path設置的配置文件中,利用nightwatch測試的全局before和after鉤子中開、關服務器就好:
var chromedriver = require('chromedriver');
function startChromeDriver() {
chromedriver.start();
}
function stopChromeDriver() {
chromedriver.stop();
}
module.exports = {
before : function(done) {
startChromeDriver.call(this);
done();
},
after : function(done) {
stopChromeDriver.call(this);
done();
}
};
複製代碼
配置尤雨溪大神的nightwatch-helpers食用更佳,補了一些api。Assertions:
Commands:
只須要在圖中位置配置一下便可
推薦使用Headless測試即不打開瀏覽器可視界面以便能跑在服務器上。好比Phantomjs能夠模擬webkit內核瀏覽器的行爲,在Nightwatch中配置一下Phantomjs環境便可,啓動nightwatch時使用--env加上配置裏的環境名激活對應的環境。現在(59版本以上)Phantomjs已經中止維護,使用Chrome自帶的headless模式是更好的選擇。也能夠使用Puppeteer來作E2E測試,好處是隻依賴一個Puppeteer,而且API相對簡單。