README
Zombie.js
Insanely fast, headless full-stack testing using Node.js
The Bite
If you're going to write an insanely fast, headless browser, how can you not call it Zombie? Zombie it is.
Zombie.js is a lightweight framework for testing client-side JavaScript code in a simulated environment. No browser required.
Let's try to sign up to a page and see what happens:
const Browser = require('zombie');
// We're going to make requests to http://example.com/signup
// Which will be routed to our test server localhost:3000
Browser.localhost('example.com', 3000);
describe('User visits signup page', function() {
const browser = new Browser();
before(function(done) {
browser.visit('/signup', done);
});
describe('submits form', function() {
before(function(done) {
browser
.fill('email', 'zombie@underworld.dead')
.fill('password', 'eat-the-living')
.pressButton('Sign Me Up!', done);
});
it('should be successful', function() {
browser.assert.success();
});
it('should see welcome page', function() {
browser.assert.text('title', 'Welcome To Brains Depot');
});
});
});
This example uses the Mocha testing framework, but Zombie will work with other testing frameworks. Since Mocha supports promises, we can also write the test like this:
const Browser = require('zombie');
// We're going to make requests to http://example.com/signup
// Which will be routed to our test server localhost:3000
Browser.localhost('example.com', 3000);
describe('User visits signup page', function() {
const browser = new Browser();
before(function() {
return browser.visit('/signup');
});
describe('submits form', function() {
before(function() {
browser
.fill('email', 'zombie@underworld.dead')
.fill('password', 'eat-the-living');
return browser.pressButton('Sign Me Up!');
});
it('should be successful', function() {
browser.assert.success();
});
it('should see welcome page', function() {
browser.assert.text('title', 'Welcome To Brains Depot');
});
});
});
Well, that was easy.
WARNING: Crawling untrusted web pages with Zombie.js is not safe.
Table of Contents
Installing
To install Zombie.js you will need Node.js:
$ npm install zombie --save-dev
Browser
browser.assert
Methods for making assertions against the browser, such as
browser.assert.element('.foo')
.
See Assertions for detailed discussion.
browser.referer
You can use this to set the HTTP Referer header.
browser.resources
Access to history of retrieved resources. See Resources for detailed discussion.
browser.pipeline
Access to the pipeline for making requests and processing responses. Use this
to add new request/response handlers the pipeline for a single browser instance,
or use Pipeline.addHandler
to modify all instances. See
Pipeline.
browser.tabs
Array of all open tabs (windows). Allows you to operate on more than one open window at a time.
See Tabs for detailed discussion.
browser.proxy
The proxy
option takes a URL so you can tell Zombie what protocol, host and
port to use. Also supports Basic authentication, e.g.:
browser.proxy = 'http://me:secret@myproxy:8080'
browser.attach(selection, filename, callback)
Attaches a file to the specified input field. The second argument is the file name. callback - called with error or nothing.
browser.back([callback])
Navigate to the previous page in history. Returns the zombie.browser.Browser to allow function chaining.
browser.body
Returns a zombie.dom.DOMNode representing the body element of the current document.
browser.check(selector, [callback])
Checks a checkbox. If called without a callback, returns a promise.
browser.choose(selector, [callback])
selects a radio box option. If called without a callback, returns a promise.
browser.clickLink(selector, callback)
Clicks on a link. Clicking on a link can trigger other events, load new page, etc. Use a callback to be notified of completion. Finds link by text content or selector.
browser.dump([output])
Dump information to the console: Zombie version, current URL, history, cookies, event loop,
etc. Useful for debugging and submitting error reports. output
defaults to
process.stdout
.
browser.evaluate(code, filename)
Evaluates a JavaScript expression in the context of the current window and returns the result. When evaluating external script, also include filename.
You can also use this to evaluate a function in the context of the window: for timers and asynchronous callbacks (e.g. XHR).
browser.field(selector)
Find and return an input field (INPUT
, TEXTAREA
or SELECT
) based on a CSS selector,
field name (its name
attribute) or the text value of a label associated with that field
(case sensitive, but ignores leading/trailing spaces).
browser.fill(selector, value, callback)
Fill in an input field or text area with the provided value.
browser.fire(selector, eventName, [callback])
Fire a DOM event. You can use this to simulate a DOM event, e.g. clicking
a link. These events will bubble up and can be cancelled. Like wait
this method takes an optional callback. If called without a callback, returns a promise.
browser.link(selector)
Finds and returns a link by its text content or selector.
browser.load(html, [callback])
Loads the HTML, processes events and calls the callback. Without a callback, returns a promise.
browser.location
Return the location of the current document (same as window.location
).
browser.pressButton(selector, [callback])
Press a button (button element or input of type submit
). Typically this will submit the
form. Use the callback to wait for the from submission, page to load and all events run
their course.
browser.query(selector, [context])
Evaluates the CSS selector against the document (or context node) and return an element.
context
defaults to document
.
browser.queryAll(selector, [context])
Evaluates the CSS selector against the document (or context node) and return array of nodes.
context
defaults to document
.
browser.querySelector(selector)
Selects the first matching element and returns it.
browser.redirected
Returns True if the page request followed a redirect.
browser.reload()
Reloads the current page. Returns the zombie.browser.Browser to allow function chaining.
browser.select(selector, value, [callback])
Selects an option inside of selector
with given value
. If called without a callback,
returns a promise.
browser.selectOption(selector, [callback])
Selects an option.
browser.status
Returns the status code returned for this window (200, 303, etc). The same as
browser.statusCode
browser.success
Return true if last response had status code 200 .. 299
browser.text(selector, [context])
Returns the text contents of the selected elements. context
defaults to document.
browser.uncheck(selector, [callback])
Unchecks a checkbox. If called without a callback, returns a promise.
browser.unselect(selector, value, [callback])
Unselects an option. If called without a callback, returns a promise.
browser.unselectOption(selector, [callback])
Unselects the an option. If called without a callback, returns a promise.
browser.visit(url, options, [callback])
Loads document from the specified URL, processes events and calls the callback, or returns a promise.
browser.click(selector, [callback])
Click on the selected element. If called without callback, returns a promise.
browser.errors
Collection of errors accumulated by the browser while loading page and executing scripts.
browser.source
Returns a string of the source HTML from the last response.
browser.html(element)
Returns a string of HTML for a selected HTML element. If argument element
is undefined
,
the function returns a string of the source HTML from the last response.
Example uses:
browser.html('div');
browser.html('div#contain');
browser.html('.selector');
browser.html();
Browser.localhost(host, port)
Allows you to make requests against a named domain and HTTP/S port, and will route it to the test server running on localhost and unprivileged port.
For example, if you want to call your application "example.com", and redirect traffic from port 80 to the test server that's listening on port 3000, you can do this:
Browser.localhost('example.com', 3000)
browser.visit('/path', function() {
console.log(browser.location.href);
});
=> 'http://example.com/path'
The first time you call Browser.localhost
, if you didn't specify
Browser.site
, it will set it to the hostname (in the above example,
"example.com"). Whenever you call browser.visit
with a relative URL, it
appends it to Browser.site
, so you don't need to repeat the full URL in every
test case.
You can use wildcards to map domains and all hosts within these domains, and you can specify the source port to map protocols other than HTTP. For example:
// HTTP requests for example.test www.example.test will be answered by localhost
// server running on port 3000
Browser.localhost('*.example.test', 3000);
// HTTPS requests will be answered by localhost server running on port 3001
Browser.localhost('*.example.test:443', 3001);
The underlying implementation hacks net.Socket.connect
, so it will route any
TCP connection made by the Node application, whether Zombie or any other
library. It does not affect other processes running on your machine.
Browser.extend
You can use this to customize new browser instances for your specific needs. The extension function is called for every new browser instance, and can change properties, bind methods, register event listeners, etc.
Browser.extend(function(browser) {
browser.on('console', function(level, message) {
logger.log(message);
});
browser.on('log', function(level, message) {
logger.log(message);
});
});
Assertions
To make life easier, Zombie introduces a set of convenience assertions that you can access directly from the browser object. For example, to check that a page loaded successfully:
browser.assert.success();
browser.assert.text('title', 'My Awesome Site');
browser.assert.element('#main');
These assertions are available from the browser
object since they operate on a
particular browser instance -- generally dependent on the currently open window,
or document loaded in that window.
Many assertions require an element/elements as the first argument, for example,
to compare the text content (assert.text
), or attribute value
(assert.attribute
). You can pass one of the following values:
- An HTML element or an array of HTML elements
- A CSS selector string (e.g. "h2", ".book", "#first-name")
Many assertions take an expected value and compare it against the actual value.
For example, assert.text
compares the expected value against the text contents
of one or more strings. The expected value can be one of:
- A JavaScript primitive value (string, number)
undefined
ornull
are used to assert the lack of value- A regular expression
- A function that is called with the actual value and returns true if the assertion is true
- Any other object will be matched using
assert.deepEqual
Note that in some cases the DOM specification indicates that lack of value is an
empty string, not null
/undefined
.
All assertions take an optional last argument that is the message to show if the assertion fails. Better yet, use a testing framework like Mocha that has good diff support and don't worry about these messages.
Available Assertions
The following assertions are available:
assert.attribute(selection, name, expected, message)
Asserts the named attribute of the selected element(s) has the expected value.
Fails if no element found.
browser.assert.attribute('form', 'method', 'post');
browser.assert.attribute('form', 'action', '/customer/new');
// Disabled with no attribute value, i.e. <button disabled>
browser.assert.attribute('button', 'disabled', '');
// No disabled attribute i.e. <button>
browser.assert.attribute('button', 'disabled', null);
assert.className(selection, className, message)
Asserts that selected element(s) has that and only that class name. May also be space-separated list of class names.
Fails if no element found.
browser.assert.className('form input[name=email]', 'has-error');
assert.cookie(identifier, expected, message)
Asserts that a cookie exists and has the expected value, or if expected
is
null
, that no such cookie exists.
The identifier is either the name of a cookie, or an object with the property
name
and the optional properties domain
and path
.
browser.assert.cookie('flash', 'Missing email address');
assert.element(selection, message)
Asserts that one element matching selection exists.
Fails if no element or more than one matching element are found.
browser.assert.element('form');
browser.assert.element('form input[name=email]');
browser.assert.element('form input[name=email].has-error');
assert.elements(selection, count, message)
Asserts how many elements exist in the selection.
The argument count
can be a number, or an object with the following
properties:
atLeast
- Expecting to find at least that many elementsatMost
- Expecting to find at most that many elementsexactly
- Expecting to find exactly that many elements
browser.assert.elements('form', 1);
browser.assert.elements('form input', 3);
browser.assert.elements('form input.has-error', { atLeast: 1 });
browser.assert.elements('form input:not(.has-error)', { atMost: 2 });
assert.evaluate(expression, expected, message)
Evaluates the JavaScript expression in the context of the currently open window.
With one argument, asserts that the value is equal to true
.
With two/three arguments, asserts that the returned value matches the expected value.
browser.assert.evaluate('$("form").data("valid")');
browser.assert.evaluate('$("form").data("errors").length', 3);
assert.global(name, expected, message)
Asserts that the global (window) property has the expected value.
assert.hasClass(selection, className, message)
Asserts that selected element(s) have the expected class name. Elements may
have other class names (unlike assert.className
).
Fails if no element found.
browser.assert.hasClass('form input[name=email]', 'has-error');
assert.hasFocus(selection, message)
Asserts that selected element has the focus.
If the first argument is null
, asserts that no element has the focus.
Otherwise, fails if element not found, or if more than one element found.
browser.assert.hasFocus('form input:nth-child(1)');
assert.hasNoClass(selection, className, message)
Asserts that selected element(s) does not have the expected class name. Elements may
have other class names (unlike assert.className
).
Fails if no element found.
browser.assert.hasNoClass('form input', 'has-error');
assert.input(selection, expected, message)
Asserts that selected input field(s) (input
, textarea
, select
etc) have
the expected value.
Fails if no element found.
browser.assert.input('form input[name=text]', 'Head Eater');
assert.link(selection, text, url, message)
Asserts that at least one link exists with the given selector, text and URL.
The selector can be a
, but a more specific selector is recommended.
URL can be relative to the current document, or a regular expression.
Fails if no element is selected that also has the specified text content and URL.
browser.assert.link('footer a', 'Privacy Policy', '/privacy');
assert.redirected(message)
Asserts the browser was redirected when retrieving the current page.
assert.success(message)
Asserts the current page loaded successfully (status code 2xx or 3xx).
assert.status(code, message)
Asserts the current page loaded with the expected status code.
browser.assert.status(404);
assert.style(selection, style, expected, message)
Asserts that selected element(s) have the expected value for the named style property. For example:
Fails if no element found, or element style does not match expected value.
browser.assert.style('#show-hide.hidden', 'display', 'none');
browser.assert.style('#show-hide:not(.hidden)', 'display', '');
assert.text(selection, expected, message)
Asserts that selected element(s) have the expected text content. For example:
Fails if no element found that has that text content.
browser.assert.text('title', 'My Awesome Page');
assert.url(url, message)
Asserts the current page has the expected URL.
The expected URL can be one of:
- The full URL as a string
- A regular expression
- A function, called with the URL and returns true if the assertion is true
- An object, in which case individual properties are matched against the URL
For example:
browser.assert.url('http://localhost/foo/bar');
browser.assert.url(new RegExp('^http://localhost/foo/\\w+