- Simple - Use jQuery to interact with items on the page and check expected conditions. Optional fibers support makes test writing and reading straightforward.
- Fast - Can perform 50+ actions per second in the browser
- Cross-browser - Tests run on any modern web browser, and even some old ones
- Flexible - Write tests in whatever node.js framework you want
Tested "Should work"
Chrome 4+ ✓
Firefox 3+ ✓
Safari 3+ ✓
Opera 10.61+ ✓
IE 5.5+ ✓
IE 8+ ✓
iOS ✓
Android ✓
PhantomJS ✓
SlimerJS ✓
Other ✓
- Your tests include the browser-harness module and tell it to start listening for connections
- Your test (or some outside process) opens a browser to http://your-test-site/path/to/harness.html?host=path-to-the-browser-harness-server
- Harness.html connects to the browser harness server, and begins running your tests
- Your tests will control a browser running within an iframe in harness.html
A standalone example of using browser harness can be found under the browser-harness-bootstrap-tests repository.
The following example uses mocha, but any test framework may be used.
//This little utility function helps reduce the boilerplate needed by each test
var _it = function(name, exec){
it(name, function(done){
asyncblock(exec, done);
});
};
describe('An abridged test of the bootstrap docs', function(){
var driver, testBrowser;
before(function(done){
//Tell the browser harness to listen for connections on port 4500
harness.listen(4500, function(){
//This event is fired when a browser makes a connection and is ready
harness.events.once('ready', function(_driver){
driver = _driver;
done();
});
//The harness.Browser class can be used to spawn browser instances
//Here, we're using default settings for Chrome
testBrowser = new harness.Browser({ type: 'chrome' });
//Tell the browser to open the harness page
//By default, the test will connect to the harness server at localhost:4500
//Pass host=address on the query string to change the address
testBrowser.open('http://localhost:8000/harness.html');
});
});
it('Loads index.html', function(done){
//setUrl tells the browser to navigate to the page.
//The callback is called once the load event of the page has been called
driver.setUrl('http://localhost:8000/index.html', done);
});
//This test uses the _it method defined above,
//which lets code be written in "blocking style"
_it('Finds the h1 element', function(done){
//findVisible finds an element only if it exists and is visible
//It accepts any jQuery selector
var h1 = driver.findVisible('.masthead h1');
//findVisible returns an object that behaves like a jQuery variable
assert.equal(h1.length, 1);
//The call to .html() makes a roundtrip to the browser, but fibers makes it
//so we don't have to deal with callbacks directly
assert.equal(h1.html(), 'Bootstrap');
});
_it('Clicks on javascript', function(){
//The selector used here is not ideal. Usually we want to select by an id or class
//For testing your own applications, it's generally better to
//modify the code than use goofy selectors
driver.findVisible('a[href="./javascript.html"]').click(); //jQuery chaining works
//Browser harness encourages you not to wait for explicit time periods.
//In fact, it doesn't even have a built-in sleep function
//Instead, use conditions that indicate when it's safe to continue test execution
driver.waitFor(function(){
//This function runs from within the browser context
return location.href.indexOf('/javascript.html') >= 0;
});
});
_it('Clicks on Modal', function(){
driver.findVisible('a[href="#modals"]').click();
driver.waitFor(function(){
return location.href.indexOf('#modals') >= 0;
});
});
_it('Launches the demo modal', function(){
driver.findVisible('a[href="#myModal"]').click();
var modal = driver.findVisible('#myModal');
//You can call findVisible(s)/findElement(s) on elements
//to scope the call to children of that element
modal.findVisible('.modal-footer .btn[data-dismiss=modal]').click();
//The close is animated, so need to wait for it
driver.waitFor(function(){
return $('#myModal').css('display') === 'none';
});
});
after(function(){
//Close the browser instance that we spawned
testBrowser.close();
});
});
The module has built-in support for fibers via asyncblock](https://github.com/scriby/asyncblock). To take advantage of it, all you need to do is install asyncblock from npm and wrap your test with it (see above for an example).
Browser harness will auto-detect that asyncblock is being used and turn all asynchronous calls into "blocking-style".
Note that using fibers to write the tests is optional, but it is highly recommended. See no-fibers.js for an example of writing tests without fibers.
Due to the way browser harness interacts with the browser, there are a few limitations.
- Requires harness.html be served from the domain of the site/application being tested
- Warning: Be careful not to include harness.html in production, as it opens a potential cross-site scripting attack vector
- Can only interact with pages hosted from within a single domain (barring some CORS configuration)
- Can not upload files from the local computer (but should be able to spoof file uploads via javascript)
- Can not interact directly with cookies that have the httponly flag
There are tests for browser harness located under the "tests" folder.
To run the tests:
cd tests
#Install test dependencies
npm install
#Run test with mocha. Set a 10 second timeout as sometimes it takes a bit for the browser to open initially
#You can also use your global mocha installation if you have it installed already
./node_modules/mocha/bin/mocha all_tests.js -R spec -t 10000
#Edit the test_browser file to run the tests in a different browser (defaults to phantomjs)
- More streamlined browser integration (windows)
- Handle alert, input, etc.
- Cookie handling
- SlimerJS integration
- Ability to take screen shots with phantomjs / slimerjs (possible with others?)
- Switch out NowJS support for plain Socket.IO for easier Windows support
- Better support detecting javascript errors in the browser
- Build in support for other event types like right click, mouse down, mouse up, keys, etc.
- More robust error handling
- See what can be done to make it easier to interact with file upload controls
- "Ease of use" improvements around common functions, like waiting for a specific page
- Documentation