carmen popoviciu - protractor styleguide | codemotion milan 2015
TRANSCRIPT
E2E Testing & Best Practices with Protractor
@CarmenPopoviciu
Agenda● E2E Testing
● Protractor
● Best Practices
E2E Testing
WHAT?
E2E testing is a means of verifying that all units of
an application interact as expected with each other
and that the system as a whole works as intended
What?● Testing from the user perspective
What?● Testing from the user perspective
● Main user interaction flows
What?● Testing from the user perspective
● Main user interaction flows
● Major subpages or routes in the site
What?● Testing from the user perspective
● Main user interaction flows
● Major subpages or routes in the site
● Essential elements on a page
WHY?
E2E testing will give you the confidence that
everything works OK
Why?● Prevents production incidents
Why?● Prevents production incidents
● Manual testing takes too much time
Why?● Prevents production incidents
● Manual testing takes too much time
● E2E tests are documentation
Why?● Prevents production incidents
● Manual testing takes too much time
● E2E tests are documentation
● Makes us better devs
Protractor
Protractor is an end-to-end testing framework for AngularJS applications
Protractor● Wrapper around WebdriverJS
● Adds Angular-specific locator strategies
● Runs tests against real browsers
● node.js program
Everything clear?
Let’s dive in a little deeper
Selenium WebDriver
¯\_(ツ)_/¯
Test Browser
Selenium WebDriver
¯\_(ツ)_/¯
Test Browser
WebDriverClient
Libraries
Test Browser
WebDriverClient
Librarieslanguage specific bindings for Selenium WebDriver API
Test Browser
language specific bindings for Selenium WebDriver API
JAVA
Ruby
Python
JS
Test Browser
JAVA
Ruby
Python
JS
WebDriverBrowser Drivers
Test Browser
WebDriver implementations specific to various browsers
JAVA
Ruby
Python
JS
WebDriverBrowser Drivers
Test Browser
Chrome Driver
Firefox Driver
IE Driver
JAVA
Ruby
Python
JS
WebDriver implementations specific to various browsers
Test Browser
Chrome Driver
Firefox Driver
IE Driver
JAVA
Ruby
Python
JS
Test Browser
Chrome Driver
Firefox Driver
IE Driver
JAVA
Ruby
Python
JS
Real browsers that can connect to anything internet can connect to, including the application under test
Test Browser
Chrome Driver
Firefox Driver
IE DriverJS
Python
Ruby
JAVA
Angular apps are written in JS, so we’ll use the JavaScript bindings for the Selenium WebDriver API
Test Browser
Chrome Driver
Firefox Driver
IE Driver
WebdriverJSJavaScript bindings for WebDriver
Test Browser
Chrome Driver
Firefox Driver
IE Driver
WebdriverJS- findElement()- getText()- click()...
JavaScript bindings for WebDriver
Test Browser
Chrome Driver
Firefox Driver
IE Driver
driver.get(url);
var el = driver.findElement( webdriver.By.name('greet'));el.sendKeys('Hi!');el.click();
Test Code
WebdriverJS- findElement()- getText()- click()...
Test Browser
Chrome Driver
Firefox Driver
IE Driver
Test Code
WebdriverJS- findElement()- getText()- click()...
Webdriver Wire Protocol
/session/:sessionId/element/:id/text/session/:sessionId/element/:id/click/session/:sessionId/keys….
Test Browser
Chrome Driver
Firefox Driver
IE Driver
Test Code
WebdriverJS- findElement()- getText()- click()...
Selenium Server
Test Browser
Chrome Driver
Firefox Driver
IE Driver
Test Code
WebdriverJS- findElement()- getText()- click()...
Selenium ServerProtractor
Test Browser
Chrome Driver
Firefox Driver
IE Driver
Test Code
WebdriverJS- findElement()- getText()- click()...
Selenium ServerProtractor
NodeJS
Still confused?
That’s OK. It will take another few attempts to get it right
(flashy green) Demo!
Control Flow
Control Flow● Non blocking API
Control Flow● Non blocking API
● WebdriverJS/Protractor APIs are purely
asynchronous
Control Flow● Non blocking API
● WebdriverJS/Protractor APIs are purely
asynchronous
● Every function returns a promise
Control Flowit('should greet’, function() {
browser.get('#/hello-world’);
var name = element(by.model(‘name’));
var greetBtn = element(by.tagName(‘button’));
var greeting = element(by.binding(‘greeting’));
name.sendKeys('DevfestRo');
greetBtn.click();
expect(greeting.getText()).toEqual('Hi DevfestRo!');
// ¯\_(ツ)_/¯
});
Control Flow● WebDriverJS maintains a queue of scheduled tasks
(pending promises), called the control flow
Control Flow● WebDriverJS maintains a queue of scheduled tasks
(pending promises), called the control flow
● Each task is executed once the one before it in the
queue is finished
Control Flow● WebDriverJS maintains a queue of scheduled tasks
(pending promises), called the control flow
● Each task is executed once the one before it in the
queue is finished
● Protractor adapts Jasmine so that each spec
automatically waits until the control flow is empty
before exiting
That was a LOT of text for one slide
Ready for another quick dive?
ControlFlow Class
Instancewebdriver.promise.controlFlow()
Task1
Task4
Task3
Task2
Queue
Task4
Task3
Task2
Task1
.execute(..)
Control Flowflow.execute(function() {
console.log('a');
});
flow.execute(function() {
console.log('b');
});
flow.execute(function() {
console.log('c');
});
// a
// b
// c
Control Flowflow.execute(function() {
console.log('a');
}).then(function() {
flow.execute(function() {
console.log('c');
});
});
flow.execute(function() {
console.log('b');
});
// a
// c
// b
(two seemingly random) Demos!
Best Practices
Use Page Objects to interact with the page under test
WHY?
Encapsulate information about the elements on the page under test
They can be reused across multiple tests
Decouple the test logic from implementation details
// 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'));
question.sendKeys('What is the purpose of life?'); button.click(); expect(answer.getText()).toEqual("Chocolate!"); });});
// recommended/* question.spec.js */
var QuestionPage = require('./question.page');
describe('Question page', function() { var question = new QuestionPage();
it('should ask any question', function() { question.ask('What is the purpose of meaning?'); expect(question.answer.getText()).toEqual('Chocolate'); });});
// 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;
Declare functions for operations that require more than one step
WHY?
Most elements are exposed by the Page Object and can be used directly in the test
Doing otherwise adds unnecessary complexity
Don't make any assertions in your Page Objects
WHY?
It is the responsibility of the test to do all the assertions
Reader of the test should be able to understand the behavior of the application by
looking at the test only
Prefer protractor locators when possible
WHY?
Access elements easier
Code is less likely to change than markup
More readable locators
<ul class="red"> <li>{{color.name}}</li> <li>{{color.shade}}</li> <li>{{color.code}}</li></ul>
/* avoid */var nameElem = element.all(by.css('.red li')).get(0);
/* recommended */var nameElem = element(by.binding('color.name'));
Prefer by.id and by.css when no protractor locators are available
WHY?
Access elements easier
Select markup that is less likely to change
More readable locators
NEVER use xpath
WHY?
Markup is very easily subject to change
xpath has performance issues
Unreadable locator
more athttps://github.com/CarmenPopoviciu/protractor-styleguide
https://goo.gl/1SmXer
THANK YOU!https://goo.gl/1SmXer