Web Components and PhantomJS

I’ve spent some time recently building a couple of web components using Polymer. I’ve been pretty excited about web components for a while now, even before I had the good fortune of attending Google I/O last year and listening to talks by the likes of Matt McNulty and Eric Bidelman. The web components I’ve built thus far (available on Github here and here) don’t have a UI – their primary purpose is to connect to an API and return information based on the attributes that were set, or by calling the components’ methods directly to get that information.

Testing & Web Components

Testing web component is relatively simple thanks to the web-component-tester tool. I typically create several HTML test files, one for each piece of functionality I want to test. Separating the test cases into different files makes maintenance much easier, especially if you have a lot of test cases.

The problem comes when using web component in other apps, and then subsequently trying to write tests for the apps themselves. The existing testing infrastructure that we use includes PhantomJS with CasperJS. Unfortunately, there’s a fairly big problem – PhantomJS does not support web components.

So, let’s suppose that we have the following code:

var system = require("system");
var e2ePort = system.env.E2E_PORT || 8099;
var url = "http://localhost:"+e2ePort+"/src/widget-e2e.html";

casper.test.begin("e2e Testing", {
  test: function(test) {
    casper.start();

    casper.thenOpen(url, function () {
      test.assertTitle("e2e Testing", "Test page has loaded");
    });

    casper.then(function () {
      casper.waitFor(function waitForCondition() {
        return this.evaluate(function() {
          // Wait for some condition to be true.
        });
      },
      function then() {
        ...
      });
    });
  }
});

In the above example, we first check that the test page has loaded, and then wait for some condition to be true before executing any tests. If the condition that we’re waiting for does not get set until sometime after polymer-ready fires, then we’re in big trouble, because polymer-ready is never going to fire.

What To Do?

To overcome this problem, you can dispatch the polymer-ready event yourself directly from CasperJS. The code to do that might look something like this:

casper.test.begin("e2e Testing", {
  test: function(test) {
    casper.start();

    casper.thenOpen(url, function () {
      test.assertTitle("e2e Testing", "Test page has loaded");
    });

    casper.then(function () {
      casper.evaluate(function () {
        var evt = document.createEvent("CustomEvent");

        evt.initCustomEvent("polymer-ready", false, false);
        window.dispatchEvent(evt);
      });

      casper.waitFor(function waitForCondition() {
        return this.evaluate(function() {
          // Wait for some condition to be true.
        });
      },
      function then() {
        ...
      });
    });
  }
});

After the test page loads, we use casper.evaluate to execute some Javascript that dispatches the polymer-ready event. This will trigger any event listeners to run and, if we’ve done things correctly, the condition will eventually be set to true and our tests will begin executing after that.

What About the Web Component Itself?

Of course, it won’t be long before we hit an even bigger obstacle – calling a method of the web component throws:

Uncaught TypeError: undefined is not a function

This makes sense, since if HTML imports don’t work in PhantomJS, then the code that exists inside of your web components will never load.

The solution I’ve used so far, which is less than ideal, is to write a mock for the public methods of the web component in a separate JS file, and then include that JS file in the HTML test page. This works OK, especially given the fact that I would have had to mock some of those methods anyway due to the API calls that the web components are making. Still, it’s not terribly efficient to be recreating the other bits of the web components’ logic, and I suspect it won’t be long before we need to start looking at other testing tools like Selenium.

What has your experience with testing web components been like? What tools do you use and how are they working out so far? I’d love to hear about it in the comments.