Archive

Archive for July, 2016

Improve the architecture of your cross-platform automation

Hi guys, today I am going to talk about how to better architect your test code and obtain better cross-platform code reuse.

The idea is that you provide an implementation of page objects for each platform you need to support (e.g. iPhone, iPad, Android phone, Android tablet, mobile web, desktop web,…).

 

Use Page Objects

While originating in web testing, the ideas of POP apply equally well to native mobile. The most obvious benefit of this is abstraction and reuse. If you have several steps needing to navigate to details, the code is reused. Also, callers of this method need not worry about the details (e.g. query and touch) or navigating to this screen.

 

Don’t specify fields in your feature files

Working this way gets you complete reuse of Cucumber features as well as step definitions: the details of interacting with the screen is pushed to page object implementations.

Scenarios should be written like a user would describe them. Beware of scenarios that only describe clicking links and filling in form fields, or of steps that contain code or CSS selectors. This is just another variant of programming, but certainly not a feature description.

Declarative features are vivid, concise and contain highly maintainable steps.

For example:

Scenario: Valid login credentials
  Given I am on login page
  When I enter valid credentials
  Then I should be redirected to the management page

 

Define a page object for each screen

For each screen you want to automate, decide on an interface for a page object class (e.g. like LoginScreen). This means that the calling code, which is usually in a step definition, is independent of platform – hence it can be reused across platforms. For each supported platform, define a class with implementations of the page-object methods.

 

Don’t expose the framework in your step definitions

Use only custom steps, and in each step definition only use page objects and their methods (no direct calls to the framework (Calabash, Selenium, Appium, Robotium).

 

Reuse step definitions

This comes in handy when a step extends another step’s behaviour or defines a superior behaviour that consists of multiple steps. You should try to reuse steps as often as possible.This will improve the maintainability of your app: If you need to change a certain behaviour, you just need to change a single step definition.

For example (Using javascript and protractor):

 this.Given(/^I am on login page$/, function() {
    loginPage.openLoginPage();
 });

 

Thank you guys ! See you next week !

 

Resources:

https://developer.xamarin.com/guides/testcloud/calabash/xplat-best-practices/

https://blog.codeship.com/cucumber-best-practices/

How to create your tests with Protractor and cucumber

July 7, 2016 1 comment

Hello guys, today I will post about how to do assertions when using Cucumber and protractor. If you don’t know what is a promise and how they work, I suggest read this link. WebDriver’s JavaScript API is entirely asynchronous and every command results in a promise. WebDriverJS uses a custom promise library with a promise manager called the ControlFlow. The ControlFlow implicitly synchronizes asynchronous actions, making it so you only have to register a promise callback when you want to catch an error or access a return value.

  • So, create a protractor.js in your project’s root folder:
'use strict';

var os = require('os');
var platform = os.platform();

var HOST = 'http://test.protractor.com';
var USER = 'protractor';
var PASS = 'test';

var chrome = {
 browserName: 'chrome'
};

var safari = {
 browserName: 'safari'
};

var firefox = {
 browserName: 'firefox'
};

var capabilities = [chrome];

// Enable Safari only on macos
if (platform === 'darwin') {
 capabilities.push(safari);
}

exports.config = {
 seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
 specs: [
 'src/features/*.feature'
 ],
 multiCapabilities: capabilities,
 noGlobals: true,
 baseUrl: 'http://localhost:5000',
 framework: 'custom',
 frameworkPath: require.resolve('protractor-cucumber-framework'),
 resultJsonOutputFile: 'test-reports/protractor/protractor.json',
 cucumberOpts: {
 require: [
 'src/js/test/common.js',
 'src/js/test/steps/**/*.js'
 ],
 format: 'pretty',
 strict: true,
 tags: [
 '@smoke',
 '~@pending'
 ]
 },
 params: {

 credentials: {
 username: USER,
 password: PASS
 },

 endpoints: {
 authenticate_url: HOST + '/auth'
 }
 },

 //promise to get browserName
 onPrepare: function() {
 var protractor = require('protractor');
 var browser = protractor.browser;
 return browser.getProcessedConfig().then(function(config) {
 browser.browserName = config.capabilities.browserName;
 });
 }
};

 

  • Create your Feature file in: your_project_folder/src/features/login/login.feature
Feature: Login Page


@smoke
 Scenario: Login success
 Given I am on the login page
 When I input valid username and password
 Then I should be redirected to the management page

 

  • Common.js file which will contain the common libraries (You need install them first with npm install command): your_project_folder/src/js/test/common.js
'use strict';

// Configure chai & sinon
global.expect = require('chai')
 .use(require('sinon-chai'))
 .use(require('chai-string'))
 .use(require('chai-as-promised'))
 .use(require('expect-to-be-a-promise'))
 .expect;

global.sinon = require('sinon');

 

  • Create your step definition file in: your_project_folder/src/js/test/steps/login_steps.js
'use strict';

var LoginActions = require('../login_page/login_actions');
var LoginAssertions = require('../login_page/login_assertions');
var protractor = require('protractor');
var browser = protractor.browser;

var LoginPageSteps = function() {

var loginActions = new LoginActions();
var loginAssertions = new LoginAssertions();

this.Given(/^I am on the login page$/, function() {
    loginActions.getPage();
 });

this.When(/^I input valid username and password$/, function() {
    var credentials = browser.params.credentials;
    loginActions.login(credentials.username, credentials.password);
 });

this.Then(/^I should be redirected to the management page$/, function() {
    return loginAssertions.assertManagementPage();
 });

};

module.exports = LoginPageSteps;

 

  • Create your login elements (map repository for login page) file in: your_project_folder/src/js/test/login_page/login_elements.js
'use strict';

var protractor = require('protractor');
var element = protractor.element;
var by = protractor.by;

function LoginStateElements() {
  return {
    url: function() {
    return '/login';
  },
  username: function() {
    return element(by.model('credentials.username'));
  },
  password: function() {
    return element(by.model('credentials.password'));
  },
  submit: function() {
    return element(by.css('[type="submit"]'));
  },
  form: function() {
   return element(by.css('[ng-submit="form("test")"]'));
  }
 };
}

module.exports = LoginStateElements;

 

  • Create your login actions file in: your_project_folder/src/js/test/login_page/login_actions.js
'use strict';

var LoginElements = require('./login_elements');
var protractor = require('protractor');
var browser = protractor.browser;

function LoginStateActions() {

 var loginElements = new LoginElements();

 return {
  getPage: function() {
     browser.get(browser.baseUrl + loginElements.url());
  },
  login: function(username, password) {
     loginElements.username().click();
     loginElements.username().clear();
     loginElements.username().sendKeys(username);
     loginElements.password().click();
     loginElements.password().clear();
     loginElements.password().sendKeys(password);
     loginElements.submit().click();
  }
 };
}

module.exports = LoginStateActions;

 

  • Create your login assertions file in: your_project_folder/src/js/test/login_page/login_assertions.js
'use strict';

var LoginElements = require('./login_elements');

function LoginAssertions() {

 var loginElements = new LoginElements();

 return {
  assertManagementPage: function() {
     var form = loginElements.form();
     var submit = loginElements.submit();
     expect(submit.isEnabled()).to.eventually.be.false;
     return expect(form.isPresent()).to.eventually.be.true;
  }

 };
}

module.exports = LoginAssertions;

 

Chai Assertion Library to understand how to use expect to assert elements.

WebDriver API commands to understand what you can do with the elements, for example, check if is displayed, click, clear, get Title, etc.

 

Thanks To Raphael Bonnet, who has been working with me on this project. It’s great when you find such a good developer to work with and really work as a team trying to find the best for the project together (Even that I am still skeptical about protractor instead of Selenium xD ).

Promise Manager (Protractor – WebDriverJs)

Hey guys,

I have coded in Java, C# and Ruby since I’ve started work with Automation, so about 8 years ago. It’s my first time coding in Javascript for AngularJs pages and for those who are like me you need to know that if you are working with protractor or webdriverjs they return promises from all of its browser interactions, which could lead to some confusing behaviour.

 

What is the promise manager ?

Protractor and webdriverjs are asynchronous and every result returns a promise. They use a custom promise library called ControlFlow, which implicitly synchronizes asynchronous actions, making it so you only have to register a promise callback when you want to catch an error or access a return value. The promise module coordinates the flow of the asynchronous tasks.

This Promise Manager ensures that calls to the browser are run in sequence, and you only need to worry about dealing with the Promises when you wish to do something with data from the page.

In practice, this means that you can ignore the returned promises while interacting with the page.

 

So, instead of:

 

driver.get('https://azevedorafaela.wordpress.com')
 .then(function() {
   return driver.findElement({class: '.searchtip'});
 })
 .then(function(search) {
   return search.sendKeys('webdriver');
 })
 .then(function() {
   return driver.findElement({class: '.button'});
 })
 .then(function(button) {
   return button.click();
 });

 

You can have:

 driver.get('https://azevedorafaela.wordpress.com');
 driver.findElement({class: '.searchtip'}).sendKeys('webdriver');
 driver.findElement({class: '.button'}).click();

 

The trick part is when you want to extract the values from the page to do an assertion for example:

 driver.get("https://azevedorafaela.wordpress.com");
    driver.getTitle().then(function(pageTitle) {
        assert.equal(pageTitle, "Rafaela Azevedo");
 });

 

You can find more information on the links below. Thank you ! See you next weekend 🙂

 

Resources: http://blog.scottlogic.com/2015/03/04/webdriverjs-and-promises.html

https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs

http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/promise.html

 

%d bloggers like this: