安装protractor进行前端自动化测试-web ui自动化测试
介绍
protractor 是个 Node.js 程序。所以首先得安装 Node.js。然后就可以使用npm 安装 protractor 了。
通常 protractor 使用 Jasmine 测试框架作为他的测试接口。
下面的教程的测试将使用一个独立的 Selenium Server 来控制浏览器。需要安装 JDK 来运行这个服务器。可以用 java -version 命令来测试是否安装成功 JDK。
安装
全局安装protractor
npm install -g protractor
这个命令会安装两个命令行工具,protractor 和 webdriver-manager。尝试跑 protractor --version 确认安装成功。webdriver-manager 是一个辅助工具,他可以简单的让Selenium Server 的实例跑起来 用这个命令可以让他来下载必要的 binaries:
webdriver-manager update
现在可以打开一个服务器
webdriver-manager start
这个命令会开启一个Selenium Server ,然后输出一串的 log 信息。你的 Protractor 测试将会把请求发到服务器来控制本地的浏览器。在整个教程中,请让服务器在后面撒欢的跑。你可以到这里看服务器的状态信息 http://localhost:4444/wd/hub 。
step 0 - 写一测试
新开个命令行或终端窗口,并选个干净整洁的文件夹来做测试目录。
Protractor 运行需要两个文件,一个是 spec file(要测试的js脚本) ,一个是 configuration file(配置文件)。
我们先从一个简单的测试开始,内容是引导一个Angular app 例子和检验他的title。我们将会用到这里的 Super Calculator application —— http://juliemr.github.io/protractor-demo/
将下面的代码 copy 到 spec.js 中:
// spec.js describe('angularjs homepage', function() { it('should have a title', function() { browser.get('http://juliemr.github.io/protractor-demo/'); expect(browser.getTitle()).toEqual('Super Calculator'); }); });
这里 describe 和 it 语法来自 Jasmine 框架。browser 由 Protractor 全局创建的,他可以用来执行浏览器级别的命令,例如用 browser 导航。
现在来创建配置文件,将下面的代码 copy 到 conf.js:
// conf.js exports.config = { seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'] }
这个配置文件通知 Protractor 你的测试文件(specs)的位置,还有Selenium Server 的对话地址(seleniumAddress)。其他配置项使用默认的,Chrome是默认浏览器。
现在运行测试(在命令行输入下面的命令,目录定位到conf.js相同的目录)
protractor conf.js
OK! 我们会看到 Chrome 打开一个指向 Calculator 的窗口,然后又闪关了。测试结果输出应为 1 tests, 1 assertion, 0 failures。真是可喜可贺,我们第一个 Protractor 测试已经成功了 (^o^)/~
step 1 - 元素交互
现在来修改下测试文件,让他与页面上的文件进行交互吧!变 ~ 身 ~
// spec.js describe('angularjs homepage', function() { it('should add one and two', function() { browser.get('http://juliemr.github.io/protractor-demo/'); element(by.model('first')).sendKeys(1); element(by.model('second')).sendKeys(2); element(by.id('gobutton')).click(); expect(element(by.binding('latest')).getText()). toEqual('5'); // This is wrong! }); });
这里也使用了两个 Protractor 创建的全局 elemenet 和 by。element 函数用来寻找页面 HTML 元素,他会返回一个 ElementFinder 对象,这个对象可与元素交互或者获取他们的信息(狗仔队~~)。在这个测试中,我们使用 sendKeys 来给 <input> 输入值,click 来点击按钮,getText 来返回元素的内容。
element 需要一个定位仪参数。by 元素创建了定位仪,我们使用三个类型的定位仪。
by.model('first') 来查找元素 ng-model="first"。
by.id('gobutton') 来根据给定的id查找。
by.binding('latest') 来发现绑定了latest变量的元素,即 { {latest} }
关于定位仪 (by) 和元素查找器 (element) 的更多信息
运行测试
protractor conf.js
1 + 2 == 5肯定会 failed 啦~(≧▽≦)/~啦啦啦
step 2 - 多个测试
稍微美化点,将两个测试结合起来,提出重复的部分
// spec.js describe('angularjs homepage', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a title', function() { expect(browser.getTitle()).toEqual('Super Calculator'); }); it('should add one and two', function() { firstNumber.sendKeys(1); secondNumber.sendKeys(2); goButton.click(); expect(latestResult.getText()).toEqual('3'); }); it('should add four and six', function() { // Fill this in. expect(latestResult.getText()).toEqual('10'); }); });
这里,我们将导航拉到一个 beforeEach 函数里,他会运行在每个 it 块前(即每个it运行前都会运行他)。
step 3 - 改变配置
// conf.js exports.config = { seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], capabilities: { browserName: 'firefox' } }
尝试重新运行测试。我们可以看到 Firefox 代替 chrome 跑了出来。capabilities 对象描述了测试使用的浏览器。所有可用的选项,看这里 the reference config file
还可以一次在多个浏览器中跑测试,像这样
// conf.js exports.config = { seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], multiCapabilities: [{ browserName: 'firefox' }, { browserName: 'chrome' }] }
大家可以试试看效果。Chrome 和 Firefox 会同步进行测试,命令行中的结果是独立显示的。
我的 Firefox 没有安装在默认 C 盘的路径上,所以有报错 Cannot find firefox binary in PATH. Make sure firefox is installed. ,把 firefox.exe 的路径加在了系统变量 PATH ,也没有解决。
这个问题修改的参考文章,万万没想到,我最后还是重装了Firefox。不知道以后还能不能愉快地自定义安装地址了呢╮(╯▽╰)╭
step 4 - 元素列表
重新回到测试文件,有时会遇到多个元素的列表需要处理的问题。可以用 element.all 处理,这个方法会返回一个 ElementArrayFinder。在我们的这个 Calculator app 中,每个操作都记录在一个历史的表格中,使用的是 Angular 的 ng-repeater。下面让我们来测试这些操作是否记录在表格中。
// spec.js describe('angularjs homepage', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); var history = element.all(by.repeater('result in memory')); function add(a, b) { firstNumber.sendKeys(a); secondNumber.sendKeys(b); goButton.click(); } beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a history', function() { add(1, 2); add(3, 4); expect(history.count()).toEqual(2); add(5, 6); expect(history.count()).toEqual(0); // This is wrong! }); });
这里我们做了两件事:
首先,我们创建了一个辅助函数add,并且增加了变量 history。
我们用了 element.all 结合 by.repeater 定位仪来获得一个 ElementArrayFinder。在 spec 文件中,我们用了count函数计算了历史记录的应有 length 。
ElementArrayFinder 处理 add 外还有许多方法。用 last 来获得一个匹配定位仪获得元素集中的最后一个元素。
it('should have a history', function() { add(1, 2); add(3, 4); expect(history.last().getText()).toContain('1 + 2'); expect(history.first().getText()).toContain('foo'); // This is wrong! });
因为 Calculator 展示了最老的结果在底部,我们用 toContain Jasmine 匹配来假设元素的文字包含有“1+2”。
ElementArrayFinder 还有 each、map、 filter 和 reduce 这些方法和 JS 的数组方法很相似。
更多 API 细节看这里
http://angular.github.io/protractor/#/api?view=ElementArrayFinder
api
=============API ===============
Home
Quick Start
Protractor Setup
Protractor Tests
Reference
Tutorial
This is a simple tutorial that shows you how to set up Protractor and start running tests.
Prerequisites
Protractor is a Node.js program. To run, you will need to have Node.js installed. You will download Protractor package using npm, which comes with Node.js. Check the version of Node.js you have by running node --version. It should be greater than v0.10.0.
By default, Protractor uses the Jasmine test framework for its testing interface. This tutorial assumes some familiarity with Jasmine, and we will use version 2.3.
This tutorial will set up a test using a local standalone Selenium Server to control browsers. You will need to have the Java Development Kit (JDK) installed to run the standalone Selenium Server. Check this by running java -version from the command line.
Setup
Use npm to install Protractor globally with:
npm install -g protractor
This will install two command line tools, protractor and webdriver-manager. Try running protractor --version to make sure it's working.
The webdriver-manager is a helper tool to easily get an instance of a Selenium Server running. Use it to download the necessary binaries with:
webdriver-manager update
Now start up a server with:
webdriver-manager start
This will start up a Selenium Server and will output a bunch of info logs. Your Protractor test will send requests to this server to control a local browser. Leave this server running throughout the tutorial. You can see information about the status of the server at http://localhost:4444/wd/hub.
Step 0 - write a test
Open a new command line or terminal window and create a clean folder for testing.
Protractor needs two files to run, a spec file and a configuration file.
Let's start with a simple test that navigates to an example AngularJS application and checks its title. We’ll use the Super Calculator application at http://juliemr.github.io/protractor-demo/.
Copy the following into spec.js:
// spec.js
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');
});
});
The describe and it syntax is from the Jasmine framework. browser is a global created by Protractor, which is used for browser-level commands such as navigation with browser.get.
Now create the configuration file. Copy the following into conf.js:
// conf.js
exports.config = {
framework: 'jasmine2',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['spec.js']
}
This configuration tells Protractor where your test files (specs) are, and where to talk to your Selenium Server (seleniumAddress). It specifies that we will be using Jasmine version 2 for the test framework. It will use the defaults for all other configuration. Chrome is the default browser.
Now run the test with
protractor conf.js
You should see a Chrome browser window open up and navigate to the Calculator, then close itself (this should be very fast!). The test output should be 1 tests, 1 assertion, 0 failures. Congratulations, you've run your first Protractor test!
Step 1 - interacting with elements
Now let's modify the test to interact with elements on the page. Change spec.js to the following:
// spec.js
describe('Protractor Demo App', function() {
it('should add one and two', function() {
browser.get('http://juliemr.github.io/protractor-demo/');
element(by.model('first')).sendKeys(1);
element(by.model('second')).sendKeys(2);
element(by.id('gobutton')).click();
expect(element(by.binding('latest')).getText()).
toEqual('5'); // This is wrong!
});
});
This uses the globals element and by, which are also created by Protractor. The element function is used for finding HTML elements on your webpage. It returns an ElementFinder object, which can be used to interact with the element or get information from it. In this test, we use sendKeys to type into <input>s, click to click a button, and getText to return the content of an element.
element takes one parameter, a Locator, which describes how to find the element. The by object creates Locators. Here, we're using three types of Locators:
by.model('first') to find the element with ng-model="first". If you inspect the Calculator page source, you will see this is <input type=text ng-model="first">.
by.id('gobutton') to find the element with the given id. This finds <button id="gobutton">.
by.binding('latest') to find the element bound to the variable latest. This finds the span containing {{latest}}
Learn more about locators and ElementFinders.
Run the tests with
protractor conf.js
You should see the page enter two numbers and wait for the result to be displayed. Because the result is 3, not 5, our test fails. Fix the test and try running it again.
Step 2 - writing multiple scenarios
Let's put these two tests together and clean them up a bit. Change spec.js to the following:
// spec.js describe('Protractor Demo App', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a title', function() { expect(browser.getTitle()).toEqual('Super Calculator'); }); it('should add one and two', function() { firstNumber.sendKeys(1); secondNumber.sendKeys(2); goButton.click(); expect(latestResult.getText()).toEqual('3'); }); it('should add four and six', function() { // Fill this in. expect(latestResult.getText()).toEqual('10'); }); });
Here, we've pulled the navigation out into a beforeEach function which is run before every it block. We've also stored the ElementFinders for the first and second input in nice variables that can be reused. Fill out the second test using those variables, and run the tests again to ensure they pass.
Step 3 - changing the configuration
Now that we've written some basic tests, let's take a look at the configuration file. The configuration file lets you change things like which browsers are used and how to connect to the Selenium Server. Let's change the browser. Change conf.js to the following:
// conf.js exports.config = { framework: 'jasmine2', seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], capabilities: { browserName: 'firefox' } }
Try running the tests again. You should see the tests running on Firefox instead of Chrome. The capabilities object describes the browser to be tested against. For a full list of options, see the reference config file.
You can also run tests on more than one browser at once. Change conf.js to:
// conf.js exports.config = { framework: 'jasmine2', seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], multiCapabilities: [{ browserName: 'firefox' }, { browserName: 'chrome' }] }
Try running once again. You should see the tests running on Chrome and Firefox simultaneously, and the results reported separately on the command line.
Step 4 - lists of elements
Let's go back to the test files. Feel free to change the configuration back to using only one browser.
Sometimes, you will want to deal with a list of multiple elements. You can do this with element.all, which returns an ElementArrayFinder. In our calculator application, every operation is logged in the history, which is implemented on the site as a table with ng-repeat. Let's do a couple of operations, then test that they're in the history. Change spec.js to:
// spec.js describe('Protractor Demo App', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); var history = element.all(by.repeater('result in memory')); function add(a, b) { firstNumber.sendKeys(a); secondNumber.sendKeys(b); goButton.click(); } beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a history', function() { add(1, 2); add(3, 4); expect(history.count()).toEqual(2); add(5, 6); expect(history.count()).toEqual(0); // This is wrong! }); });
We've done a couple things here - first, we created a helper function, add. We've added the variable history. We use element.all with the by.repeater Locator to get an ElementArrayFinder. In our spec, we assert that the history has the expected length using the count method. Fix the test so that the second expectation passes.
ElementArrayFinder has many methods in addition to count. Let's use last to get an ElementFinder that matches the last element found by the Locator. Change the test to:
it('should have a history', function() { add(1, 2); add(3, 4); expect(history.last().getText()).toContain('1 + 2'); expect(history.first().getText()).toContain('foo'); // This is wrong! });
Since the Calculator reports the oldest result at the bottom, the oldest addition (1 + 2) be the last history entry. We're using the toContain Jasmine matcher to assert that the element text contains "1 + 2". The full element text will also contain the timestamp and the result.
Fix the test so that it correctly expects the first history entry to contain the text "3 + 4".
ElementArrayFinder also has methods each, map, filter, and reduce which are analogous to JavaScript Array methods. Read the API for more details.
Where to go next
This should get you started writing tests. To learn more, see the documentation Table of Contents.
----------
Using Locators
The heart of end-to-end tests for webpages is finding DOM elements, interacting with them, and getting information about the current state of your application. This doc is an overview of how to locate and perform actions on DOM elements using Protractor.
Overview
Protractor exports a global function element, which takes a Locator and will return an ElementFinder. This function finds a single element - if you need to manipulate multiple elements, use the element.all function.
The ElementFinder has a set of action methods, such as click(), getText(), and sendKeys. These are the core way to interact with an element and get information back from it.
When you find elements in Protractor all actions are asynchronous. Behind the scenes, all actions are sent to the browser being controlled using the JSON Webdriver Wire Protocol. The browser then performs the action as a user natively would.
Locators
A locator tells Protractor how to find a certain DOM element. Protractor exports locator factories on the global by object. The most common locators are:
// find an element using a css selector
by.css('.myclass')
// find an element with the given id
by.id('myid')
// find an element with a certain ng-model
by.model('name')
// find an element bound to the given variable
by.binding('bindingname')
For a list of Protractor-specific locators, see the Protractor API: ProtractorBy.
The locators are passed to the element function, as below:
element(by.css('some-css'));
element(by.model('item.name'));
element(by.binding('item.name'));
When using CSS Selectors as a locator, you can use the shortcut $() notation:
$('my-css');
// Is the same as
element(by.css('my-css'));
Actions
The element() function returns an ElementFinder object. The ElementFinder knows how to locate the DOM element using the locator you passed in as a parameter, but it has not actually done so yet. It will not contact the browser until an action method has been called.
The most common action methods are:
var el = element(locator);
// Click on the element
el.click();
// Send keys to the element (usually an input)
el.sendKeys('my text');
// Clear the text in an element (usually an input)
el.clear();
// Get the value of an attribute, for example, get the value of an input
el.getAttribute('value');
Since all actions are asynchronous, all action methods return a promise. So, to log the text of an element, you would do something like:
var el = element(locator);
el.getText().then(function(text) {
console.log(text);
});
Any action available in WebDriverJS on a WebElement is available on an ElementFinder. See a full list.
Finding Multiple Elements
To deal with multiple DOM elements, use the element.all function. This also takes a locator as its only parameter.
element.all(by.css('.selector')).then(function(elements) {
// elements is an array of ElementFinders.
});
element.all() has several helper functions:
// Number of elements.
element.all(locator).count();
// Get by index (starting at 0).
element.all(locator).get(index);
// First and last.
element.all(locator).first();
element.all(locator).last();
Finding Sub-Elements
To find sub-elements, simply chain element and element.all functions together as shown below.
Using a single locator to find:
an element
element(by.css('some-css'));
a list of elements:
element.all(by.css('some-css'));
Using chained locators to find:
a sub-element:
element(by.css('some-css')).element(by.tagName('tag-within-css'));
to find a list of sub-elements:
element(by.css('some-css')).all(by.tagName('tag-within-css'));
You can chain with get/first/last as well like so:
element.all(by.css('some-css')).first().element(by.tagName('tag-within-css'));
element.all(by.css('some-css')).get(index).element(by.tagName('tag-within-css'));
element.all(by.css('some-css')).first().all(by.tagName('tag-within-css'));
Behind the Scenes: ElementFinders versus WebElements
If you're familiar with WebDriver and WebElements, or you're just curious about the WebElements mentioned above, you may be wondering how they relate to ElementFinders.
When you call driver.findElement(locator), WebDriver immediately sends a command over to the browser asking it to locate the element. This isn't great for creating page objects, because we want to be able to do things in setup (before a page may have been loaded) like
var myButton = ??;
and re-use the variable myButton throughout your test. ElementFinders get around this by simply storing the locator information until an action is called.
var myButton = element(locator);
// No command has been sent to the browser yet.
The browser will not receive any commands until you call an action.
myButton.click();
// Now two commands are sent to the browser - find the element, and then click it
ElementFinders also enable chaining to find subelements, such as element(locator1).element(locator2).
All WebElement actions are wrapped in this way and available on the ElementFinder, in addition to a couple helper methods like isPresent.
You can always access the underlying WebElement using element(locator).getWebElement(), but you should not need to.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
element.all(by.css('.items li')).then(function(items) {
expect(items.length).toBe(3);
expect(items[0].getText()).toBe('First');
});
element.all(locator).all(locator) View code
ElementArrayFinder.prototype.all
Calls to ElementArrayFinder may be chained to find an array of elements using the current elements in this ElementArrayFinder as the starting point. This function returns a new ElementArrayFinder which would contain the children elements found (and could also be empty).
Example
View
<div id='id1' class="parent">
<ul>
<li class="foo">1a</li>
<li class="baz">1b</li>
</ul>
</div>
<div id='id2' class="parent">
<ul>
<li class="foo">2a</li>
<li class="bar">2b</li>
</ul>
</div>
Code
var foo = element.all(by.css('.parent')).all(by.css('.foo'))
expect(foo.getText()).toEqual(['1a', '2a'])
var baz = element.all(by.css('.parent')).all(by.css('.baz'))
expect(baz.getText()).toEqual(['1b'])
var nonexistent = element.all(by.css('.parent')).all(by.css('.NONEXISTENT'))
expect(nonexistent.getText()).toEqual([''])
element.all(locator).each(eachFunction) View code
ElementArrayFinder.prototype.each
Calls the input function on each ElementFinder represented by the ElementArrayFinder.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
element.all(by.css('.items li')).each(function(element, index) {
// Will print 0 First, 1 Second, 2 Third.
element.getText().then(function (text) {
console.log(index, text);
});
});
element.all(locator).filter(filterFn) View code
ElementArrayFinder.prototype.filter
Apply a filter function to each element within the ElementArrayFinder. Returns a new ElementArrayFinder with all elements that pass the filter function. The filter function receives the ElementFinder as the first argument and the index as a second arg. This does not actually retrieve the underlying list of elements, so it can be used in page objects.
Example
View
<ul class="items">
<li class="one">First</li>
<li class="two">Second</li>
<li class="three">Third</li>
</ul>
Code
element.all(by.css('.items li')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === 'Third';
});
}).then(function(filteredElements) {
filteredElements[0].click();
});
element.all(locator).get(index) View code
ElementArrayFinder.prototype.get
Get an element within the ElementArrayFinder by index. The index starts at 0. Negative indices are wrapped (i.e. -i means ith element from last) This does not actually retrieve the underlying element.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
var list = element.all(by.css('.items li'));
expect(list.get(0).getText()).toBe('First');
expect(list.get(1).getText()).toBe('Second');
element.all(locator).first() View code
ElementArrayFinder.prototype.first
Get the first matching element for the ElementArrayFinder. This does not actually retrieve the underlying element.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
var first = element.all(by.css('.items li')).first();
expect(first.getText()).toBe('First');
element.all(locator).last() View code
ElementArrayFinder.prototype.last
Get the last matching element for the ElementArrayFinder. This does not actually retrieve the underlying element.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
var last = element.all(by.css('.items li')).last();
expect(last.getText()).toBe('Third');
element.all(locator).count() View code
ElementArrayFinder.prototype.count
Count the number of elements represented by the ElementArrayFinder.
Example
View
<ul class="items">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Code
var list = element.all(by.css('.items li'));
expect(list.count()).toBe(3);
element.all(locator).locator View code
ElementArrayFinder.prototype.locator
Returns the most relevant locator.
Example
Code
// returns by.css('#ID1')
$('#ID1').locator()
// returns by.css('#ID2')
$('#ID1').$('#ID2').locator()
// returns by.css('#ID1')
$$('#ID1').filter(filterFn).get(0).click().locator()
element.all(locator).map(mapFunction) View code
ElementArrayFinder.prototype.map
Apply a map function to each element within the ElementArrayFinder. The callback receives the ElementFinder as the first argument and the index as a second arg.
Example
View
<ul class="items">
<li class="one">First</li>
<li class="two">Second</li>
<li class="three">Third</li>
</ul>
Code
var items = element.all(by.css('.items li')).map(function(elm, index) {
return {
index: index,
text: elm.getText(),
class: elm.getAttribute('class')
};
});
expect(items).toEqual([
{index: 0, text: 'First', class: 'one'},
{index: 1, text: 'Second', class: 'two'},
{index: 2, text: 'Third', class: 'three'}
]);
element.all(locator).reduce(reduceFn) View code
ElementArrayFinder.prototype.reduce
Apply a reduce function against an accumulator and every element found using the locator (from left-to-right). The reduce function has to reduce every element into a single value (the accumulator). Returns promise of the accumulator. The reduce function receives the accumulator, current ElementFinder, the index, and the entire array of ElementFinders, respectively.
Example
View
<ul class="items">
<li class="one">First</li>
<li class="two">Second</li>
<li class="three">Third</li>
</ul>
Code
var value = element.all(by.css('.items li')).reduce(function(acc, elem) {
return elem.getText().then(function(text) {
return acc + text + ' ';
});
});
expect(value).toEqual('First Second Third ');
element.all(locator).evaluate View code
ElementArrayFinder.prototype.evaluate
Evaluates the input as if it were on the scope of the current underlying elements.
Example
View
<span id="foo">{{variableInScope}}</span>
Code
var value = element(by.id('foo')).evaluate('variableInScope');
element.all(locator).allowAnimations View code
ElementArrayFinder.prototype.allowAnimations
Determine if animation is allowed on the current underlying elements.
Example
Code
// Turns off ng-animate animations for all elements in the
element(by.css('body')).allowAnimations(false);
===========================
Working with Spec and Config Files
Protractor needs two files to run, the test or spec file, and the configuration file.
Spec files
Protractor tests are written using the syntax of your test framework, for example Jasmine, and the Protractor API.
Example Spec File
This simple script (example_spec.js) tests the 'The Basics' example on the angularjs.org homepage.
describe('angularjs homepage', function() {
it('should greet the named user', function() {
// Load the AngularJS homepage.
browser.get('http://www.angularjs.org');
// Find the element with ng-model matching 'yourName' - this will
// find the <input type="text" ng-model="yourName"/> element - and then
// type 'Julie' into it.
element(by.model('yourName')).sendKeys('Julie');
// Find the element with binding matching 'yourName' - this will
// find the <h1>Hello {{yourName}}!</h1> element.
var greeting = element(by.binding('yourName'));
// Assert that the text element has the expected value.
// Protractor patches 'expect' to understand promises.
expect(greeting.getText()).toEqual('Hello Julie!');
});
});
Global Variables
Protractor exports these global variables to your spec (test) file:
browser - A wrapper around an instance of WebDriver, used for navigation and page-wide information. The browser.get method loads a page. Protractor expects Angular to be present on a page, so it will throw an error if the page it is attempting to load does not contain the Angular library. (If you need to interact with a non-Angular page, you may access the wrapped webdriver instance directly with browser.driver).
element - A helper function for finding and interacting with DOM elements on the page you are testing. The element function searches for an element on the page. It requires one parameter, a locator strategy for locating the element. See Using Locators for more information. See Protractor's findelements test suite (elements_spec.js) for more examples.
by - A collection of element locator strategies. For example, elements can be found by CSS selector, by ID, or by the attribute they are bound to with ng-model. See Using Locators.
protractor - The Protractor namespace which wraps the WebDriver namespace. Contains static variables and classes, such as protractor.Key which enumerates the codes for special keyboard signals.
Config Files
The configuration file tells Protractor how to set up the Selenium Server, which tests to run, how to set up the browsers, and which test framework to use. The configuration file can also include one or more global settings.
Example Config File
A simple configuration (conf.js) is shown below.
// An example configuration file
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://localhost:4444/wd/hub',
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
// Spec patterns are relative to the configuration file location passed
// to protractor (in this example conf.js).
// They may include glob patterns.
specs: ['example-spec.js'],
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true, // Use colors in the command line report.
}
};
==================
Setting Up the System Under Test
Protractor uses real browsers to run its tests, so it can connect to anything that your browser can connect to. This means you have great flexibility in deciding what you are actually testing. It could be a development server on localhost, a staging server up on your local network, or even production servers on the general internet. All Protractor needs is the URL. There are a couple of things to watch out for!
If your page does manual bootstrap Protractor will not be able to load your page using browser.get. Instead, use the base webdriver instance - browser.driver.get. This means that Protractor does not know when your page is fully loaded, and you may need to add a wait statement to make sure your tests avoid race conditions.
If your page uses $timeout for polling Protractor will not be able to tell when your page is ready. Consider using $interval instead of $timeout.
If you need to do global preparation for your tests (for example, logging in), you can put this into the config in the onPrepare property. This property can be either a function or a filename. If a filename, Protractor will load that file with Node.js and run its contents. See the login tests for an example.
var env = require('./environment.js');
environment.js content as:
//---begin---
module.exports = {
// A base URL for your application under test.
baseUrl:'http://localhost:2024/Web/index.html#/'
};
//---end---
// This is the configuration file showing how a suite of tests might
// handle log-in using the onPrepare field.
exports.config = {
seleniumAddress: env.seleniumAddress,
framework: 'jasmine2',
specs: [
'login/login_spec.js'
],
capabilities: env.capabilities,
baseUrl: env.baseUrl,
onPrepare: function() {
browser.driver.get(env.baseUrl + '/login.html');
browser.driver.findElement(by.id('username')).sendKeys('Jane');
browser.driver.findElement(by.id('password')).sendKeys('1234');
browser.driver.findElement(by.id('clickme')).click();
// Login takes some time, so wait until it's done.
// For the test app's login, we know it's done when it redirects to
// index.html.
return browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return /index/.test(url);
});
}, 10000);
}
};
=======================
Using Page Objects to Organize Tests
When writing end-to-end tests, a common pattern is to use Page Objects. Page Objects help you write cleaner tests by encapsulating information about the elements on your application page. A Page Object can be reused across multiple tests, and if the template of your application changes, you only need to update the Page Object.
Without Page Objects
Here’s a simple test script (example_spec.js) for ‘The Basics’ example on the angularjs.org homepage.
describe('angularjs homepage', function() {
it('should greet the named user', function() {
browser.get('http://www.angularjs.org');
element(by.model('yourName')).sendKeys('Julie');
var greeting = element(by.binding('yourName'));
expect(greeting.getText()).toEqual('Hello Julie!');
});
});
With PageObjects
To switch to Page Objects, the first thing you need to do is create a Page Object. A Page Object for ‘The Basics’ example on the angularjs.org homepage could look like this:
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();
};
};
The next thing you need to do is modify the test script to use the PageObject and its properties. Note that the functionality of the test script itself does not change (nothing is added or deleted).
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!');
});
});
Configuring Test Suites
It is possible to separate your tests into various test suites. In your config file, you could setup the suites option as shown below.
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://localhost:4444/wd/hub',
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
// Spec patterns are relative to the location of the spec file. They may
// include glob patterns.
suites: {
homepage: 'tests/e2e/homepage/**/*Spec.js',
search: ['tests/e2e/contact_search/**/*Spec.js',
'tests/e2e/venue_search/**/*Spec.js']
},
// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true, // Use colors in the command line report.
}
};
From the command line, you can then easily switch between running one or the other suite of tests. This command will run only the homepage section of the tests:
protractor protractor.conf.js --suite homepage
==================
Debugging Protractor Tests
End-to-end tests can be difficult to debug because they depend on an entire system, may depend on prior actions (such as log-in), and may change the state of the application they're testing. WebDriver tests in particular can be difficult to debug because of long error messages and the separation between the browser and the process running the test.
Types of Failure
Protractor comes with examples of failing tests (failure_spec.js). To run, start up the test application and a Selenium Server, and run the command below. Then look at all the stack traces.
protractor debugging/failureConf.js
This test suite shows various types of failure:
WebDriver throws an error - When a command cannot be completed, for example an element is not found.
Protractor will fail when it cannot find the Angular library on a page. If your test needs to interact with a non-angular page, access the WebDriver instance directly with browser.driver.
Expectation Failure - Shows what a normal expectation failure looks like.
Timeouts
There are several ways that Protractor can time out. See the Timeouts reference for full documentation.
Pausing to Debug
Protractor supports two methods for pausing to debug - browser.pause() and browser.debugger(). You probably want to use browser.pause(), unless you would like precise control over the node debugger.
Using pause
Insert browser.pause() into your test where you want to pause.
it('should fail to find a non-existent element', function() {
browser.get('app/index.html#/form');
browser.pause();
// This element doesn't exist, so this fails.
var nonExistant = element(by.binding('nopenopenope')).getText();
});
Run your tests normally.
protractor failureConf.js
The test will pause execution after the scheduled navigation to app/index.html#/form but before trying to get text from the nonnexistant element. The terminal will print instructions for continuing or inspecting the application and a list of the currently pending tasks on the WebDriver control flow.
-- WebDriver control flow schedule
|- waiting for debugger to attach
|--- at [object Object].<anonymous> (failure_spec.js:13:13)
|- Protractor.waitForAngular()
|--- at [object Object].<anonymous> (failure_spec.js:16:59)
wd-debug>
Enter c to move the test forward by one task. Enter repl to enter interactive mode. In interactive mode, you can send WebDriver commands to your browser. The resulting value or error will be reported to the terminal.
> element(by.binding('nopenopenope')).getText()
NoSuchElementError: No element found using locator: by.binding("nopenopenope")
>
> element(by.binding('user')).getText()
'Anon'
While the test is paused you may also interact with the browser. Note that if you open the Chrome Dev Tools, you must close them before continuing the test because ChromeDriver cannot operate when the Dev Tools are open.
When you finish debugging, exit by pressing Ctrl-C. Your tests will continue where they left off, using the same browser.
Using debugger
Insert browser.debugger(); into your test where you want to break:
it('should fail to find a non-existent element', function() {
browser.get('app/index.html#/form');
// Run this statement before the line which fails. If protractor is run
// with the debugger (protractor debug <...>), the test
// will pause after loading the webpage but before trying to find the
// element.
browser.debugger();
// This element doesn't exist, so this fails.
var nonExistant = element(by.binding('nopenopenope')).getText();
});
Then run the test in debug mode:
protractor debug debugging/failureConf.js
This uses the node debugger. Enter c to start execution and continue after the breakpoint.
browser.debugger(); is different from from node's debugger; statement because it adds a breakpoint task asynchronous queue. This means the example above will pause after the get statement has been executing. Using debugger; pauses the test after the get command is scheduled but has not yet been executed.
Protractor's debugger() method works by scheduling a node debug breakpoint on the control flow.
When debugger() is called, it also inserts all the client side scripts from Protractor into the browser as window.clientSideScripts. They can be used from the browser's console.
// In the browser console (e.g. from Chrome Dev Tools)
> window.clientSideScripts.findInputs('username');
// Should return the input element with model 'username'.
// You can also limit the scope of the locator
> window.clientSideScripts.findInputs('username', document.getElementById('#myEl'));
Setting Up WebStorm for Debugging
To set up WebStorm for Protractor, do the following:
Open the Run/Debug Configurations dialog
Add new Node.js configuration.
On the Configuration tab set:
Node Interpreter: path to node executable
Working directory: your project base path
JavaScript file: path to Protractor cli.js file (e.g. node_modules\protractor\lib\cli.js)
Application parameters: path to your Protractor configuration file (e.g. protractorConfig.js)
Click OK, place some breakpoints, and start debugging.
Testing Out Protractor Interactively
When debugging or first writing test suites, you may find it helpful to try out Protractor commands without starting up the entire test suite. You can do this with the element explorer.
To run element explorer, simply run protractor as you normally would, but pass in the flag --elementExplorer:
protractor --elementExplorer
This will load up the URL on WebDriver and put the terminal into a REPL loop. You will see a > prompt. The browser, element and protractor variables will be available. Enter a command such as:
> browser.get('http://www.angularjs.org')
or
> element(by.id('foobar')).getText()
Typing tab at a blank prompt will fill in a suggestion for finding elements. You can also use the list(locator) command to list all elements matching a locator.
Element explorer will start chrome by default. However, you can specify another browser, change browser settings, or specify any other config that you normally would with your protractor test. To do this, pass configs to protractor like you normally would, but with the --elementExplorer flag set:
protractor [configFile] [options] --elementExplorer
For example, to connect to ChromeDriver directly, use
protractor --directConnect --elementExplorer
Element explore will ignore your specs, not set up your framework (e.g. jasmine, mocha, cucumber), and only allow you to pass in 1 capability, but will honor every other parameter in your config.
Note baseUrl is used here as the initial page, i.e. element explorer will try to navigate to baseUrl automatically on start.
Taking Screenshots
WebDriver can snap a screenshot with browser.takeScreenshot(). This can be a good way to help debug tests, especially for tests that run on a continuous integration server. The method returns a promise which will resolve to the screenshot as a base-64 encoded PNG.
Sample usage:
// at the top of the test spec: var fs = require('fs'); // ... other code // abstract writing screen shot to a file function writeScreenShot(data, filename) { var stream = fs.createWriteStream(filename); stream.write(new Buffer(data, 'base64')); stream.end(); } // ... // within a test: browser.takeScreenshot().then(function (png) { writeScreenShot(png, 'exception.png'); });
failure_spec.js
@sjelinsjelin on 10 Feb chore(lint): ran the Closure Linter all over our code
3 contributors @juliemr @sjelin @DavidMikeSimon
RawBlameHistory 60 lines (43 sloc) 1.712 kB
var webdriver = require('selenium-webdriver'); describe('modes of failure', function() { it('should fail to find a non-existent element', function() { browser.get('index.html#/form'); // Run this statement before the line which fails. If protractor is run // with the debugger (protractor debug debugging/conf.js), the test // will pause after loading the webpage but before trying to find the // element. browser.debugger(); // This element doesn't exist, so this fails. var nonExistant = element(by.binding('nopenopenope')).getText(); }); it('should fail to click a hidden element', function() { browser.get('index.html#/form'); element(by.id('hiddenbutton')).click(); }); it('should fail to use protractor on a non-Angular site', function() { browser.get('http://www.google.com'); }, 20000); it('should fail within a promise', function() { browser.get('index.html#/form'); var greeting = element(by.binding('greeting')); greeting.getText().then(function(text) { expect(text).toEqual('This is not what it equals'); }); }); it('should fail an assertion', function() { browser.get('index.html#/form'); var greeting = element(by.binding('greeting')); expect(greeting.getText()).toEqual('This is not what it equals'); }); it('should fail comparing a promise to another promise', function() { browser.get('index.html#/form'); var greeting = element(by.binding('greeting')); expect(greeting.getText()).toEqual(greeting.getAttribute('value')); }); it('should fail because it throws an error', function() { function foo() { throw new Error('bar!'); } foo(); }); }); ============= expect(browser.getCurrentUrl()).toMatch('someUrlString'); description.getAttribute('value'); driver.touchActions(). tap(element1). doubleTap(element2). perform(); element(by.id('awesomeTypo')).click();. Here is where debugging comes in. // Describe a feature describe('Becoming Awesome', function(){ it('should start out not very awesome', function(){ var awesomeStatus = element(by.id('awesomeStatus')); expect(awesomeStatus.getText()).toContain('I am not awesome'); }); it('should become awesome', function(){ // Inserting a debugger statement here // causes the browser to stop when it reaches this line. browser.debugger(); element(by.id('awesomeTypo')).click(); }); it('should be awesome', function(){ var awesomeStatus = element(by.id('awesomeStatus')); expect(awesomeStatus.getText()).toContain('I am awesome'); }); });
Run protractor in debug mode with protractor debug protractor.config.js
一个实际的测试例子(但不完整)
简要说明,conf.js里加了登录,因为后面的测试要访问的地址都需要登录后才可以访问,所以放在conf.js里
=====conf.js======
// An example configuration file. exports.config = { directConnect: true, // Selenium server 测试服务器接入地址 SeleniumAddress: 'http://localhost:4444/wd/hub', // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' }, //baseUrl: 'http://localhost:2024/daNiuJob/www/ionicWeb/index.html#/', //Framework to use. Jasmine 2 is recommended. framework: 'jasmine2', //frameworks: ['mocha', 'jasmine'], // Spec patterns are relative to the current working directly when // protractor is called. //specs: ['testmain.js','testlogin.js'], specs: ['testmain.js','testteaPartyList.js','testpositionSearchIndex.js','testpositionList.js','testresumesview.js'], //specs: ['testlogin.js','testresumesview.js'], //specs: ['testpositionSearchIndex.js'], //Options to be passed to Jasmine. jasmineNodeOpts: { defaultTimeoutInterval: 250000 }, mocks: { //dir 貌似可以随便填 //dir: '../node_modules/protractor-http-mock', dir: 'mocks', default: [] }, onPrepare: function() { //protractor-http-mock config require("protractor-http-mock").config = { //protractor-http-mock 模块的相对路径 不要lib rootDirectory: '../node_modules/protractor-http-mock', //protractorConfig 相当与httpmock.js 的相对路径 protractorConfig: "../../../../example/conf.js", // name of the config here }; //=====login begin ===== browser.driver.get('http://localhost:2024/daNiuJob/www/ionicWeb/index.html#/login'); element(by.model('data.userName')).sendKeys('14500000006'); element(by.model('data.password')).sendKeys('123456'); var btnlogin = element(by.id('Regist')).element(by.tagName('a')); btnlogin.click(); // Login takes some time, so wait until it's done. // For the test app's login, we know it's done when it redirects to // index.html. return browser.driver.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return /index/.test(url); }); }, 10000); //=====login end======== } };
======testteaparty.js========
describe('angularjs homepage', function() { //var env = require('./env.js'); var mock = require('protractor-http-mock'); beforeEach(function() { //var url ='http://test.xxx.com:285/Job/tbJobPositions/Query?industryPost=互联网软件_市场营销_营销总监®ion=上海市&top=20'; var url ='http://test.xxx.com:285/TeaParty/tbPartyMasks/328/TeaParties?top=20'; //var url ='http://test.xxx.com:285/Job/tbJobPositions/Query?region=上海市&searchText=java&top=20'; var rep = {total:1, value:[{TeaPartyStage:"谷歌科技",Name:235,CreateTime:"2015-09-08 14:15",Brand:{Name:"互联网软件_市场营销_营销总监"},Address:"深圳市",Id:4} ]}; var rep2 = {total:0,value:[]}; mock([{ request: { path: url, method: 'GET' }, response: { data: rep, } }]); }); afterEach(function() { mock.teardown(); }); it('should add one and two', function() { //browser.get(env.baseUrl+'myTeaPartys'); browser.get('http://localhost:2024/daNiuJob/www/ionicWeb/index.html#/myTeaPartys'); browser.getTitle().then(function(text){ console.log('title='+text); }); expect(browser.getTitle()).toEqual('myteapartys'); var todoList = element.all(by.repeater('item in gItems.value')); //expect(todoList.count()).toEqual(2); todoList.count().then(function(text){ console.log('num2='+text); //expect(text).toEqual(18); }); browser.sleep(5000); }); });
注:这里的mock没有成功,没找到原因
[javascript] view plain copy
- browser.sleep(5000); 主要用在有写操作有延时的情况,比如点击按钮去数据,数据不会立即出来则sleep一下,这样后面去这个数据就不会有问题
======testmain.js=========
describe('angularjs homepage', function() { var env = require('./env.js'); it('should add one and two', function() { var todoList; todoList = element.all(by.css('.row div')); //console.log('url='+env.baseUrl); browser.get(env.baseUrl+'main'); //expect(todoList.get(1).getText()).toEqual('build an angular app'); var num = todoList.count(); console.log('num='+num); num.then(function(text){ expect(text).toEqual(22); console.log('num2='+text); }); //var first = element.all(by.css('col mainCol')).first(); //console.log('first='+first.getText()); //expect(first.getText()).toBe('First'); //}); }); it('should have a title', function() { browser.getTitle().then(function(text){ console.log('title='+text); }); expect(browser.getTitle()).toEqual('XXXXX'); }); ////by id 获取按钮触发点击事件ok //var btn = element(by.id('div6')); //btn.click(6); });
======testlogin.js========
describe('angularjs homepage', function() { //browser.ignoreSynchronization = true; var mock = require('protractor-http-mock'); var todoList; var url ='http://test.xxx.com:285/Actor/tbUsers/LoginAndGet'; var req = {Mobile:'14500000006',Password:'123456',AppSoftware:'求职',OrderSource:'Brand',OsVersion:'',AppVersion:'A:1.0',DeviceModel:'',DeviceSn:''}; var rep = {UserId:164,AccountId:328,Token:'328:dc91d536ab424aa0b8d7f1ecaf64c55b',ChatPassword:'cd00692c3bfe59267d5ecfac5310286c', Resumes:[ {Birthday:'1994-08-10T16:00:00Z', Education:{College:'深圳大学',Major:'计算机科学与技术',HighestDegree:'本科',GraduateDate:'2015-10-06T16:00:00Z'}, NetName:'二粒',Phone:'14500000006',MediaList:[],Residence:'上海市',Married:true,NativePlace:'福田区',UpdateTime:'2015-08-26T10:28:10Z', JobWanted:{MaxSalary:32767,IndustryPost:'互联网软件_后端开发_JAVA',Region:'深圳市'}, JobWanted2:{MaxSalary:32767,IndustryPost:'互联网软件_市场营销_营销总监',Region:'深圳市'}, Energy:100,AccessTime:'2015-09-07T02:15:20Z', Location:{lat:22.552965753580025,lon:113.94462046281436},LocationTime:'2015-08-26T10:28:10Z', Experiences:[{Company:'伊利牛奶',Department:'市场部',IndustryPost:'互联网软件_市场营销_营销总监',Industry:'互联网和软件_移动互联开发', BeginDate:'2015-07-07T16:00:00Z',EndDate:'1970-01-01T00:00:00Z',Leader:'奶牛',LeaderPhone:'11111',Salary:310}], PositionIntends:[],Name:'二粒',Id:328}]}; beforeEach(function() { //var url ='http://dev.wiqun.com:285/Job/tbJobPositions/Query?industryPost=互联网软件_市场营销_营销总监®ion=上海市&top=20'; mock([{ request: { path: url, method: 'POST', data:req, }, response: { data: rep, } }]); console.log('mock='+mock); }); afterEach(function() { mock.teardown(); }); it('should login', function() { browser.get('http://localhost:2024/daNiuJob/www/ionicWeb/index.html#/login'); element(by.model('data.userName')).sendKeys('14500000006'); element(by.model('data.password')).sendKeys('123456'); var btnlogin = element(by.id('Regist')).element(by.tagName('a')); //browser.pause(); expect(browser.getTitle()).toEqual('用户登录'); browser.getTitle().then(function(text){ console.log('title='+text); //expect(text).toEqual(18); }); //cause mock error //expect(mock.requestsMade()).toEqual(rep); btnlogin.click(); browser.sleep(8000); }); });
注意:如果不在conf.js里进行预登录,单独测试登录页面,访问服务端时用mock则可以成功。但上面那个用同样的方法却不行,nnd
===========env.js===========
module.exports = { // A base URL for your application under test. baseUrl:'http://localhost:2024/daNiuJob/www/ionicWeb/index.html#/' };
运行测试 protractor conf.js 即可看到测试是否通过与否。
赠人玫瑰
手留余香
我们曾如此渴望命运的波澜,到最后才发现:人生最曼妙的风景,竟是内心的淡定与从容……我们曾如此期盼外界的认可,到最后才知道:世界是自己的,与他人毫无关系!-杨绛先生
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的