Thursday, December 18, 2014

A First Attempt at E2E Testing of Polymer with Protractor


I continue my experiments using Protractor to test Polymer elements. I do not expect to get this working completely. Instead I am trying to understand what end-to-end testing of a Polymer element might look like. I also hope to understand where the gaps still exist between current WebDriver tools and real end-to-end testing.

Mostly, I am just having fun.

Tonight's fun takes the form of trying to convert last night's unit test into a real e2e Polymer test. The test that I wound up with is almost pretty:
  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');
  });
A beforeEach() block loads the page containing the element to be tested: the <x-pizza> pizza building Polymer element. This test then adds pepperoni to the first half and then expects that the element's value property is updated to include pepperoni in it.

This is not a real unit test, in that it does not test a single method of the Polymer element. It is also not a true e2e test because of the executeScript() call that manipulates the innards of the Polymer element. This is something in between. It is a functional test (it tests functionality across concerns), but it still lies closer to unit test than e2e, which is what Protractor wants to do.

The version of the <x-pizza> element being tested here looks like:



(I switched to a Material Design later)

If those drop downs were not part of <x-pizza>, I might tell protractor to interact with them by replacing the executeScript() work-under-the-covers with something like:


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

    $('#firstHalf options').
      then(function(options){
        options[1].click();
      });

    $('#firstHalf button').
      then(function(button){
        button.click();
      });
But, since WebDriver (and hence Protractor) does not support the shadow DOM, I get an error like:
  1) <x-pizza> updates value when internal state changes
   Message:
     NoSuchElementError: No element found using locator: By.cssSelector("#firstHalf options")
So what is a fellow to do? Well, hopefully recreate this with executeScript:
    browser.executeScript(
      'var el = document.querySelector("' + selector + '"); ' +
      'var options = el.shadowRoot.querySelectorAll("#firstHalf option"); ' +
      'options[1].selected = true; ' +
      'button = el.shadowRoot.querySelector("#firstHalf button"); ' +
      'button.click(); '
    );
Ick. This code still finds the <x-pizza> element, but now is selects the second option (pepperoni) and clicks the “Add Topping” button.

I suppose that is not too horrible, but I sure hate all that coding inside JavaScript strings. Especially since it does not even work:
  1) <x-pizza> updates value when internal state changes
   Message:
     Expected '

' to match 'pepperoni'.
If I add a browser.wait(30*1000) (waiting 30 seconds is my preferred debugging methodology in Protractor), I can see that this code would seem to do what I expect. The pepperoni option is selected. The first half topping button is clicked. But I still get a failure. More importantly, I get a code failure in the JavaScript console of the waiting browser.

The problem occurs in the code that adds unknown toppings. I will fix that another day (my tests found a bug!). For now, I need to figure out why this is trying to add an unknown topping when I clearly added pepperoni. Thankfully, I have already solved this. Polymer needs a change event to be triggered before it will update its bound values:
    browser.executeScript(
      'var el = document.querySelector("' + selector + '"); ' +
      'var options = el.shadowRoot.querySelectorAll("#firstHalf option"); ' +
      'options[1].selected = true; ' +
      '' +
      'var e = document.createEvent("Event"); ' +
      'e.initEvent("change", true, true); ' +
      'options[1].dispatchEvent(e); ' +
      '' +
      'button = el.shadowRoot.querySelector("#firstHalf button"); ' +
      'button.click(); '
    );
That actually gets my test working:
$ protractor conf.js
Using the selenium server at http://localhost:4444/wd/hub
[launcher] Running 1 instances of WebDriver
...

Finished in 1.363 seconds
3 tests, 6 assertions, 0 failures
That is a good stopping point for tonight. I have learned that it is possible to write actual end-to-end tests for Polymer. I also learned that it is pretty ugly doing so inside Protractor browser.executeScript() blocks. Not only is it ugly, but there is definite gap between Protractor/WebDriver and a Polymer e2e testing tool. The lack of access to the shadow DOM is one problem, but now I see that Polymer events are also tricky.

Those problems aside, I also see definite advantages to e2e testing of Polymer elements—the most obvious being the bug that I found tonight by interacting with my Polymer element like a user would. And yes, I am still having fun.


Day #28

No comments:

Post a Comment