Friday, April 3, 2015

Page Objects and web-component-tester

I am a Page Objects fan. I like page objects for testing web pages, applications, and even Polymer. Tonight, I try to use them with the web-component-tester (WCT).

I typically eschew code reuse in my tests. That is not quite strong enough. More accurately, indirection in tests inspires feelings of anger, bordering on rage. I prefer my test code to be right there in the tests, not scattered around a bunch of files. When something is misbehaving in tests, I loathe searching test code to find the problem.

I make an exception in the case of page objects because they are built primarily around user interactions with applications—code reuse is simply a (tolerable) byproduct. Putting myself in the frame of mind of a human interacting with my applications is where I find value.

I start by adding a new suite to last night's multi-suite WCT for the tasty <x-pizza> Polymer element:
<!doctype html>
  <meta charset="utf-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../elements/x-pizza.html">
  <x-pizza id="fixture"></x-pizza>
I have every reason to expect that page objects will work with WCT. Happily, I am not disappointed. I mostly copy my Karma and Jasmine implementation of Page Objects for this element.

The constructor takes a single element argument:
function XPizzaComponent(el) {
  this.el = el;
The methods supported for this element facilitate adding toppings as a human might and reading the results:
XPizzaComponent.prototype = {
  currentPizzaStateDisplay: function() {
    if (this.el.$ === undefined) return '';
    return this.el.$.state.textContent;

  addWholeTopping: function(v, cb) {
    // Select topping from drop-down and click a button here...
    return this.flush(cb);

  flush: function(cb) {
    if (cb === undefined) cb = function(){};
The most important part of this is the flush() method, which accepts a callback parameter. It invokes Polymer's async() method to ensure that all bound variables are updated and that the UI is updated. Once everything has changed, the supplied callback is invoked. It is useful to invoke flush() directly from tests, but even more useful to invoke it from tests that update the UI—as the addWholeToppings() method does.

This is important for asynchronous testing of Polymer elements. I can supply Mocha's done callback to flush() so that subsequent tests will not run until the element has finished updating itself. So, to test that I can update whole toppings, I call the addWholeTopping() method of the page object and supply the done callback:
  describe('adding a whole topping', function(){
      xPizza.addWholeTopping('green peppers', done);

    it('updates the pizza state accordingly', function(){
      var with_toppings = JSON.stringify({
        firstHalfToppings: [],
        secondHalfToppings: [],
        wholeToppings: ['green peppers']

The test itself will not run until the element has completely updated its properties, bound variables, and UI. This is perfect because the test is trying to assert that the element has been updated to include pepperoni.

This works exactly as desired:
Running "wct-test:local" (wct-test) task
Starting Selenium server for local browsers
Selenium server running on port 58383
Web server running on port 39429 and serving from /home/chris/repos/polymer-book/play/web-component-tester
chrome 41                Beginning tests via http://localhost:39429/js/test/index.html?cli_browser_id=0
chrome 41                Tests passed
firefox 37               Beginning tests via http://localhost:39429/js/test/index.html?cli_browser_id=1
firefox 37               Tests passed
Test run ended with great success

chrome 41 (5/0/0)                       firefox 37 (5/0/0)                    

Done, without errors.
I am growing fond of WCT. It supports the various kinds of testing that I might want. I still worry that there might be some deficiencies that might give me pause for using it today in Patterns in Polymer, so I will continue to explore tomorrow.

Day #18

1 comment: