DISCLAIMER: This project is no longer maintained. If you wish to take it over, let me know.
WebSpecter is a an acceptance test framework for web applications. It simulates how a real user would interact with a web browser using BDD-style tests written in CoffeeScript or JavaScript. A simple test example:
feature "GitHub search", (context, browser, $) ->
before (done) -> browser.visit 'https://github.com/search', done
it "finds WebSpecter", (done) ->
$('input[name=q]').fill 'webspecter'
$(button: 'Search').click ->
$(link: "jgonera / webspecter").present.should.be.true
done()
WebSpecter runs on PhantomJS (not Node.js). It is the only dependency that needs to be installed manually.
As of now, compatibility with older versions of PhantomJS is not a priority so if something doesn't work, please install the latest stable version of PhantomJS.
Since PhantomJS doesn't have any package manager you install WebSpecter by cloning the repo with submodules:
git clone https://github.com/jgonera/webspecter.git --recursive
Then create a symlink to webspecter/bin/webspecter
in ~/bin
or
/usr/local/bin
or add webspecter/bin
to PATH
.
Once WebSpecter is correctly installed, this should work:
$ webspecter --version
0.1.0dev
Important: WebSpecter is in early development stage. API may still change.
Tests can be written in JavaScript or CoffeeScript. The examples in the docs use the latter.
Running webspecter <path>
will run all the tests in the given path, which
can be either a directory or a specific file. If path is a directory, all
files with .js
and .coffee
extensions will be loaded.
Every test must contain a feature, like so:
feature "Feature name", (context, browser, $) ->
# test cases follow
Inside a feature, standard BDD syntax is used to describe test cases. it
keyword denotes a single test and describe
can be used to group tests.
Moreover, before
, after
, beforeEach
, and afterEach
are also
available. Mocha is the framework used internally, see its
documentation for details.
Chai is used as an assertion library. All of its three interfaces
(should
, expect
and assert
) can be used.
Each feature has its own context
, browser
and a jQuery-like function
$
. jQuery is not used internally, so don't expect 100% compatibility.
Apart from feature
, WebSpecter adds two other global keywords to the DSL:
wait
and parallelize
.
See details below on how to use all the features or WebSpecter examples for more real life use cases.
Browser is used to navigate web sites in tests. It's a wrapper for PhantomJS's WebPage.
Contains current URL.
Visits a URL. If callback is provided it is called after the page is loaded.
beforeEach (done) -> browser.visit 'http://google.com/', done
Evaluates code in web site's context.
windowWidth = browser.evaluate -> window.innerWidth
Finds elements on the web site. If the argument is a string it treats it as a CSS selector. If the arguments is an object, it can have one of the following forms:
$(button: "Button caption")
, finds buttons with the specified caption
(either <button>
or <input>
).
$(field: "Field label")
, finds inputs and textareas with specific label,
id or placeholder.
$(link: "Text")
, finds links with specific text.
$(text: "Text")
, finds elements with specific text.
$(xpath: 'XPath query')
, finds elements using XPath, e.g.
//li[text()="special"]
finds all <li>
elements that contain the text
"special".
$
returns an object with the following properties and methods that can let
you inspect and manipulate web site's elements.
True if the element is present.
$('h1').present.should.be.true
True if the element is visible.
Element's text contents.
Input's or textarea's value.
True for checked radioboxes and checkboxes.
Value of element's name
attribute.
Value of element's name
CSS property.
$('header').style('color') # returns color of the <header> element
Fills an input or textarea with value
.
Types text
into an input or textarea by sending a JavaScript TextEvent
.
Checks a checkbox or radiobox.
Unchecks a checkbox.
Selects an option in <select>
by its name.
Submits a <form>
and calls optional callback. Example:
$('form').submit ->
$('p#flash').text.should.include "successfully submitted"
Clicks the element. If an argument is given it's treated as a callback that will be run after the page is loaded (e.g. after submitting the form).
is
contains the following functions: is.present()
, is.visible()
,
is.checked()
. They return the same values as the properties mentioned before.
They can be used as a condition function in the wait
functions.
When more than one element is returned by a query, by default all the methods apply to the first element from the list. You can access consecutive elements with an array-like syntax:
$('#somelist li')[2].text # access the third <li>'s text
You can also iterate over all elements:
$('#somelist li').each (element) ->
element.text.should.include 'item'
The context
object contains the whole environment for a particular
feature. In fact, it also contains the browser
object and $
function. It
also contains all the helper functions defined in the
environment file.
The context
object contains the default context for the feature, but you
can create multiple contexts using context.newContext()
. This can be used
to simulate multiple users interacting with your web application at the same
time.
For a complete example of such use case see examples/stypi.coffee
.
Returns the browser object for the context.
The $
function.
Returns a new context with optional name
.
Includes additional helper functions in the context from a file defined by
path
. See Defining helpers for details.
Every context can include helper functions. Helper functions defined in the environment file are available in new contexts by default. However, you can also include helpers from other files.
When defining helpers this
is set to the context so that you can use
context's browser and $
function inside them (@browser
and @$
in
CoffeeScript, this.browser
and this.$
in JavaScript).
Example:
# my_helpers.coffee
exports.helpers =
sendMessage: (text) ->
@$(field: "Message").fill text
@$(button: "Send").click()
# my_feature.coffee
feature "Messages", (context, browser, $) ->
context.include 'my_helpers.coffee'
it "displays sent messages", ->
context.sendMessage "test message"
$('#messages').text.should.include "test message"
There is one special file which you can put in the tests directory, called
env.coffee
or env.js
. This file can contain global configuration for all
the features, additional selectors for the $
function and default helper
functions for contexts. Example:
exports.config =
baseUrl: 'http://localhost:3001'
exports.selectors =
tea: (query) -> xpath: "//*[text()='#{query} tea']"
exports.helpers =
pageTitle: -> @$('h2').text
Contains global configuration for all the features.
A string that will be prepended in all browser.visit()
calls so that
instead of repeating the host and port in all the features, you can simply
write browser.visit('/path')
and it will visit baseUrl + '/path'
.
Additional selector for the $
function. The example above adds the
following:
$(tea: 'black') # will select all elements whose text is "black tea"
Default helpers defined in a way described in Defining helpers.
Very often when you test web applications you might want to wait until
something happens (e.g. a DOM element appears) before continuing the test. You
can do it using the wait.while()
and wait.until()
functions.
Both functions receive two functions as their arguments: a condition function
and a callback. For wait.until()
the condition function is checked constantly
until it returns true
and then the callback is called. For wait.while()
the
callback is called when the condition function returns false
.
Optionally, the second argument can be an object with the following options:
for
which sets the timeout in ms and message
which sets a custom error
message if the timeout is reached. The error message can be also set by
creating a message
attribute in the condition function.
Examples:
$('#addWithDelay').click()
wait.until $('#delayed').is.present, ->
$('#delayed').text.should.equal 'something'
done()
appReady = ->
browser.evaluate -> window.appReady
appReady.message = "Timed out waiting for the app to be ready"
wait.until appReady, for: 1000, ->
$('#workspace').present.should.be.true
done()
In certain situations you may want several asynchronous tasks to be performed
in parallel (e.g. registering two users accounts at the same time). You can
achieve this by using parallelize
. It accepts a single argument which is the
number of tasks to be performed simultaneously and returns a function which can
be used instead of done
.
Example:
alice = context.newContext()
bob = context.newContext()
parallel = parallelize 2
# register two users in parallel (createUser is a helper)
alice.createUser aliceUser, parallel
bob.createUser bobUser, parallel
parallel.done =>
# do something more
done()
If you want to add something to WebSpecter, remember to write tests for it. Tests are also written using Mocha. You can run the whole WebSpecter test suite with the following command:
make test
This automatically starts the test web application before running tests. The
test web app can be found in support/server
and requires Node.js.