#Sparrow functional testing
A functional website testing framework based on jasmine and JQuery. Sparrow is designed to solve the problem of async testing inherent in website testing by making async testing easier.
Sparrow:
- Makes async testing easier
- Allows testing multiple pages simultaneously so complex interactions can be tested
- Runs tests in a browser and headless
- Allows using the IntelliJ/Webstorm debugger with both test and page code
- Uses JQuery and Jasmine to reduce the learning curve
- Makes it easier to create independent tests that are not dependent on each other
##Quick Start
-
Download the latest sparrow
-
cd to installation directory
-
type 'npm install' (requires node)
-
type 'grunt headed' to create specRunner.html (requires grunt)
NOTE: Make sure you run this command after creating new spec/helper files and after upgrading Sparrow. You can also rungrunt watch
to build specRunner.html automatically when spec/helper files change. -
open runner.html in a browser (tested with newest Chrome and FF)
(NOTE: FF seems to allow running from the local filesystem. Chrome requires running it from a web server) -
Look at the files in /specs to see some of the possibilities
##Running a single test or a group of tests
To run a single test or a group of tests, simply add ?spec=some%20it%20or%20description to the end of the url the same way you would running Jamsine directly.
example:
to run 'should do something' use ?spec=should%20do%20something at the end of the url.
##Running tests in headless mode
To run tests in headless/CI mode. type 'grunt' or 'grunt headless'
If you need to run the tests from a server with dynamic content, simply add the host address in Gruntfile.js. It is best to run the tests from the source directory the server is pointed to or a symlink.
##Sparrow options
####sparrow.WAIT_TIME The time to wait for the waitFor*, waitUntil*, waitWhile*
This can be set in a helper to make it a global setting for all tests
##Basics
There are two ways to use the sparrow functions. You can call them directly and provide a callback for the async functions, or you can do things the easier way and use the sparrow async() monad.
describe('block of tests', function() {
it('should do something', function(done) {
createTestWindow('win');
// open the page
$win.open('http://example.com', function() {
$win.click('#some-link');
// fill the form in the popup
$win.waitForSelector('#some-popup', function() {
$win.fill('#some-form', {name:'me', ...};
$win.click('#sumit-button');
// Do something with the resulting page
$win.waitforText('new page loaded', function() {
$win.click('#another-link');
$win.waitWhileVisible('#some-modal', function() {
myAsyncFunction(function() {
// Test that we see 'finished'
expect($win.find('#result').html()).toBe('finished');
done();
});
});
});
});
});
});
});
describe('block of tests', function() {
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
// open the page
.open('http://example.com')
.click('#some-link')
// fill the form in the popup
.waitForSelector('#some-popup')
.fill('#some-form', {name: 'me', ...}
.click('#submit-button')
// Do something with the resulting page
.waitForText('new page loaded')
.click('#another-link')
.waitWhileVisible('#some-modal')
.fn(myAsyncFunction)
// Test that we see 'finished'
.syncFn(function() {
expect($win.find('#result').html()).toBe('finished')
})
.run();
});
function myAsyncFunction(done) {
// do something async
done();
}
});
Sparrow is based on jasmine so tests are written in the same style. See jasmine docs for more possibilities.
##Using with Meteor
Unpack sparrow someplace in the /public directory. I put it in /public/sparrow. Sparrow tests will then be available at http://localhost:3000/sparrow/runner.html.
NOTE: I have found that I must clear the cache in my browser to get updates after changing specs.
NOTE: If you are using the async monad, ignore the doneCB argument on the following, this is handled for you.
- .run()
- .open(url, doneCB)
- .show()
- .waitUntilTrue(testFn, doneCB)
- .waitForText(text, doneCB)
- .waitForSelector(selector, doneCB)
- .waitUntilVisible(selector, doneCB)
- .waitWhileVisible(selector, doneCB)
- .wait(ms, doneCB)
- .click(selector)
- .log(message)
- .fill(selector, formData)
- .close()
- .fn(someFunction)
- .syncFn(someFunction)
- .extend(obj)
- .http.post(url, data, success, done)
Opens a test window tab and creates $name variable in the test scope.
it('should do something', function() {
createTestWindow('aTestWindow');
... creates $aTestWindow
});
####.async(doneCB)
Creates an async monad to make async functions easier
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a#link')
.waitForText('something on new page')
.run()
});
####.run()
Starts a previously created async monad
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
// other instructions here
.run();
});
Opens a url in a window tab.
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.open('http://google.com'
// other instructions here
.run()
});
####.show()
Show the tab for this window
it('should do something', function(done) {
createTestWindow('win');
createTestWindow('another');
$win.async(done)
.show() // This will show the $win window (tab)
.run()
$another.async(done)
.show() // This will show the $another window (tab)
.run()
});
####.waitUntilTrue(testFn, doneCB)
Wait until the test function returns true
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('#something')
.waitUntilTrue(somethingHappening)
});
function somethingHappening() {
return $j('#xxxx').html() === 'ready'
}
waitUntilTrue can also takes an async function containing a "done" final argument.
The passed async function will be called until it calls done() with a truthy value or sparrow.WAIT_TIME is reached.
it('should do something', function(done) {
var count = 0;
$waitFor.async(done)
.waitUntilTrue(asyncFunc)
.run();
function asyncFunc(done) {
setTimeout(function() {
++count === 2 ? done(1) : done(0);
},1);
}
})
});
####.waitForText(text, doneCB)
Wait until the text is visible on the webpage
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.waitForText('some text on the page')
.run()
});
####.waitForSelector(selector, doneCB)
Wait until the selected element is in the dom
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.waitForSelector('a#my-link')
.run()
});
####.waitUntilVisible(selector, doneCB)
Wait until the selected element is visible
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.waitUntilVisible('#myAsyncModal')
.run()
});
####.waitWhileVisible(selector, doneCB)
Wait while the selected element is visible
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.waitWhileVisible('#loadingMessage')
.run()
});
Wait for some period of milliseconds
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.wait(2000)
.click('#something else')
.run()
});
####.click(selector)
Click on some selected element
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.wait(2000)
.click('#something else')
.run()
});
####.log(message)
Send a log message to the console.
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.log('I clicked it')
.run()
});
Fill in the selected form
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.fill('#my-form', {name: 'Scott', email:'me@mine.com'})
.click('#submit')
.run()
});
####.close()
Close an open test window (tab)
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.fill('#my-form', {name: 'Scott', email:'me@mine.com'})
.click('#submit')
.close()
.run()
});
Call an async function
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.fn(myAsyncFunction)
.run()
});
function myAsyncFunction(done) {
// do something
done();
}
Call a function. Currently this is the best way to add expects into your test code.
it('should do something', function(done) {
createTestWindow('win');
$win.async(done)
.click('a')
.syncFn(function() {
expect($win.find('#something').html()).toBe('my content');
})
.run()
});
Add functions to sparrow. The first argument will be the test window variable. For example, if you created a test window called "myWin", then called $myWin.write(). winVar would be $myWin.
NOTE: To add an async function, the final argument must be named 'done'
sparrow.extend({
write: function(winVar, selector, content) {
winVar.find(selector).append(content);
},
waitForTesting: function(winVar, done) {
winVar.waitForText('testing', done);
}
});
`
####<a name="httpPost">.http.post(url, data, success, done)
```javascript
$tests.async(done)
.fn(function(done) {
$tests.http.post('/some/url', {some:'data'}, _.partial(checkReturn, done));
})
.run();
function checkReturn(done, ret) {
expect(ret).toBe('<some> <html>');
done();
}
##Troubleshooting
You can use the $window variable to access the window object inside of a tab from the debugging console.
For example, if your tab is $page, then $page.$window will give you the window variable within that tab.
From there you can access any global variables.
###Debugging in the middle of an async monad chain
The easiest way to debug in the middle of the async chain is to use syncFn. Add a temporary .syncFn() call and put your debug code inside of the passed function. The same technique works for setting breakpoints.
$win.async(done)
.click('#something')
.waitFor('#something-else')
.syncFn(function() {
console.log($win.find('#some-thing').html())
})
.run()
If you find Sparrow useful, please let me know. If you have any questions or concerns, please feel free to let me know at scott@bulldoginfo.com.
I created Sparrow because none of the other testing frameworks I found had the features I was looking for. I have found this framework very useful for testing production websites.
If you are looking for someone to setup functional testing for your web application or website, contact me.