App tests

App tests are acceptance tests for web applications, somewhat similar to Selenium, although the way they're written in HyperTest is a lot different. They can simulate a user and test whether responses to user actions yield the results that one expectes. A test sequence can for instance fill in a form, press a 'submit' button and examine the response page.

App tests run always in a sequence: the apptest library creates an iframe and loads the URL for the first test into it. After that, it expects the tests to make the iframe jump to a different page, and will basically just wait for results until all tests are ran. Tests are registered along with a URL, which for the first test is used to load inside the iframe, and later on is checked to determine whether the scenario is still running the expected trail.

Test functions get two arguments passed to them: the first, 'at', is a reference to the apptest library, the second, called 'browser' here, a reference to a special Browser object that can be used to control and read data from the browser.

Example

function test_sync(at, browser) {
    /* simple test of a plain HTML document */
    // see whether the title is what we expect; if this fails (or
    // any other at.assert* call), execution of the test chain
    // stops entirely
    at.assertEquals(browser.title,
                            'Test document 1');

    // also test the content of the 'body' element
    at.assertEquals(
        string.strip(
            browser.document.getElementsByTagName(
                'body'
            )[0].childNodes[0].nodeValue
        ),
        'Test document content'
    );

    // in the end we have to make sure we send the browser to the 
    // next location, either by clicking a button or by setting a new
    // location (unless this is the last test, in that case nothing
    // special needs to be done)
    browser.navigateTo('apptestdata/test_async.html');
};

window.app_test_registry.push(['apptestdata/test_sync.html', test_sync]);

Testing event-driven (async) code

In some situations, writing code that is executed in a loop will not do. Perhaps you're working with XMLHttpRequest callbacks, or with events that are executed after each other and you need to wait for the result. JavaScript? does not implement a way to let the code sleep (it doesn't even support threading, so even the act of waiting for something would cause that something to never happen), so there's no way to write a function that checks for the results of the XMLHttpRequest or event sequence, and throws an exception if it's not correct, without having the function 'break out' of the main loop.

To overcome this problem, AppTest lets you take over control of the code flow, run code and tests, and give control back in the end of the tests.

Example

// we define a simple handler here, of course in real-life you will
// have something like a class with the handlers for each test (as
// it may become quite a long chain for each page) but for now at least
// it displays that we have a seperate function body that continues 
// testing, and gives control back to the framework when done
var poll_and_continue = function(at, browser, callback, orgtext) {
    // we poll here, since we can't hook into any load/unload stuff (we
    // have a div, which has no on[un]load support)
    var newtext = browser.getText(
        browser.document.getElementById('container')
    );
    // check if the text has been updated yet
    if (newtext == orgtext) {
        // not yet updated, poll
        misclib.schedule(this, poll_and_continue, 100);
        return;
    };
    // assertion on the newly loaded text
    at.assertEquals(string.strip(newtext), 'Text loaded');

    // when we're done we need to make sure the client navigates
    // to the next page, *and* call a callback... since we have no
    // more tests, in our case the callback is all, though
    callback();
};

function test_async(at, browser, callback) {
    /* we have a document with a div that is filled with data, using 
        XMLHttpRequest, after pressing some button...
    */

    // first we store the original text
    var orgtext = browser.getText(
        browser.document.getElementById('container')
    );
    // see whether it's the text we expect 
    at.assertEquals(
        string.strip(orgtext),
        'Nothing to see here, please move along.'
    );

    // click the button that starts the XHR text loading
    browser.getElementsByText('load')[0].click();

    // start the polling and continue testing there
    poll_and_continue(at, browser, callback, orgtext);

    // tell the framework to wait until the callback is called
    return at.CONTINUE_ASYNC;
};

window.app_test_registry.push(['apptestdata/test_async.html', test_async]);

This way even the most complex AJAX applications can be fully tested.