September 10, 2015

NATIONAL SWAP

IDEAS DAY

Introduction

My name is Tomas Miliauskas

Main hobbies...

Wix Hotels and Guild activities in Lithuania

JavaScript, Node and Stuff like that

Responsibilities...

Reach me out on...

tomasm@wix.com

Skype: tqmukas

email/hangouts:

Simplify Protractor Tests

Wix Hotels & TDD

The numbers...

1 year 6 month

~2050 client side tests

85-90% Coverage

(about half are e2e)

Issues with tests code

  • Doesn't look nice

  • Not self explaining

  • Lots of code duplication

  • Hard to maintain

We started to use...

Page Objects

Tests Code (only)

Page Object

(selectors & helpers)

Protractor

Eventually we created...

hotels-world-statics-e2e-utils 

...library

  • Page

  • Elem

  • BI

  • Browser (coming soon)

Library Structure

Page reference

  • .extend(constructor, props)

  • .experiments(obj)

  • .modify(what, how)

  • .addMock(name, mock)

  • .addMockOnce(namemock)

  • .addMocks(mocks)

  • .removeMock(name)

  • .open(url, query, fragment);

  • .refresh()

  • .clearLocalStorage()

  • .setLocalStorageItem(key, value)

  • .closeNewWindow()

Elem reference

  • .elem(locator, properties)

  • .elem.many(locator, properties)

Elem instance

var elem = require('hotels-world-statics-e2e-utils').elem
var field = elem('.shiny-field');
var country = elem(by.model('ctrl.country')); 
var slides = elem.many('.blury-slide');

slides.get(index);
slides.first();
slides.last();
slides(index);

field.enter(text);
field.obtain('width');
field.obtain('class');

country.optionByValue(value);
country.optionByLabel(label);
country.obtain('selectedValue');

Example 1

Testing a feature toggle

Regular way

'use strict';

describe('ICal scenarios', function() {
  it('should not show iCal url field when experiment is disabled', function () {
    browser.addMockModule('experimentsMock', function() {
      angular.module('experimentsMock', []).value('experiments', {ICalField: false});
    });
    browser.get('/ical');
    expect(element(by.model('ctrl.icalLink')).isPresent()).toBe(false);
  });

  it('should show iCal url field when experiment is enabled', function () {
    browser.addMockModule('experimentsMock', function() {
      angular.module('experimentsMock', []).value('experiments', {ICalField: true});
    });
    browser.get('/ical');
    expect(element(by.model('ctrl.icalLink')).isDisplayed()).toBe(true);
  });
});

Creating a page object

'use strict';

var Page = require('hotels-world-statics-e2e-utils').page;

function ICalPage() {
  Page.call(this, '/ical');
}

module.exports = Page.extend(ICalPage, {
  back: elem('.go-back'),
  discard: elem('.btn-cancel'),
  save: elem('.btn-save'),
  room: elem('.room', {
    errors: elem('errors')
  }),
  url: elem(by.model('ctrl.icalLink')),
  hint: elem('.hint', {
    link: elem('.info a')
  })
});

Writing tests

'use strict';

describe('ICal scenarios', function() {
  var page = new (require('./pages/ICalPage'))();

  it('should not show iCal url field when experiment is disabled', function () {
    page.experiments({ICalField: false}).open();
    expect(page.url().isPresent()).toBe(false);
  });

  it('should show iCal url field when experiment is enabled', function () {
    page.experiments({ICalField: true}).open();
    expect(page.url().isDisplayed()).toBe(true);
  });
});

Example 2

Testing values

Regular way

'use strict';

describe('Rooms scenarios', function() {
  it('should render room list with appropriate data', function () {
    browser.get('/rooms/list');
    expect(element.all(by.bind('room.name')).get(0).getText()).toBe('Room1');
    expect(element.all(by.bind('room.name')).get(1).getText()).toBe('Room3');
    expect(element.all(by.bind('room.name')).get(2).getText()).toBe('Room2');
    expect(element.all('.item-big img').get(0).isDisplayed()).toBe(true);
    expect(element.all('.item-big img').get(1).isDisplayed()).toBe(true);
    expect(element.all('.item-big img').get(2).isDisplayed()).toBe(true);
  });
});

Creating a page object

'use strict';

var Page = require('hotels-world-statics-e2e-utils').page;

function RoomsPage() {
  Page.call(this, '/rooms');
}

module.exports = Page.extend(RoomsPage, {
  list: elem.many(by.repeater('room in rooms'), {
    img: elem('.left-part img'),
    title: elem(by.binding('::room.name')),
    amount: elem(by.binding('::room.noOfRooms')),
    edit: elem('.actions-bar .btn-icon[ui-sref*=edit]'),
    copy: elem('.actions-bar .btn-icon[ui-sref*=copy]'),
    delete: elem('.actions-bar .btn-icon[ng-click="deleteRoom(room.roomId)"]'),
    tooltip: elem('.tooltip'),
    handle: elem('.btn-drag')
  }),
});

Writing tests

'use strict';

describe('Rooms scenarios', function() {
  var rooms = new (require('./pages/RoomsPage'))();

  it('should render room list with appropriate data', function () {
    page.open();
    expect(rooms.list(0).title().obtain('text')).toBe('Room1');
    expect(rooms.list(1).title().obtain('text')).toBe('Room3');
    expect(rooms.list(2).title().obtain('text')).toBe('Room2');
    expect(rooms.list(0).img().isDisplayed()).toBe(true);
    expect(rooms.list(1).img().isDisplayed()).toBe(true);
    expect(rooms.list(2).img().isDisplayed()).toBe(true);
  });
});

Questions?

Cheers!

Simplify Protractor Tests

By Tomas Miliauskas

Simplify Protractor Tests

  • 1,346