technology

My Love-hate Relationship with PhantomJS

When I search our Slack history for “PhantomJS,” it yields 304 results.  That number is rather meaningless out of context, so for a little color, “AngularJS” has 126 hits, “Python” 1,237, and “Flask” 208.  I think that last one is mostly about the Python framework, and not about drinking out of small containers 🙂

The above are all technologies we use heavily, but I find it interesting that we talk so much more about our headless browser for testing than we do about the frameworks that we actually build our products in.  I guess that tells you something about how dogmatic we are about testing at Jana, but it’s probably more because of how much of a PITA PhantomJS can be.  Don’t get me wrong, I love that PhantomJS allows us to run all of our unit and integration tests seamlessly in our deployment pipeline.  We basically couldn’t do our work without it, and I’m grateful for its existence.  However, I somewhat regularly find myself banging my head against the wall because of it.

Here’s a sampling of some of our most recent PhantomJS Slack mentions:

phantomjs2 doesn’t like that

it might fail in phantomjs

phantomjs isn’t rendering properly

…crashes with phantomjs2

because phantomjs

… is not supported in phantomjs

ugh, this integration test i wrote won’t work in phantomjs

seems like a memory leak issue with phantomjs

but phantomjs is having lots of issues

there’s an OSX El Capitan issue with brew and phantomjs

That list goes on and on.

We recently had to pull the trigger and make the upgrade from PhantomJS 1.9.x to version 2.  There’s a lot of stuff that we use (flexbox, ES2015, and more) that was abysmally supported in versions prior to 2, so our tests weren’t accurately reflecting our production environment.  We’ve spent a bunch of time debugging issues with the upgrade, so I thought I’d put a few things together that I found helpful.  Note: some of these may be less important now as support for version 2 amongst package managers and task runners has become more robust.

El Capitan issue:

phantomjs: OS X Yosemite or older is required.

😦

This post helped me out when we were having issues installing PhantomJS 2 via Homebrew on our local machines after an OSX upgrade, when @Vitallium posted a compiled version to install.

The other thing that helped me out with this issue for our remote machines was this post, when someone pointed out that while Homebrew didn’t yet support the newest version, NPM did, so this did the trick:

npm install phantom phantomjs -g

Jasmine test memory leak:

After an AngularJS version upgrade to 1.5.x (we’re still preparing for the 2.x plunge), we noticed that PhantomJS started crashing.  Officially, the AngularJS team notes that “PhantomJs isn’t among the officially supported browsers.”  While that may  be the case, we need it, so we didn’t have a choice but to fix it.

Some people had suggested breaking up tests into separate runs to avoid memory issues, but that didn’t seem very sustainable to me.  Then I found this Stack Overflow post, which had an awesome way to refactor your Jasmine tests to avoid cross-test memory leaks by properly cleaning them up after each individual test, and then after each suite of tests.  You add all of your required dependencies and global variables to a test suite-scoped object, and then remove them at the end.  You also clean up compiled directives after each run.  Here’s an example:

'use strict';

describe('My Directive unit tests', () => {

  let suite = {};

  beforeEach(module('launch',
                    'directives/my_directive/my_directive.html'));

  beforeEach(inject((_$rootScope_, _$compile_) => {
    suite.$compile = _$compile_;
    suite.scope = _$rootScope_;
    suite.scope.myData = {
      someGlobally: "available data"
    };
    suite.elementHtml = '<my-directive></my-directive>';
  }));

  afterEach(() => {
    suite.element.remove();
  });

  afterAll(() => {
    suite = null;
  });

  function compileDirective(scope, elementHtml) {
    let element = angular.element(elementHtml),
    el = suite.$compile(element)(scope);
    scope.$digest();
    return el;
  };

  it('should show some text', () => {
    suite.element = compileDirective(suite.scope, suite.elementHtml);
    expect(suite.element.html()).toContain('some text');
  });

});

 

 

Personally, I’d much rather write my tests in a sane and clean way than just continue to break them up into grouped runs.  This worked well for us.

Karma PhantomJS Launcher Issues

This one isn’t really relevant now, but I thought I’d bring it up because I know a lot of other people encountered the same problem.  We run our Jasmine tests with Karma, which we use through Grunt.  We need Karma PhantomJS Launcher to run the tests with PhantomJS, but much like Homebrew, PhantomJS v2 support lagged pretty hard.  It was really helpful when gskachkov forked the runner and added support.  Thankfully, the original version has since added support (2.1.3 at the time of this writing), so you can now go that route.  One thing that is worth noting is that the forked version suggested this Karma config to get it working:

phantomjsLauncher: {
  exitOnResourceError: true
}

 

which totally blew up our tests after the upgrade.  Removing it solved that problem for us.

Ultimately, PhantomJS is a massively helpful tool for us, and like I said above, we couldn’t do a lot of what do without it.  We have several thousand tests for our code, and PhantomJS is a key part of getting them to work.  I personally think that our culture of testing is Jana is excellent, and it’s part of why our engineering team is so successful.  If you want to learn more, shoot us a note.  We’re always looking for smart people🙂

Discussion

One response to “My Love-hate Relationship with PhantomJS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s