Professional Documents
Culture Documents
Protractor - End-To-End Testing For Angularjs
Protractor - End-To-End Testing For Angularjs
Video
Carmen and Andres gave a talk about this style guide at AngularConnect in London. Here's the video in case you want to watch it.
Table of contents
Test suites
Locator strategies
Page objects
Project structure
Test suites
Don't e2e test what’s been unit tested
Why?
Why?
Using the real application with all the dependencies gives you high confidence
Helps you spot some corner cases you might have overlooked
Use Jasmine2
Why?
/* avoid */
userProperties.name.sendKeys('Teddy B');
userProperties.saveButton.click();
browser.get('#/user‐list');
userList.search('Teddy B');
expect(userList.getNames()).toEqual(['Teddy B']);
});
userProperties.name.clear().sendKeys('Teddy C');
userProperties.saveButton.click();
browser.get('#/user‐list');
userList.search('Teddy C');
expect(userList.getNames()).toEqual(['Teddy C']);
});
/* recommended */
beforeAll(function() {
browser.get('#/user‐list');
userList.newButton.click();
userProperties.name.sendKeys('Teddy B');
userProperties.saveButton.click();
browser.get('#/user‐list');
});
browser.get('#/user‐list');
});
Why?
Have a suite that navigates through the major routes of the app
Why?
Makes sure the major parts of the application are correctly connected
Users usually don’t navigate by manually entering urls
Gives confidence about permissions
Locator strategies
NEVER use xpath
Why?
/* avoid */
<ul class="red">
<li>{{color.name}}</li>
<li>{{color.shade}}</li>
<li>{{color.code}}</li>
</ul>
<div class="details">
<div class="personal">
<input ng‐model="person.name">
</div>
</div>
/* avoid */
/* recommended */
Why?
Why?
Text for buttons, links, and labels tends to change over time
Your tests should not break when you make minor text changes
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.
/* avoid */
/* question‐spec.js */
describe('Question page', function() {
it('should answer any question', function() {
var question = element(by.model('question.text'));
var answer = element(by.binding('answer'));
var button = element(by.css('.question‐button'));
/* recommended */
/* question‐spec.js */
var QuestionPage = require('./question‐page');
/* recommended */
/* question‐page.js */
var QuestionPage = function() {
this.question = element(by.model('question.text'));
this.answer = element(by.binding('answer'));
this.button = element(by.className('question‐button'));
this.ask = function(question) {
this.question.sendKeys(question);
this.button.click();
};
};
module.exports = QuestionPage;
Each page object should declare a single class. You only need to export one class.
/* avoid */
module.exports = UserPropertiesPage;
module.exports = UserSettingsPage;
/* recommended */
/** @constructor */
var UserPropertiesPage = function() {};
module.exports = UserPropertiesPage;
Why? One page object per file means there's only one class to export.
Why?
// specs
});
Why?
<form>
Name: <input type="text" ng‐model="ctrl.user.name">
E‐mail: <input type="text" ng‐model="ctrl.user.email">
<button id="save‐button">Save</button>
</form>
/** @constructor */
var UserPropertiesPage = function() {
// List all public elements here.
this.name = element(by.model('ctrl.user.name'));
this.email = element(by.model('ctrl.user.email'));
this.saveButton = $('#save‐button');
};
Why?
The user of the page object should have quick access to the available elements on a page
Declare page object functions for operations that require more than one step
/**
* Page object for the user properties view.
* @constructor
*/
var UserPropertiesPage = function() {
this.newPhoneButton = $('button.new‐phone');
/**
* Encapsulate complex operations in a function.
* @param {string} phone Phone number.
* @param {string} contactType Phone type (work, home, etc.).
*/
this.addContactPhone = function(phone, contactType) {
this.newPhoneButton.click();
$$('#phone‐list .phone‐row').first().then(function(row) {
row.element(by.model('item.phoneNumber')).sendKeys(phone);
row.element(by.model('item.contactType')).sendKeys(contactType);
});
};
};
Why?
Most elements are already exposed by the page object and can be used directly in the test.
Doing otherwise will not have any added value
Why?
Add page object wrappers for directives, dialogs, and common elements
Some directives render complex HTML or they change frequently. Avoid code duplication by writing wrappers to interact with complex
directives.
Dialogs or modals are frequently used across multiple views.
When the directive changes you only need to change the wrapper once.
For example, the Protractor website has navigation bar with multiple dropdown menus. Each menu has multiple options. A page object for the menu
would look like this:
/**
* Page object for Protractor website menu.
* @constructor
*/
var MenuPage = function() {
this.dropdown = function(dropdownName) {
/**
* Dropdown api. Used to click on an element under a dropdown.
* @param {string} dropdownName
* @return {{option: Function}}
*/
var openDropdown = function() {
element(by.css('.navbar‐nav'))
.element(by.linkText(dropdownName))
.click();
};
return {
/**
* Get an option element under a dropdown.
* @param {string} optionName
* @return {ElementFinder}
*/
option: function(optionName) {
openDropdown();
return element(by.css('.dropdown.open'))
.element(by.linkText(optionName));
}
}
};
};
module.exports = MenuPage;
menu.dropdown('Reference').option('Protractor API').click();
expect(browser.getCurrentUrl())
.toBe('http://www.protractortest.org/#/api');
});
});
Why?
When you have a large team and multiple e2e tests people tend to write their own custom locators for the same directives.
Project structure
Group your e2e tests in a structure that makes sense to the structure of your project
Why?
/* avoid */
|‐‐ project‐folder
|‐‐ app
|‐‐ css
|‐‐ img
|‐‐ partials
home.html
profile.html
contacts.html
|‐‐ js
|‐‐ controllers
|‐‐ directives
|‐‐ services
app.js
...
index.html
|‐‐ test
|‐‐ unit
|‐‐ e2e
home‐page.js
home‐spec.js
profile‐page.js
profile‐spec.js
contacts‐page.js
contacts‐spec.js
/* recommended */
|‐‐ project‐folder
|‐‐ app
|‐‐ css
|‐‐ img
|‐‐ partials
home.html
profile.html
contacts.html
|‐‐ js
|‐‐ controllers
|‐‐ directives
|‐‐ services
app.js
...
index.html
|‐‐ test
|‐‐ unit
|‐‐ e2e
|‐‐ page‐objects
home‐page.js
profile‐page.js
contacts‐page.js
home‐spec.js
profile‐spec.js
contacts‐spec.js