Jasmine, EnvJS, and OpenLayers
During the FOSS4G 2011 code sprint I played around with Jasmine and EnvJS. I wanted to take a look at Jasmine, as an alternative to Test.AnotherWay, and experiment with running OpenLayers tests outside the browser, in an headless way.
A bit of context
So we currently use the Test.AnotherWay testing framework for OpenLayers. Test.AnotherWay has been serving us pretty well, but it has downsides:
- Its UI isn't of the best. E.g. running a specific test file involves looking up that test file in a long scrollable list.
- Each test function must call t.plan to provide the framework with the number of assertions "planed" in this function. This may be useful, but also annoying when actually writing the tests. To my knowledge other testing frameworks don't have this requirement.
- Test files are loaded in a hidden frame. This has been causing up problems in IE and FF: getComputedStyle doesn't work properly in certain browsers, making it impossible to write certain tests; see also Handler/Box.html.
- Some tests very often fail because of timing issues. These tests may pass when run again, but not always. This is a real pain, and prevents us from being able to do continuous testing.
- Running all the tests takes approximately 7 minutes in FireFox 7. Although that is somewhat acceptable it would be a very good thing to reduce that.
Jasmine
I provide here some instructions on how to set up Jasmine in OpenLayers, and write test files (k.a. spec files in the Jasmine jargon).
- create directory "jasmine" in "tests":
$ cd openlayers/tests $ mkdir jasmine
- download the Jasmine zip and unzip it:
$ cd jasmine $ wget http://pivotal.github.com/jasmine/downloads/jasmine-standalone-1.0.2.zip $ unzip jasmine-standalone-1.0.2.zip
- create directory spec/Layer
$ mkdir spec/Layer
- create file spec/Layer/Grid.spec.js with the following content:
// add a toBeInstance matcher (should be defined elsewhere) beforeEach(function() { this.addMatchers({ toBeInstanceOf: function(type) { return this.actual instanceof type; } }) }); describe('Layer.Grid', function() { var layer; describe('when applying new', function() { beforeEach(function() { layer = new OpenLayers.Layer.Grid( '', 'http://vmap0.tiles.osgeo.org/wms/vmap0', {layers: 'basic', format: 'image/png'}); }); it('creates a layer grid instance', function() { expect(layer).toBeInstanceOf(OpenLayers.Layer.Grid); }); it('sets "buffer" as expected', function() { expect(layer.buffer).toEqual(0); }); it('sets "ratio" as expected', function() { expect(layer.ratio).toEqual(1.5); }); it('sets "numLoadingTiles" as expected', function() { expect(layer.numLoadingTiles).toEqual(0); }); }); it('can register "tileloaded" listeners', function() { layer = new OpenLayers.Layer.Grid( '', 'http://vmap0.tiles.osgeo.org/wms/vmap0', {layers: 'basic', format: 'image/png'}); layer.events.register('tileloaded', {}, function() {}); expect(layer.events.listeners['tileloaded'].length).toEqual(1); }); }); - open SpecRunner.html and add the following two lines (at proper locations):
<script type="text/javascript" src="../OLLoader.js"></script> <script type="text/javascript" src="spec/Layer/Grid.spec.js"></script>
- open /tests/jasmine/SpecRunner.html in your browser
Jasmine + EnvJS
Going on to attempting to run OpenLayers tests outside the browser...
After struggling a bit with node.js and jasmine-node I gave EnvJasmine a try. It looked good initially, but I quickly realized that it had serious bugs, and that is wasn't maintained anymore.
Here are the steps to run our Grid.spec.js test file with EnvJasmine:
- clone EnvJasmine from Github (anywhere):
$ git clone https://github.com/trevmex/EnvJasmine.git
In the rest we assume that the EnvJasmine clone is in the directory as OpenLayers. - create an OpenLayers build that includes everything but OpenLayers.js and Firebug:
$ cd openlayers/build $ cat > envjasmine.cfg << EOF [first] [last] [include] [exclude] OpenLayers.js Firebug EOF $ python build.py envjasmine.cfg OpenLayers.js
- tell EnvJasmine where the test files are:
$ cd ../../EnvJasmine $ echo 'EnvJasmine.specsDir = EnvJasmine.rootDir + "/../openlayers/tests/jasmine/spec";' >> include/dependencies.js
- make EnvJasmine load OpenLayers:
$ cd ../../EnvJasmine $ echo 'EnvJasmine.load(EnvJasmine.rootDir + "/../openlayers/build/OpenLayers.js");' >> include/dependencies.js
- change the content of EnvJasmine/lib/envjasmine.html as follows:
$ cat > lib/envjasmine.html << EOF <html> <head> </head> <body> <div id="map"></div> <script type="text/envjs"> var map = document.getElementById('map'); map.setAttribute('style', 'display:block;width:600px;height:400px'); jasmine.getEnv().execute(); </script> </body> </html> EOF - run the Grid.spec.js tests:
$ ./bin/run_test.sh [ Envjs/1.6 (Rhino; U; Linux i386 2.6.32-33-generic-pae; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13 ] Loading: /home/elemoine/public_html/openlayers/openlayers/tests/jasmine/spec/Layer/Grid.spec.js ..... Passed: 5 Failed: 0 Total : 5
Unfortunately EnvJS has serious bugs when setting style properties on a DOM element. See https://github.com/thatcher/env-js/pull/19, which was created one year ago! This bug, and the fact that it's still unresolved, has made me move away from EnvJS.
Jasmine + PhantomJS
My current hope is Jasmine + PhantomJS. PhantomJS is a headless webkit with a JavaScript API.
Install PhantomJS on Debian/Ubuntu (for other OSes http://code.google.com/p/phantomjs/wiki/Installation and http://code.google.com/p/phantomjs/wiki/BuildInstructions):
$ sudo apt-get install libqt4-dev libqtwebkit-dev qt4-qmake $ git clone git://github.com/ariya/phantomjs.git && cd phantomjs $ git checkout 1.3 $ qmake-qt4 && make
Get my Jasmine spec runner, and my PhantomJS script:
$ git clone https://github.com/elemoine/openlayers.git $ cd openlayers $ git checkout jasmine
Run the PhantomJS script:
$ cd tests/jasmine $ /path/to/phantomjs/bin/phantomjs phantomjs-script.js
