Even after a week or so of working with Protractor as testing solution for Polymer, I still do not know if I have a good feel for recommending it as a solution. I begin to understand its strengths and weaknesses as a Polymer testing tool—its async support works brilliantly with Polymer while its complete lack of shadow DOM support is nearly a show stopper.
Nearly a show stopper, but when I can write tests like this:
describe('<x-pizza>', function(){ beforeEach(function(){ browser.get('http://localhost:8000'); }); it('updates value when internal state changes', function() { new XPizzaComponent(). addFirstHalfTopping('pepperoni'); expect($('x-pizza').getAttribute('value')). toMatch('pepperoni'); }); });How can I dismiss it? To be sure, the
XPizzaComponent
Page Object helps to mask some ugliness—but the same test in Karma has just as much shadow DOM ugliness, plus async ugliness.I have already tried TDDing a bug fix with Protractor, which went reasonably well. So tonight I wonder what it is like TDDing a new feature? I have been playing with an early version of the
<x-pizza>
pizza building Polymer element as I explore these questions:This version of the element has the exploration advantage of including some old-timey HTML form elements with which to play. The advantage here is that I made old-timey mistakes in the backing code. The bug that I TDD'd the other day resulted from adding unknown toppings to the pizza. I eliminated the bug, but it is still possible to click the “Add First Half Topping” button without choosing a topping. I would like to change that. So I write a Protractor test:
describe('<x-pizza>', function(){ beforeEach(function(){ browser.get('http://localhost:8000'); }); // ... it('does nothing when adding a topping w/o choosing first', function(){ new XPizzaComponent(). clickAddFirstHalfToppingButton(); expect($('x-pizza').getAttribute('value')). toEqual('\n\n'); }); });I need to write the
clickAddFirstHalfToppingButton()
method for my page object. This is already done as part of the addFirstHalfTopping()
method, but now I need it separate:function XPizzaComponent() { this.selector = 'x-pizza'; } XPizzaComponent.prototype = { // ... clickAddFirstHalfToppingButton: function() { browser.executeScript(function(selector){ var el = document.querySelector(selector), button = el.$.firstHalf.querySelector("button"); button.click(); }, this.selector); } };The
executeScript()
method is hackery to work around Protractor's lack of shadow DOM support. It is not too horrible, but I am extremely tempted to factor the el
assignment out into its own method, especially since both addFirstHalfTopping()
and clickAddFirstHalfToppingButton()
both assign it in the exact same way:XPizzaComponent.prototype = { addFirstHalfTopping: function(topping) { browser. executeScript(function(selector, v){ var el = document.querySelector(selector), select = el.$.firstHalf.querySelector("select"); }, this.selector, topping); }, clickAddFirstHalfToppingButton: function() { browser. executeScript(function(selector){ var el = document.querySelector(selector), button = el.$.firstHalf.querySelector("button"); // ... }, this.selector); } };The problem with trying to factor this out is that I am obtaining the element inside an anonymous function that is called by
browser.executeScript()
and the reason that I am doing this is because regular Protractor locators cannot find Polymer elements. The best that I can come up with is:XPizzaComponent.prototype = { el: function(){ return browser.executeScript(function(selector){ return document.querySelector(selector); }, this.selector); }, // ... clickAddFirstHalfToppingButton: function() { browser. executeScript(function(el){ var button = el.$.firstHalf.querySelector("button"); button.click(); }, this.el()); } });This is not too horrible, I suppose. But I am writing as much test code (and working as hard to write it) as I am with the actual code.
Regardless, I have a failing test:
$ protractor --verbose Using the selenium server at http://localhost:4444/wd/hub [launcher] Running 1 instances of WebDriver <x-pizza> has a shadow DOM - pass updates value when internal state changes - pass syncs <input> values - pass does nothing when adding a topping w/o choosing first - fail Failures: 1) <x-pizza> does nothing when adding a topping w/o choosing first Message: Expected 'unknown ' to equal ' '. Stacktrace: Error: Failed expectation at [object Object].<anonymous> (/home/chris/repos/polymer-book/play/protractor/tests/XPizzaSpec.js:30:7) Finished in 2.032 seconds 4 tests, 4 assertions, 1 failureI can make that pass easily enough. My Polymer element's
addFirstHalf()
method simply needs a guard clause:Polymer('x-pizza', { // .. addFirstHalf: function() { if (this.currentFirstHalf == '') return; this.model.firstHalfToppings.push(this.currentFirstHalf); }, // ... });With that, I have my passing test:
$ protractor --verbose Using the selenium server at http://localhost:4444/wd/hub [launcher] Running 1 instances of WebDriver <x-pizza> has a shadow DOM - pass updates value when internal state changes - pass syncs <input> values - pass does nothing when adding a topping w/o choosing first - pass Finished in 2.012 seconds 4 tests, 4 assertions, 0 failuresAnd a nice new feature.
I normally do not care for refactoring tests, so the difficulty experienced tonight really should not bother me. But it does. The lack of shadow DOM support makes me think that I will need to write many helpers—if not an entire library—to make Protractor and Polymer really play nicely. If I struggle with the simple stuff like getting the Polymer element itself, this does not bode well for more in-depth testing adventures. I may play with this some more over the next few days, but I am leaning toward not using Protractor at this point.
Day #34
google 2875
ReplyDeletegoogle 2876
google 2877
google 2878
google 2879