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 = {
// directConnect: true,
// Capabilities to be passed to the webdriver instance.
//capabilities: {
// 'browserName': 'chrome'
//},
multiCapabilities: [ {
'browserName': 'chrome',
//'chromeOptions': {
// 'args': ['--load-extension=/opt/local/share/nginx/html/radar/tanxtag'],
//}
}],
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directly when
// protractor is called.
specs: ['basic/demo_spec.js','basic/angular_spec.js'],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};
  • 运行测试程序
1
protractor conf.json

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 => {
//This module will read the dependencies/devDependencies/peerDependencies/optionalDependencies in your package.json
// and load grunt tasks that match the provided patterns.
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); //wait for an element to become clickable
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