Thursday, December 11, 2014

Kicking the Tires on Polymer and The Intern


One of the things that I love about JavaScript is the dynamic ecosystem that exists just about everywhere. Of course, that's also the thing that I hate most about it.

It is thrilling that so much cool, new stuff pops up daily. It is especially fun for someone who needs to learn something every day lest the gods smite him (or her). That said, it is frustrating when new best practices pop up with alarming frequency—especially for things that ought to be solved problems like testing.

Along those lines, I was recently accused (in the nice, playful way) of being stuck in 2012 by testing my Polymer JavaScript with Karma. I will say this for Karma: it works and is easy to get running under CI services. So there is something to be said for 2012. That said, it is almost 2015 so perhaps there are better options out there, like The Intern.

I start by copying a Karma tested Polymer project and removing Karma configuration and dependency references. Next I install Intern:
npm install intern --save-dev
...
intern@2.2.2 node_modules/intern
...
Next, I update the NPM package.json to include an Intern test runner:
{
  "name": "plain_old_forms",
  "devDependencies": {
    "grunt": "~0.4.0",
    "grunt-contrib-watch": "~0.5.0",
    "intern": "^2.2.2"
  },
  "scripts": {
    "postinstall": "bower install",
    "test": "intern-runner config=tests/intern"
  }
}
I copy the default test config:
$ mkdir tests
$ cp node_modules/intern/tests/example.intern.js tests/intern.js
Normally in that configuration, I would need to list AMD compatible modules to be loaded into each test:
  // ...
 loader: {
  // Packages that should be registered with the loader in each testing environment

  packages: [
   { name: 'todo', location: 'js' },
   { name: 'jquery', location: 'bower_components/jquery' },
   { name: 'underscore', location: 'bower_components/underscore' },
   { name: 'backbone', location: 'bower_components/backbone' },
   { name: 'backboneStorage', location: 'bower_components/backbone.localStorage' }
  ]

 },
 // ...
As far as I know there is nothing AMD compatible in Polymer, so I leave this blank instead:
 // ...
 loader: { },
 // ...
I have yet to write a test, but I start Intern anyway to see where I am at:
$ SAUCE_USERNAME=eee-c SAUCE_ACCESS_KEY=<my-key> ./node_modules/.bin/intern-runner config=tests/intern 
Listening on 0.0.0.0:9000
Starting tunnel...
...
Starting secure remote tunnel VM...
...
Secure remote tunnel VM is now: booting
Secure remote tunnel VM is now: running
...
Starting Selenium listener...
Establishing secure TLS connection to tunnel...
Selenium listener started on port 4444.
Connection established.
Ready
Error: Failed to load module tests/functional from /home/chris/repos/polymer-book/play/theintern/tests/functional.js (parent: *34)
  at <node_modules/intern/node_modules/dojo/dojo.js:757:12>
  at <fs.js:207:20>
  at Object.oncomplete  <fs.js:107:15>
Where I am at is that I need to write a test, which is good news. Connectivity to Sauce Labs seems to just work—always nice not to have to start my own Selenium Server (ick).

So I actually add a first pass at testing my <x-pizza> Polymer element. I am pretty sure this is going to need to be a functional test, I save it as tests/functional.js:
define([
  'intern!object',
  'intern/chai!assert',
  'require'
], function (registerSuite, assert, require) {
  registerSuite({
    name: 'index',

    '<x-pizza>': function () {
      return this.remote
        .get(require.toUrl('../index.html'))
        .setFindTimeout(5000)
        .findByTagName('x-pizza')
        .getProperty('shadowRoot')
        .then(function (doc) {
           assert.isDefined(doc);
         });
        }
    });
});
The test in this case simply tried to assert that my <x-pizza> Polymer element has a shadow DOM.

After adding this test to the intern.js configuration:
  // ...
  functionalSuites: [ 'tests/functional' ],
  // ...
I am ready to try my test again.

But that does not seem to work. The test process just hangs:
$ SAUCE_USERNAME=eee-c SAUCE_ACCESS_KEY=<my-key> ./node_modules/.bin/intern-runner config=tests/intern
Listening on 0.0.0.0:9000
...
Secure remote tunnel VM is now: booting
Secure remote tunnel VM is now: running
...
Starting Selenium listener...
Establishing secure TLS connection to tunnel...
Selenium listener started on port 4444.
Connection established.
Ready
This may have been a lack of patience on my part (though I did wait for more than 2 minutes). Eventually I was able to get this working by fiddling with the environments value in intern.js I had copied in a local value when I should have been using a Sauce Labs compatible version:
 environments: [
  { browserName: 'chrome', version: '34', platform: [ 'Linux' ] }
            ]
With that, I am able to get the test to run, though it still fails:
Test main - index - <x-pizza> FAILED on chrome 34.0.1847.116 on Linux:
StaleElementReference: [POST http://(redacted)@localhost:4444/wd/hub/session/239770a379f045a2ad8d3b098e9e05df/execute / {"script":"return arguments[0]
[arguments[1]];","args":[{"ELEMENT":"0.7332835071720183-1"},"shadowRoot"]}] stale element reference: element is not attached to the page document
  (Session info: chrome=34.0.1847.116)
...
=============================== Coverage summary ===============================
Statements   : 19% ( 19/100 )
Branches     : 0% ( 0/20 )
Functions    : 32% ( 8/25 )
Lines        : 20.43% ( 19/93 )
================================================================================
chrome 34.0.1847.116 on Linux: 1/1 tests failed

-----------------|-----------|-----------|-----------|-----------|
File             |   % Stmts |% Branches |   % Funcs |   % Lines |
-----------------|-----------|-----------|-----------|-----------|
   elements/     |        19 |         0 |        32 |     20.43 |
      x_pizza.js |        19 |         0 |        32 |     20.43 |
-----------------|-----------|-----------|-----------|-----------|
All files        |        19 |         0 |        32 |     20.43 |
-----------------|-----------|-----------|-----------|-----------|

TOTAL: tested 1 platforms, 1/1 tests failed
Unfortunately, my attempts to get this fixed do not go well. I believe that the stale element reference is due to gaining a reference to the <x-pizza> element before Polymer upgrades it (Sauce Lab replays would seem to confirm this). I try to wait for the <body> element to lose its unresolved attribute, which Polymer removes when it is loaded:
    '<x-pizza>': function () {
      return this.remote
        .get(require.toUrl('../index.html'))
        .setFindTimeout(5000)
        .findByXpath('//body[not(@unresolved)]')
        .findByTagName('x-pizza')
        .getProperty('shadowRoot')
        .then(function (doc) {
           console.log(doc);
           assert.isDefined(doc);
         });
    }
Again, Sauce Lab replays would seem to confirm that this has the desired effect—immediately after that step passes, the display starts to fade-in. But I still get the stale element error.

My best guess is that, even though Polymer is loaded, it needs just a moment longer before it replaces the original <x-pizza> with the version that I am trying to test. Unfortunately, if brief delays are easy in Intern, I cannot find them. So I call it a night here and will pick back up with the effort tomorrow.

Day #21

No comments:

Post a Comment