Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Use of Script Language Closures for Test Automation Implementation

DZone's Guide to

Use of Script Language Closures for Test Automation Implementation

Learn about using closures for the script languages in automated testing tool Squish, to unify function calls with a different set of arguments.

· DevOps Zone ·
Free Resource

Easily enforce open source policies in real time and reduce MTTRs from six weeks to six seconds with the Sonatype Nexus Platform. See for yourself - Free Vulnerability Scanner. 

All supported script languages in the automated GUI Testing Tool Squish support closures. In this article, I’ll write up a simple example how closures can be used to unify function calls that seem to have a different set of arguments.

Assume a test snippet that tests two ways to open a settings dialog, together with two ways to cancel the dialog. The script snippet may look like this:

function main() {
    ...
    activateItem(waitForObjectItem(":MenuBar", "File"));
    activateItem(waitForObjectItem(":File_Menu", "Settings"));
    type(waitForObject(":Settings_Dialog"), "<Esc>");

    type(waitForObject(":MainWindow"), "<Ctrl+Shift+p>");
    clickButton(waitForObject(":Settings.Cancel_Button"));
    ...
}

To harden this script, e.g. test that the dialog was not open before opening, is really closed after closing, etc, the following refactoring is done:

function openPreferenceDialog(how)
{
    try {
        findObject(":Settings_Dialog");
        test.fail("Dialog is open");
        return;
    } catch (e) {}

    if (how == "menu") {
        activateItem(waitForObjectItem(":MenuBar", "File"));
        activateItem(waitForObjectItem(":File_Menu", "Settings"));
    } else if (how == "shortcut") {
        type(waitForObject(":MainWindow"), "<Ctrl+Shift+p>");
    }
    waitForObject(":Settings_Dialog");
}

function closePreferenceDialog(how)
{
    findObject(":Settings_Dialog");

    if (how == "cancelButton") {
        clickButton(waitForObject(":Settings.Cancel_Button"));
    } else if (how == "esc") {
        type(waitForObject(":Settings_Dialog"), "<Esc>");
    }
    for (var i = 0; i < 10; ++i) {
        try {
            findObject(":Settings_Dialog");
            snooze(.5);
        } catch (e) {
            break; // good, not found anymore
        }
    }
}

function main() {
    ...
    openPreferenceDialog("menu");
    closePreferenceDialog("esc");

    openPreferenceDialog("shortcut");
    closePreferenceDialog("cancelButton");
    ...
}

The how argument selects between the two open/close methods.
The openPreferenceDialog / closePreferenceDialog functions hard-code the objects they target. Therefore, refactor it further so that the how string argument is a function instead. So, something like this:

function openDialog(dialogName, openFunction)
{
    try {
        findObject(dialogName);
        test.fail("Dialog is open");
        return;
    } catch (e) {}

    openFunction();

    waitForObject(dialogName);
}

function main()
{
    ...
    openDialog(":Settings_Dialog", function() {
        activateItem(waitForObjectItem(":MenuBar", "File"));
        activateItem(waitForObjectItem(":File_Menu", "Settings"));
    });
    ...

By the way, for this particular example, I wouldn’t further generalize the script code in practice.

But for the purpose of this blog, using the scripts closure feature, let's make these openFunction closeFunction functions re-usable. Somehow, the object names must be passed to these functions, and intuitively pass these names to openDialog, which then passes them further to openFunction. Both activateItem and type take an object name and a second item or text argument but, for closing, clickButton and type don’t match in a number of arguments.
Here, closures provide a rather simple solution. Use a function that provides a function. The returned function can use the arguments passed to outer-function.

function typeFunction(objectName, text) {
    return function() {
        type(waitForObject(objectName), text);
    };
}

function clickMenuFunction(menuBar, menuBarItem, menu, item) {
    return function() {
        activateItem(waitForObjectItem(menuBar, menuBarItem));
        activateItem(waitForObjectItem(menu, item));
    }
}

function clickButtonFunction(objectName) {
    return function() {
        clickButton(waitForObject(objectName));
    };
}

function openDialog(dialogName, openFunction)
{
    try {
        findObject(dialogName);
        test.fail("Dialog is open");
        return;
    } catch (e) {}
    openFunction();
    waitForObject(dialogName);
}

function closeDialog(dialogName, closeFunction)
{
    findObject(dialogName);
    closeFunction()
    for (var i = 0; i < 10; ++i) {
        try {
            findObject(dialogName);
            snooze(.5);
        } catch (e) {
            break; //good, not found anymore
        }
    }
}

function main() {
    ...
    openDialog(":Settings_Dialog", clickMenuFunction(":MenuBar", "File", ":File_Menu", "Settings"));
    closeDialog(":Settings_Dialog", typeFunction(":Settings_Dialog", "<Esc>"));
    openDialog(":Settings_Dialog", typeFunction(":MainWindow", "<Ctrl+Shift+p>"));
    closeDialog(":Settings_Dialog", clickButtonFunction(":Settings.Cancel_Button"));
    ...
}

Closures are often used for creating so-called callback functions to prevent the need for global variables. The Squish API has waitFor, and all the Squish editions have an installEventHandler function, which can be called with a callback function.

Closure Reference

Automate open source governance at scale across the entire software supply chain with the Nexus Platform. Learn more.

Topics:
closures ,scripting ,software testing ,javascript ,devops ,test automation ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}