dominator.open('#home')
.click('a.redButton')
.waitForPage('#boom')
.destroy();
Synopsis
Dominator adds functional test assertions to QUnit.
Functional tests should be simple to write, easy to read, and adaptable to any JavaScript framework.
For us, functional tests are a smoke screen. When you need more, then it is time to abstract and unit test.
Quick Example
module('open');
test('should open first page', function() {
dominator.open('#home')
.waitForPage('#home')
.destroy();
});
module('settings');
test('should save settings', function() {
dominator.open('#settings')
.click('#save')
.waitForEvent('document', 'settingsChange');
.destroy();
});
Downloading
Installing
Dominator
<!-- include dominator.js after qunit.js -->
<script type="text/javascript" src="qunit.js"></script>
<script type="text/javascript" src="dominator.js"></script>
Custom Dominators
<!-- include custom dominators after dominator.js -->
<script type="text/javascript" src="qunit.js"></script>
<script type="text/javascript" src="dominator.js"></script>
<script type="text/javascript" src="dominator.jqtouch.js"></script>
Getting Started
Download QUnit
Download QUnit from github.com/jquery/qunit.
Download Dominator.js
Go to the Downloading section.
File Structure
Feel free to keep your current application file structure. However, let's assume you are using something like this:
app/ ...................
app/index.html ......... Web application
app/test/ ..............
app/test/index.html .... Test runner
app/test/dominator/ .... Dominator.js
app/test/qunit/ ........ QUnit
app/test/tests/ ........ QUnit tests
Create a Test Page
In /app/test/index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="qunit/qunit.css" />
<!-- test framework (load qUnit first) -->
<script type="text/javascript" src="qunit/qunit.js"></script>
<script type="text/javascript" src="dominator/dominator.js"></script>
<!-- load application to test -->
<script type="text/javascript">
dominator.load({
src: '../index.html',
id: 'app',
name: 'app',
width: '320',
height: '480',
scrolling: 'auto',
frameborder: '0'
});
</script>
<!-- tests -->
<script type="text/javascript" src="tests/home.test.js"></script>
<script type="text/javascript" src="tests/settings.test.js"></script>
<title>App Tests</title>
</head>
<body>
<h1 id="qunit-header">App Tests</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">
<!-- populated dynamically -->
</div>
</body>
</html>
Write Tests
In /app/test/tests/home.test.js:
module('open');
test('should open home page', function() {
dominator.open('#home')
.waitForPage('#home')
.destroy();
});
test('should have a logo', function() {
dominator.open('#home')
.waitForVisible('#home .logo')
.destroy();
});
In /app/test/tests/settings.test.js:
module('settings');
test('should save settings', function() {
dominator.open('#settings')
.click('#save')
.waitForEvent('document', 'settingsChange');
.destroy();
});
Run Tests
Dominator assertions are chainable and executed with destroy();
Open /app/test/index.html
Manually Loading the App to Test (optional)
If the default dominator.load
implementaton is not doing it for you, then you can override it or not use it.
Just remember that Dominator disables QUnit's auto-run, so that you can wait for your application to load.
// After loading the test and application
// you can safely start QUnit with:
QUnit.start();
You could override the application loader like this:
dominator.fn.load = function(some, custom, params) {
// do whatever you need to load the application
dominator.config.window = myApplicationWindow;
QUnit.start();
};
Reference
Application Loader
An optional helper function to load the test application.
//
// Convenience function to load an application into an <iframe />
//
// Can be called before or after adding the QUnit tests.
//
// - Sets dominator.config.window
// - Calls QUnit.start(); after the application has loaded
//
// @param options {Object} attributes to add to the <iframe />
//
dominator.load({
src: '../index.html',
id: 'app',
name: 'app',
width: '320',
height: '480',
scrolling: 'auto',
frameborder: '0'
});
Assertions
Here are the cold, mechanical limbs of the dominator:
dominator.open('#home')
dominator.click('#home a.button')
dominator.waitForVisible('#home .notification')
dominator.waitForNotVisible('#home .error')
dominator.waitForEvent('#home a.button', 'touchend')
dominator.waitForPage('#settings')
About Chaining
Assertions are queued and executed with destroy();
:
// Does nothing, but tries to run all queued assertions
dominator.destroy();
// Asserts that home page opened
dominator.open('#home')
.destroy();
// Asserts that:
// - home page opened
// - a button inside the home page was clicked
dominator.open('#home')
.click('#home a.button')
.destroy();
// Same as above
dominator.open('#home');
dominator.click('#home a.button');
dominator.destroy();
Customizing
Every application is structured different.
This is why Dominator was designed for customization.
Feel free to override any existing assertion and add your own.
Extend
//
// Add and Override Assertions
//
// Add your arguments that you need, such as `id`.
// The final argument must be a callback function.
// Call the callback when your assertion is complete.
//
// This example adds `dominator.tap("body a.button");`
// and uses the XUI JavaScript library.
//
dominator.extend('tap', function(id, callback) {
this.window.x$(id).trigger('mousedown');
callback();
});
Window
//
// Window Handle of Test Application
//
// While testing, you will need a handle to the
// application window.
//
// The window value is set before running QUnit.start() with:
// dominator.config.window = window;
//
dominator.extend('tap', function(id, callback) {
this.window.x$(id).trigger('tap');
callback();
});
Expect
//
// Expect an Assertion Count
//
// Tells QUnit how many assertions should fire.
//
// Typically, expect is used within extend.
//
// Expect will auto-increment the existing
// expectation count, so there is no need to worry
// about clobbering a preset value.
//
dominator.extend('tap', function(id, callback) {
// 'tap' should fire on positive assertion
this.expect(1);
// 'tap' requires at least one tappable element
if (x$(id).length > 0) ok(true, 'found at least one ' + id);
x$(id).trigger('mousedown');
callback();
});
Config
The Dominator configuration is stored in dominator.config
:
// window {Object}
//
// The window handle to the test application.
// This must be set before calling QUnit.start();
//
// This value is used by all Dominator assertions.
//
// See the Reference section for more detail.
//
dominator.config.window = DOMElement;
// Feel free to pass any property or function
// that you extensions may need
//
// For example:
//
dominator.config.postResults = function(value) {
// XHR POST results to a server
};
Fn
Extend the dominator prototype:
// Override Application Loader
// dominator.load({});
//
dominator.fn.load = function(options) {
// implementation
};
// Create Something New
// dominator.beep();
//
dominator.fn.beep = function() {
console.log('BOOM!!!');
};
Community
Google Groups
Please use the PhoneGap Google Group.
IRC
Jump onto #phonegap @ irc.free.node and look for mwbrooks
Direct Message
Contact Michael Brooks through a GitHub message.
Contribute
Improvements
Help me out by using the GitHub Issue Tracker to report a bug and discuss changes.
Pull Requests
I appreciate pull requests!
I appreciate them more when on topic branch, instead of the master branch.
Everything else
Here are a few ideas:
- Minor Web design tweaks
- Documentation
- Usage feeback
Acknowledgement
- Nitobi guys for running the library through the dirt
- Ryan Betts for the Web design feedback
- Sam Brown for Red Robot
- Selenium Core for the syntax influence
- Yohei Shimomae for the Web design feedback