uitest.js is able to load a webpage into a frame, instrument that page and the javascript in that page (e.g. add additional scripts at the end of the document, ...) and execute actions on that page.
uitest.js can be used standalone or with any test framework. However, there is also some syntactic sugar so that it integrates more easily into test frameworks.
- Layout Debugging during test runs:
- Uses an 100% width/height iframe to see the real layout of the application.
- Adds a button to toggle between the iframe and the testrunner.
- Mobile support: Adds/removes a
<meta name="viewport">
tag in the top window depending on the<meta>
tag in the current page to be tested.
- Instrumentations for a page:
- add a script or function at the beginning/end of the page
- intercept calls to any named function on the page, no matter if the function is global or not
- Cache busting feature to always fetch the recent versions of scripts in pages.
- Wait for the end of asynchronous work, e.g. xhr, setTimeout, setInterval, page loading, ... This can be easily extended.
- Easy access to global variables of the app to be tested in the test case using dependency injection.
- Extended integration with frameworks:
- angular.js for mocking backend calls
- mobile sites for automatically adjusting the viewport
- testframeworks: syntactic sugar for Jasmine BDD, ...
- Compatibility:
- Testframeworks: Use any test framework and test runner, can also run standalone.
- Supports applications that use requirejs 2.x.
- Browsers: Chrome, Firefox, IE7+, Safari, Mobile Safari, Android Browser 2.3+.
- Supports running tests from
file://
urls (Safari and chrome with command line argument--disable-web-security
)
- Dependencies:
- No additional JS libs required
- Does not need any additional test server, only a browser to execute the tests
- Supports xhtml on many browsers.
- Not on Fx (as Fx does uses
javascript:
urls in a special way :-( ) - Not on IE<=8, as IE<=8 is not able to load xhtml in an iframe (error XML5632: only one root element is allowed).
- Not on Fx (as Fx does uses
- include uitest.js as library into your test-code.
- In the pages that should be tested, include the following line as first line in the header:
<script type="text/javascript">parent.uitest && parent.uitest.instrument(window);</script>
- create a uitest instance calling
uitest()
. - configure the instance, e.g. setting setting
<uitest>.url('someUrl')
. - run the test page, e.g. by calling
<uitest>.ready
.
Preconditions:
- The page to be tested must be loaded from the same domain as the test code.
See the ui tests under test/ui/*Spec.js
.
Please use this Plunk as starting point.
Directory structure
- compiled: The created versions of uitest.js
- src: The main files of uitest.js
- test/ui: The ui self tests for uitest.js
- test/unit: The unit tests of uitest.js
Install the dependencies: npm install
.
Build it: ./node_modules/.bin/grunt
- set the right path to phantomjs before, e.g.
export PHANTOMJS_BIN=./node_modules/.bin/phantomjs
.
Auto-Run tests when file change: ./node_modules/.bin/grunt dev
uitest.create()
: Creates a new uitest instance.uitest.current
: A singleton that delegates all functions to the "current" uitest instance. This is different for every testframework, see below for details.
At first, every uitest instance is in configuration mode. In this mode, the following methods are available
-
parent(uitest)
: connects this uitest instance with the given parent uitest instance, so that all properties are inherited / merged during runtime. Note that the link is live, i.e. changing the parent after calling this also affects the child. -
url(someUrl)
: Sets the url of the page ot be loaded. If the url is relative, it will be resolved relative to the path of theuitest.js
in the current page. Please note that to prevent caching issues, and also to allow to only change the hash between different tests, all pages will get a further query parameter containing the current time. E.g. setting a url/someUrl
will result in a location of/someUrl?12345
. If you need that query parameter doubled somewhere else in your url, use{now}
. E.g. an url of/someUrl?check={now}
will yield to a location of/someUrl?check=12345&12345
. -
trace(boolean)
: Enables debug logging for several features. -
prepend(someScriptUrl | callback)
: Adds the given script or an inline script that calls the given callback at the beginning of the<head>
of the document to be loaded. The callback is called using dependency injection, see below. -
append(someScriptUrl | callback)
: Adds the given script or an inline script that calls the given callback at the end of the<body>
of the document to be loaded. -
intercept({script: 'someScript', fn: 'someFnName', callback: callback})
Intercepts all calls to functions with the namesomeFnName
in scripts whose filename issomeScript
. The function does not need to be a global function, but may also be a nested function definition. The script name must be provided without a folder, and will match all script urls ignoring their folders too. The callback is called using dependency injection, using the argument names of the original function and all global variables. The argument with the special name$delegate
will contain the following data:{fn: ..., name: 'someFnName, self: ..., args: ...}
, allowing access to the original function, the originalthis
andarguments
properties. The original function can be called by calling the givenfn
function. -
feature('feature1', 'feature2', ...)
Enables the given features.
On the first call to the ready
function on the uitest instance, the iframe gets created,
and all configuration is applied. Now the uitest is in running mode, and the following methods
may be called.
Please note that the iframe is shared between all uitest instances.
-
ready(callback)
: Waits until all ready sensors say the test page is ready. On the first call, this will also load the test page into the iframe. The callback is called using dependency injection. See below for details about ready sensors. -
reloaded(callback)
: Waits until the testpage has been reloaded and is ready again. This is useful for testing pages that do a form submit or link to other pages. The callback is called using dependency injection, see below. -
inject(callback)
: Calls the given callback using dependency injection.
Many methods in the API take a callback whose arguments can use dependency injection. For this, the names of the arguments of the callback are inspected. The values for the callback arguments then are the globals of the test frame with the same name as the arguments.
E.g. a callback that would have access to the jQuery object of the test frame: function($) { ... }
.
The following ready sensors are built-in. To enable them, they have to be enabled as feature:
feature('xhrSensor')
: Waits for the end of all xhr callsfeature('timeoutSensor')
: Waits for the end of allsetTimeout
callsfeature('intervalSensor')
: Waits for the end of allsetInterval
calls (viaclearInterval
).feature('jqmAnimationSensor')
: waits for the end calls to$.fn.animationComplete
(jQuery mobile event listener for css3 animations).
To make it easier to use uitest together with jasmine, a separate uitest instance
is created for every suite and every spec. The uitest instance for a spec/suite
inherits from the uitest instance of the parent suite using the parent
instance.
The following additional functions exist for Jasmine-BDD:
uitest.current
: This returns a singleton uitest instance, whose functions delegate to the current uitest instance of the spec/suite.uitest.current.runs(callback[,timeout])
: First, this executes awaitsFor
call usinguitest.current.ready
. Then it executes the the given callback using aruns
call from jasmine and does dependency injection for the arguments of the callback usinguitest.current.inject
.uitest.current.runsAfterReload(callback[,timeout])
: First, this executes awaitsFor
call usinguitest.current.reloaded
. Then it executes the the given callback using aruns
call from jasmine and does dependency injection for the arguments of the callback usinguitest.current.inject
.
The feature('angularIntegration')
enables angular.js support in uitest:
Angular provides a special library for unit testing, angular-mocks.js
. This library contains a mock $httpBackend
that can be programmed to return fake responses (see http://docs.angularjs.org/api/ngMock.$httpBackend). The feature('angularIntegration')
automatically enables this mock backend, so you can program and verify xhr calls just like unit tests. E.g.
var uit = uitest.current;
uit.feature("angularIntegration");
uit.append("lib/angular-mocks.js");
uit.runs(function($httpBackend) {
$httpBackend.whenGet(...);
...
$httpBackend.flush();
});
now you can use angular services for dependency injection at all places, e.g. in uitest.current.runs
to access angular services of the app to be tested. E.g.
var uit = uitest.current;
uit.feature("angularIntegration");
uit.runs(function($rootScope) {
...
});
Mobile applications usually control the viewport of an application using a <meta name="viewport">
tag to adjust the zoom level of the browser.
The feature('mobileViewport')
automatically copies this <meta>
tag form the iframe to the top window so that the zoom level is correct, although the app is loaded in an iframe.
Somtimes, the cache in the browser keeps an old copy of the scripts that we stored on the server or file system. A simple solution for this is to add a query parameter with an always changing id to every script tag. This is exactly, what the feature("cacheBusting")
does for normal scripts as well as for scripts included by requirejs.
Note: If you want to create breakpoints in your browser while cache busting is activated,
use the debugger;
statement in your scripts to manually stop the browser.
Triggering events:
- See the notes from QUnit on this topic: http://qunitjs.com/cookbook/#testing_user_actions
- use
jQuery.trigger
if you are using jQuery in your application.
Mocking the backend:
- Use https://github.com/philikon/MockHttpRequest/blob/master/lib/mock.js with the
prepend
function.
Copyright (c) 2013 Tobias Bosch
Licensed under the MIT license.