protractor用于前端UI自动化测试,特别为angular程序定制
特点
- 端对端(e2e)测试
- 采用jasmine作为测试框架
- 基于WebDriverJS,(selenium-webdriver)
- 针对angular应用增加定位器,更加方便实用
- 实现自动等待,告别sleep wait,变异步为同步
- 支持测试代码的调试
- 支持多浏览器的并行UI测试
使用方法
准备工作
- 1 安装protractor: npm install -g protractor
- 2 安装selenium-standlone: webdriver-manager update
- 3 启动selenium服务器: webdriver-manager start
spec书写
spec.js是用于书写测试用例的文件, protractor默认使用jasmine作为测试框架,举最简单的例子来说,一个spec文件可以这样写,使用describe作为
测试程序”块”, it定义一个用例,expect作为断言,其中browser这个全局的变量,用于操作浏览器.
1 2 3 4 5 6
| describe('Protractor Demo App', function() { it('should have a title', function() { browser.get('http://juliemr.github.io/protractor-demo/'); expect(browser.getTitle()).toEqual('Super Calculator'); }); });
|
运行
- 配置conf.json
在测试之前,我们需要建立一个conf.json的文件,在这个文件中,可以配置测试的相关内容,例如:
- multiCapabilities:使用哪些浏览器测试
- chromeOptions:chrome浏览器的运行参数(使用哪些插件等)
- framework:使用哪种测试框架: cucumber macha 还是jasmine
- specs:测试哪些文件
详细的配置信息请参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| exports.config = { multiCapabilities: [ { 'browserName': 'chrome', }], framework: 'jasmine', specs: ['basic/demo_spec.js','basic/angular_spec.js'], jasmineNodeOpts: { defaultTimeoutInterval: 30000 } };
|
grunt运行测试
有时我们需要使用grunt来配置测试任务,下面就是使用grunt-concurrent 模块实现并行运行多浏览器(也可通过conf.json中配置multiCapabilities解决)测试程序的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| module.exports = grunt => { require('load-grunt-tasks')(grunt); grunt.initConfig({ concurrent: { protractor_test: ['protractor-chrome', 'protractor-firefox', 'protractor-safari'] }, protractor: { options: { keepAlive: true, singleRun: false, configFile: "conf.js" }, run_chrome: { options: { args: { browser: "chrome" } } }, run_firefox: { options: { args: { browser: "firefox" } } }, run_safari: { options: { args: { browser: "safari" } } } } }); grunt.registerTask('protractor-chrome', ['protractor:run_chrome']); grunt.registerTask('protractor-firefox', ['protractor:run_firefox']); grunt.registerTask('protractor-safari', ['protractor:run_safari']); grunt.registerTask('protractor-e2e', ['concurrent:protractor_test']); };
|
调试测试程序
除了非常方便的运行机制,protractor还提供便捷的调试方式, 使用selenium-webdriver操纵浏览器的时候,调试是非常困难的,在这里protractor就提供调试方式
在代码中加入 browser.pause(); 并且在终端输入 “repl” 就可以使用WebDriver commands来调试程序了:
1 2 3 4 5 6
| wd-debug> repl > element function (locator) { return new ElementArrayFinder(ptor).all(locator).toElementFinder_(); } >
|
测试非angular的应用
protractor内置方法测试angular的程序,例如它会自动检测angular页面加载完毕才会执行测试程序,当测试非angular程序的时候需要:
- 1 使用 browser.driver 代替 driver
- 2 添加 browser.driver.ignoreSynchronization = true
参考
protractor的详细使用
在protractor中,有几大类用于测试代码,详情请见protractorAPI
- browser: 浏览器的操作
- element & by: 定位获取页面元素
- ExpectedConditions:用于页面操作的逻辑函数,一般同wait连用
- webdriver: selenium 原生的语法函数
- promise:selenium内置的promise方法
浏览器的操作-browser
常用操作代码如下:
- browser.get:
- browser.findElement
- browser.switchTo().frame()
- browser.executeScript:
- browser.executeAsyncScript
- browser.wait:
- browser.sleep:
选择器- by & element
支持多源选择器
- by.css()
- by.id()
- by.xpath()
- by.name()
- by.tagName()
- by.model():angular专用
- by.binding():angular专用
- by.repeater():angular专用
通过element获取:element(by.id(‘frameId’))或者element.all(by.css(‘some-css’));
在非angular应用中使用browser.driver.findElement(by.id(‘frameId’))
ExpectedConditions
预定义了wait的条件,常用的有
- elementToBeClickable: 按钮可以点击
- presenceOf: 元素出现在dom中
- titleContains: title含有某个字符串
- visibilityOf: 某个元素显示
1 2 3 4 5 6 7
| var EC = protractor.ExpectedConditions; var button = $('#xyz'); var isClickable = EC.elementToBeClickable(button); browser.get(URL); browser.wait(isClickable, 5000); button.click();
|
综合实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| it ('test login error', function () { _driver.get('http://subway.simba.taobao.com/#!/login'); _driver.wait(protractor.until.elementLocated(by.css('.login-ifr')),1000).then(function (elem) { _driver.switchTo().frame(elem); _driver.findElement(by.name('TPL_username')).sendKeys('zhangmeng1986712'); _driver.findElement(by.name('TPL_password')).sendKeys('xxxxx'); _driver.findElement(by.id('J_SubmitStatic')).click(); _driver.sleep(1000); browser.driver.findElement(by.css('.error')).then(function (elem) { return elem.getInnerHtml().then(function(text) { expect(text).toMatch('密码和账户名不匹配'); }); }); }); });
|
page object pattern
page object的模式大家一定不陌生,通过合理的配置可以使测试代码更容易维护,举例来说可以这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| //书写一个input操作类 var AngularHomepage = function() { var nameInput = element(by.model('yourName')); var greeting = element(by.binding('yourName')); this.get = function() { browser.get('http://www.angularjs.org'); }; this.setName = function(name) { nameInput.sendKeys(name); }; this.getGreeting = function() { return greeting.getText(); }; }; //测试代码 describe('angularjs homepage', function() { it('should greet the named user', function() { var angularHomepage = new AngularHomepage(); angularHomepage.get(); angularHomepage.setName('Julie'); expect(angularHomepage.getGreeting()).toEqual('Hello Julie!'); }); });
|
mobile端的测试
详情参考
这个例子是使用Appium作为server端进行测试的,由于selenium-webdriver不能直接联Appium, 所以需要使用wd-bridge进行折衷.
e2e测试程序设计准则
参考
参考代码
本文的参考代码见 Github