Writing Gemini tests

For each block of website that will be tested you need to write one or more test suites. Suite consists of few states that need to be verified. For each state you need to specify action sequence that gets block to this state.

Defining suites

Test suite is defined with gemini.suite method.

Example:

gemini.suite('button', function(suite) {
    suite
        .setUrl('/path/to/page')
        .setCaptureElements('.button')
        .before(function(actions, find) {
            this.button = find('.buttons');
        })
        .capture('plain')
        .capture('hovered', function(actions, find) {
            actions.mouseMove(this.button);
        })
        .capture('pressed', function(actions, find) {
            actions.mouseDown(this.button);
        })
        .capture('clicked', function(actions, find) {
            actions.mouseUp(this.button);
        });
});

Arguments of a gemini.suite:

Suite builder methods

All methods are chainable:

Capture region is determined by minimum bounding rect for all of the elements plus their box-shadow size.

Can also accept an array:

js suite.setCaptureElements(['.selector1', '.selector2']);

All tests in a suite will fail if none of the elements will be found.

Callback accepts two arguments: * actions — chainable object that should be used to specify a series of actions to perform.

js suite .before(function(actions, find) { this.button = find('.buttons'); }) .capture('hovered', function(actions, find) { actions.mouseMove(this.button); }) .capture('pressed', function(actions, find) { actions.mouseDown(this.button); });

Skip tests

Sometimes you need to skip tests in specific browsers. For example, tested features are not available in some browsers yet.

To skip all tests in suite you can use skip.in(/.*/).

All browsers from subsequent calls to .skip.in are added to the skip list:

js suite .skip.in('id1') .skip.in(/RegExp1/);

is equivalent to

js suite.skip.in([ 'id1', /RegExp1/ ]);

To skip test silently, use only.in function. This way, skipped browsers will not appear in the report.

For example:

suite.only.in(['chrome', 'firefox']);
suite.only.notIn(/ie/, 'opera');

Nested suites

Suites can be nested. In this case, inner suite inherits url, captureElements from outer. This properties can be overridden in inner suites without affecting the outer. Each new suite causes reload of the browser, even if URL was not changed.

gemini.suite('parent', function(parent) {
    parent.setUrl('/some/path')
        .setCaptureElements('.selector1', '.selector2')
        .capture('state');

    gemini.suite('first child', function(child) {
        //this suite captures same elements on different pages
        child.setUrl('/other/path')
            .capture('other state');
    });

    gemini.suite('second child', function(child) {
        //this suite captures different elements on the same page
        child.setCaptureElements('.next-selector')
            .capture('third state', function(actions, elements) {
                // ...
            });

        gemini.suite('grandchild', function(grandchild) {
            //child suites can have own childs
            grandchild.capture('fourth state');

        });
    });

    gemini.suite('third child', function(child) {
        //this suite uses completely different URL and set of elements
        child.setUrl('/some/another/path')
            .setCaptureElements('.different-selector')
            .capture('fifth state');
    });
});

Available actions

By calling methods of the actions argument of a callback you can program a series of steps to bring the block to desired state. All calls are chainable and next step is always executed after previous one has completed. In the following list element can be either CSS selector or result of a find call:

js actions.executeJS(function(window) { window.alert('Hello!'); });

Note that function is executed in a browser context, so any references to outer scope of callback won't work.

:warning: window.scrollTo does not work in Opera@12.16 (see details).

You can send a special key using one of the provided constants, i.e:

js actions.sendKeys(gemini.ARROW_DOWN);

Full list of special keys (there are shortcuts for commonly used keys):

NULL, CANCEL, HELP, BACK_SPACE, TAB, CLEAR, RETURN, ENTER, LEFT_SHIFTSHIFT, LEFT_CONTROLCONTROL, LEFT_ALTALT, PAUSE, ESCAPE, SPACE, PAGE_UP, PAGE_DOWN, END, HOME, ARROW_LEFTLEFT, ARROW_UPUP, ARROW_RIGHTRIGHT, ARROW_DOWNDOWN, INSERT, DELETE, SEMICOLON, EQUALS, NUMPAD0, NUMPAD1, NUMPAD2, NUMPAD3, NUMPAD4, NUMPAD5, NUMPAD6, NUMPAD7, NUMPAD8, NUMPAD9, MULTIPLY, ADD, SEPARATOR, SUBTRACT, DECIMAL, DIVIDE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, COMMANDMETA, ZENKAKU_HANKAKU.

Passing a context to tests

Add system.ctx field to your configuration file:

module.exports = {
    // ...
    system: {
        ctx: {
            foo: 'bar'
        }
    }
};

ctx will be available in tests via gemini.ctx method:

console.log(gemini.ctx); // {foo: 'bar'}

Recommendation: use ctx in your tests in favor of global variables.