Skip to content

scottburch/sparrow

Repository files navigation

#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

  1. Download the latest sparrow

  2. cd to installation directory

  3. type 'npm install' (requires node)

  4. 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 run grunt watch to build specRunner.html automatically when spec/helper files change.

  5. 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)

  6. 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.

Callback example

    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();
                            });
                        });
                    });
                });
            });
        });
    });

Async monad example (much cleaner and easier to refactor)

    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.

##Documentation

NOTE: If you are using the async monad, ignore the doneCB argument on the following, this is handled for you.

####createTestWindow('name')

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();
    });

####.open(url, doneCB)

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(ms, doneCB)

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(selector, formData)

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()
    });

####.fn(someFunction)

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();
    }

####.syncFn(someFunction)

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()
    });

####sparrow.extend(obj)

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

Accessing the window object in a tab

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()

Feedback

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.

About

A functional testing framework that allows for control of multiple "windows"

Resources

License

Stars

Watchers

Forks

Packages

No packages published