Friday, March 21, 2014

Polymer Page Objects in Dart


I remain skeptical that Page Objects are a good idea for long term maintenance of a test suite. But after last night, they sure seem like they can come in handy.

So tonight, I give them a try in my Dart test suite for a simple <x-pizza> Polymer element:



I start a new test.dart with a Page Object wrapper for <x-pizza> elements. To begin with, I only need a getter that extracts the current pizza state from a pre#state element inside the Polymer element:
class XPizzaComponent {
  PolymerElement el;
  XPizzaComponent(this.el);
  String get currentPizzaStateDisplay => el.$['state'].text;
}
Then, I do the usual Polymer.dart test setup, calling initPolymer(), adding my <x-pizza> element to the test page, and finally creating a Page Objects instance:
main() {
  initPolymer();

  PolymerElement _el;
  XPizzaComponent xPizza;

  setUp((){
    _el = createElement('<x-pizza></x-pizza>');
    document.body.append(_el);

    xPizza = new XPizzaComponent(_el);
  });
  // ...
}
With that, I can write my first Page Object test as:
  group("[defaults]", (){
    test('it has no toppings', (){
      var no_toppings = JSON.encode({
        'firstHalfToppings': [],
        'secondHalfToppings': [],
        'wholeToppings': []
      });

      expect(
        xPizza.currentPizzaStateDisplay,
        no_toppings
      );
    });
  });
That fails because this is an older version of the Polymer element that is not JSON encoding the pizza state:
CONSOLE MESSAGE: FAIL: [defaults] it has no toppigns
  Expected: '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":[]}'
    Actual: 'First Half: []
'   
    'Second Half: []
'   
To get the test passing, I update my Polymer class to properly JSON encode the pizza state:
import 'package:polymer/polymer.dart';
import 'dart:convert';

@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  Pizza model;
  @observable String pizzaState;

  XPizza.created(): super.created() {
    // ...
    updatePizzaState();
  }

  updatePizzaState([_]) {
    pizzaState = JSON.encode({
      'firstHalfToppings': model.firstHalfToppings,
      'secondHalfToppings': model.secondHalfToppings,
      'wholeToppings': model.wholeToppings
    });
  }
  // ...
}
Now comes the fun part.

I need to be able to select items from the <select> list in my Polymer element. That was a pain in the JavaScript version of this element. Thankfully, the Page Object version of the addWholeTopping() method in Dart is pretty straight-forward. At least, it is straight-forward having the JavaScript version to reference. The Dart version is:
class XPizzaComponent {
  PolymerElement el;
  XPizzaComponent(this.el);
  String get currentPizzaStateDisplay => el.$['state'].text;
  XPizzaComponent addWholeTopping(String topping) {
    var toppings = el.$['wholeToppings'],
        select = toppings.query('select'),
        button = toppings.query('button');

    var index = -1;
    for (var i=0; i<select.length; i++) {
      if (select.options[i].value == topping) index = i;
    }
    select.selectedIndex = index;

    var event = new Event.eventType('Event', 'change');
    select.dispatchEvent(event);

    button.click();

    return this;
  }
}
That is more or less copied-and-pasted from the JavaScript version of the Page Object. As in JavaScript, I find that I have to dispatch a change event to the <select> element. That aside, this method is straight procedural code selecting an item and clicking a button.

Armed with that, I can write the next test as:
  group("[adding toppings]", (){
    setUp((){
      var _completer = new Completer();
      Timer.run(_completer.complete);
      return _completer.future;
    });

    test('updates the pizza state accordingly', (){
      xPizza.addWholeTopping('green peppers');

      var toppings = JSON.encode({
        'firstHalfToppings': [],
        'secondHalfToppings': [],
        'wholeToppings': ['green peppers']
      });

      new Timer(
        new Duration(milliseconds: 100),
        expectAsync((){
          expect(
            xPizza.currentPizzaStateDisplay,
            toppings
          );
        })
      );
    });
  });
As I found in the JavaScript version of the tests, I have to introduce some minor time delays to allow the Polymer element to draw or update itself. But with carefully placed Timer runs, I have a second passing test:
PASS: [defaults] it has no toppings
PASS: [adding toppings] updates the pizza state accordingly
Those Timer delays are a little on the ugly side, but Page Objects again play very well with testing my Polymer element. There may be something to these Page Object things after all.


Day #10

No comments:

Post a Comment