Wednesday, December 17, 2014

Testing Polymer Async with Protractor


I have a fair Protractor test for a Polymer element. It reads fairly well. It actually tests the Polymer element (i.e. it fails if the element is changed). It makes me happy. But it is limited in scope and I worry that expanding that scope will cause things to get messy.

The current test verifies that there is a shadow DOM for my <x-pizza> pizza building element and that the element has a title:
describe('<x-pizza>', function(){
  describe('element content', function(){
    beforeEach(function(){
      browser.get('http://localhost:8000');
    });
    it('has a shadow DOM', function(){
      expect($('[unresolved]').waitAbsent()).toBeTruthy();
      expect($('x-pizza').getShadowRootHtml()).
        toMatch("<h2>Build Your Pizza</h2>");
    });
  });
});
Pretty, right?

There are three problems with it, however. First, the test is simple—it asserts against the initial HTML and I have no idea how well this approach will work with the asynchronous nature of Polymer. Second, this involves no user interaction, which is really the point of Protractor. Last, it lacks the ability to probe the internal state of the Polymer element because of WebDriver's lack of shadow DOM support.

For tonight, I would like to see if Protractor built-in asynchronous support, in the form of promises, will help when working with Polymer elements. The current Karma Jasmine test for <x-pizza> makes assertions on the value after updating the internal state:
describe('properties', function(){
    it('updates value when internal state changes', function(done) {
      el.model.firstHalfToppings.push('pepperoni');
      el.async(function(){
        expect(el.value).toContain('pepperoni');
        done();
      });
    });
  });
To ensure that Karma does not check the assertion until Polymer has had a chance to update the element, I had to set the assertion inside Polymer's async() callback. The supplied callback will be invoked once the most recent Polymer operation has flushed all changes to the screen and DOM. I would definitely prefer to avoid that in my tests since it adds framework complexity to my tests. Also, accessing async() in Protractor would difficult, if not impossible.

I copy that into Protactor-ese, which should look something like:
  it('updates value when internal state changes', function(done) {
    var selector = 'x-pizza';

    browser.executeScript(
      'var el = document.querySelector("' + selector + '"); ' +
      'el.model.firstHalfToppings.push("pepperoni"); '
    );

    expect($(selector).getAttribute('value')).
      toMatch('pepperoni');
  });
As I did last night, I am forced to interact with the element via browser.executeScript() since Protractor / WebDriver does not allow access.

Unfortunately, I get:
A Jasmine spec timed out. Resetting the WebDriver Control Flow.
The last active task was:
unknown
F

Failures:

  1) <x-pizza> properties updates value when internal state changes
   Message:
     timeout: timed out after 30000 msec waiting for spec to complete
   Stacktrace:
     undefined

Finished in 31.427 seconds
2 tests, 5 assertions, 1 failure
There is no expectation error in there, just an "unknown" failure. It takes me longer than I care to admit to realize that I had copied the done callback from the original Karma / Jasmine test into this Protractor version. I simply remove the done argument to the test's function:
  it('updates value when internal state changes', function() {
    var selector = 'x-pizza';

    browser.executeScript(
      'var el = document.querySelector("' + selector + '"); ' +
      'el.model.firstHalfToppings.push("pepperoni"); '
    );

    expect($(selector).getAttribute('value')).
      toMatch('pepperoni');
  });
Which actually works.

And really, aside from the ugliness of the code in browser.executeScript(), this is a nice test. I update the internal state and expect the attribute to change as a result. Brilliant.

Of course, the ugliness of the browser.executeScript() is trying to tell me that I am doing something wrong. In this case, I am manipulating a page through code rather than user interaction. That brings me to the next item on my list of worries, which I will investigate tomorrow.

Day #27

No comments:

Post a Comment