One of Squish's most useful features is the ability to access the toolkit's API from test scripts. This gives test engineers a great deal of flexibility and allows them to test just about anything in the AUT. With Squish's Web-specific API it is possible to find and query objects, access properties and methods, and evaluate arbitrary JavaScript code in the Web-application's context. In addition, Squish provides a convenience API (see How to Use the Web Convenience API (Section 13.3.6)) that provides facilities for executing common actions on Web sites such as clicking a button or entering some text.
A variety of examples that show how to use the scripting Web API to access and test complex Web elements is given in the How to Test Web Elements (Section 13.3.9) section.
![]() | Testing with the Web Proxy Mechanism |
|---|---|
There are two ways to work with web applications—directly, or using the web proxy mechanism introduced in Squish 4.1. For testing web applications with Safari on Mac OS X, Microsoft Internet Explorer on Windows, or Firefox/Mozilla on Unix it is best not to use the proxy mechanism since the mechanism imposes a few limitations. For more about how to use the web proxy mechanism see Web Proxy (Section 15.4.7). |
Squish provides two functions—findObject
and waitForObject—that return a reference
to the object (HTML or DOM element), for a given qualified object name.
The difference between them is that waitForObject waits for an object to become
available (up to its default timeout, or up to a specified timeout), so
it is usually the most convenient one to use. However, only
findObject can be used on hidden objects.
See the Web Object API (Section 14.10) for full details of Squish's Web classes and methods.
There are several ways to indentify a particular Web object:
Multiple-property (real) names—These names
consist of a list of one or more
property–name/property–value pairs, separated by spaces if
there is more than one, and the whole name enclosed in curly braces.
Given a name of this kind, Squish will search the document's DOM tree
until it finds a matching object. An example of such a name is:
“{tagName='INPUT' id='r1' name='rg' form='myform'
type='radio' value='Radio 1'}”.
Single property value—Given a particular value,
Squish will search the document's DOM tree until it finds an object
whose id, name or innerText
property has the specified value.
Path—The full path to the element is given.
An example of such a path is
“DOCUMENT.HTML1.BODY1.FORM1.SELECT1”.
To find an object's name, you can use the Spy to introspect the Web application's document. See the How to Use the Spy (Section 13.19.3) section for details.
If we want to interact with a particular object—for example, to check its properties, or to do something to it, such as click it, we must start by getting a reference to the object.
If we use the findObject function, it will
either return immediately with the object, or it will throw a catchable
exception if the object isn't available. (An object might not be
available because it is an AJAX object that only appears under certain
conditions, or it might only appear as the result of some JavaScript
code executing, and so on.) Here's a code snippet that shows how to use
findObject without risking an error being
thrown, by using the object.exists function:
radioName = ("{tagName='INPUT' id='r1' name='rg' form='myform' " +
"type='radio' value='Radio 1'}")
if object.exists(radioName):
radioButton = findObject(radioName)
clickButton(radioButton)
var radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " +
"type='radio' value='Radio 1'}";
if (object.exists(radioName)) {
var radioButton = findObject(radioName);
clickButton(radioButton);
}
my $radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " .
"type='radio' value='Radio 1'}"
if (object::exists($radioName)) {
my $radioButton = findObject($radioName);
clickButton($radioButton);
}
set radioName {{tagName='INPUT' id='r1' name='rg' form='myform' \
type='radio' value='Radio 1'}}
if {[object exists $radioName]} {
set radioButton [findObject $radioName]
invoke clickButton $radioButton
}
This will only click the radio button if it exists, that is, if it is
accessible at the time of the object.exists
call.
An alternative approach is to use the waitForObject function:
radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " +
"form='myform' type='radio' value='Radio 1'}")
clickButton(radioButton)
var radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " +
"form='myform' type='radio' value='Radio 1'}");
clickButton(radioButton);
my $radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " .
"form='myform' type='radio' value='Radio 1'}");
clickButton($radioButton);
set radioButton [waitForObject {{tagName='INPUT' id='r1' name='rg' \
form='myform' type='radio' value='Radio 1'}}]
invoke clickButton $radioButton
This will wait up to 20 seconds (or whatever the default timeout has been set to), and providing the radio button becomes accessible within that time, it is clicked.
Using the findObject and waitForObject functions in conjunction with
appropriate object identifiers means that we can access all the elements
in a Web document's object tree, and test their properties, and
generally interact with them.
For every object returned by the findObject
and waitForObject functions, it is possible
the evaluate an XPath statement. The object on which the XPath statement
is evaluated is used as the context node.
For example, to retrieve the reference to a link referring to the
URL www.froglogic.com which is a
child of the DIV element with the id
“mydiv”, we can use the following code:
div = findObject("{tagName='DIV' id='mydiv'}")
link = div.evaluateXPath("A[contains(@href," +
"'www.froglogic.com')]").snapshotItem(0)
var div = findObject("{tagName='DIV' id='mydiv'}");
var link = div.evaluateXPath("A[contains(@href," +
"'www.froglogic.com')]").snapshotItem(0);
my $div = findObject("{tagName='DIV' id='mydiv'}");
my $link = $div->evaluateXPath("A[contains(@href," .
"'www.froglogic.com')]")->snapshotItem(0);
set div [findObject {{tagName='DIV' id='mydiv'}}]
set link [invoke [invoke $div evaluateXPath \
"A[contains(@href, 'www.froglogic.com')]"] snapshotItem 0]
The XPath used here says, “find all A (anchor) tags
that have an href attribute, and whose value is
www.froglogic.com”. We then call the
snapshotItem method and ask it to retrieve the first
match—it uses 0-based indexing—which is returned as an object
of type HTML_Object Class (Section 14.10.24).
Each XPath query can produce a boolean (true or false), a number, a
string, or a group of elements as the result. Consequently, the
HTML_Object.evaluateXPath
method returns an object of type
HTML_XPathResult Class (Section 14.10.35) on which
you can query the result of the XPath evaluation.
How to Access Table Cell Contents (Section 13.3.9.5) has an example of
using the HTML_Object.evaluateXPath
method to extract the contents of an HTML table's cell.
![]() | XPath Queries |
|---|---|
For more information about how you can create XPath queries to help produce flexible and compact test scripts, refer to documentation that specializes in this topic. For example, we recommend the XPath Tutorial from the W3Schools Online Web Tutorials website. |
See also the Squish for Web tutorial Inserting Additional Verification Points (Section 7.4).
Using the script API it is possible to access most of the DOM properties for any HTML or DOM element in a Web application. See the Web Object API (Section 14.10) for full details of Squish's Web classes and methods.
Here is an example where we will change and query the
value property of a form's text element.
entry = waitForObject(
"{tagName='INPUT' id='input' form='myform' type='text'}")
entry.value = "Some new text"
test.log(entry.value)
var entry = waitForObject(
"{tagName='INPUT' id='input' form='myform' type='text'}");
entry.value = "Some new text";
test.log(entry.value);
my $entry = waitForObject(
"{tagName='INPUT' id='input' form='myform' type='text'}");
$entry->value = "Some new text";
test::log($entry->value);
set entry [waitForObject {{tagName='INPUT' id='input' \
form='myform' type='text'}}]
[property set $entry value "Some new text"]
test log [property get $entry value]
Squish provides similar script bindings to all of the standard DOM
elements' standard properties. But it is also possible to access the
properties of custom objects using the
property method. For example, to check a
DIV element's offset width, we can write code like this:
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
test.compare(div.property("offsetWidth"), 18)
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
test.compare(div.property("offsetWidth"), 18);
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
test::compare($div->property("offsetWidth"), 18);
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"] test compare [invoke $div property "offsetWidth"] 18
Note that for hidden elements we must always use the findObject function rather than the waitForObject function.
In addition to properties, you can call standard DOM functions on all Web objects from test scripts, using the API described in the Web Object API (Section 14.10).
For example, to get the first child node of a DIV element,
you could use the following test script which makes use of the
HTML_Object.firstChild function:
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
child = div.firstChild()
test.log(child.tagName)
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
var child = div.firstChild();
test.log(child.tagName);
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
my $child = $div->firstChild();
test::log($child->tagName);
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"] set child [invoke $div firstChild] test log [property get $child tagName]
Or, to get the text of the selected option from a select form element, we could use the following code:
element = findObject(
":{tagName='INPUT' id='sel' form='myform' type='select-one'}")
option = element.optionAt(element.selectedIndex)
test.log(option.text)
var element = findObject(
":{tagName='INPUT' id='sel' form='myform' type='select-one'}");
var option = element.optionAt(element.selectedIndex);
test.log(option.text);
my $element = findObject(
":{tagName='INPUT' id='sel' form='myform' type='select-one'}");
my $option = $element->optionAt($element->selectedIndex);
test::log($option->text);
set element [findObject ":{tagName='INPUT' id='sel' \
form='myform' type='select-one'}"]
set option [invoke $element optionAt [property get element selectedIndex]]
test log [property get $option text]
Squish provides script bindings like those shown here to all the
standard DOM elements' standard functions. And in addition, it is also
possible to call custom functions via a generic
invoke function. For example, to call a custom
myOwnFunction function with string argument
“an argument”, on a DIV element, we could
write code like this:
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
div.invoke("myOwnFunction", "an argument")
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
div.invoke("myOwnFunction", "an argument");
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
$div->invoke("myOwnFunction", "an argument");
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"] invoke $div "myOwnFunction" "an argument"
Beyond the DOM API bindings and the invoke
function, Squish offers a Browser object which can be
used by test scripts to query which browser is being used, as the
following Python snippet shows:
# This will print out the name of the browser:
test.log("We are running in " + Browser.name())
if Browser.id() == InternetExplorer:
...
elif Browser.id() == Mozilla:
...
elif Browser.id() == Firefox:
...
elif Browser.id() == Safari:
...
elif Browser.id() == Konqueror:
...
In addition to test scripts being able to access all the properties and
methods of DOM elements, it is also possible to let Squish execute
arbitrary JavaScript code in the Web browser's JavaScript interpreter
and to retrieve the results. For this purpose, Squish provides the
evalJS function. Here is an example of
its use:
style_display = evalJS("var d = document.getElementById(" +
"'busyDIV'); d ? d.style.display : ''")
var style_display = evalJS("var d = document.getElementById(" +
"'busyDIV'); d ? d.style.display : ''");
my $style_display = evalJS("var d = document.getElementById(" .
"'busyDIV'); d ? d.style.display : ''");
set style_display [invoke evalJS "var d = document.getElementById(\
'busyDIV'); d ? d.style.display : ''"]
The evalJS function returns the result of
the last statement executed—in this case the last statement is
d ? d.style.display : '' so if the document contains an
element with ID “busyDIV”, style_display will
be set to that element's style.display property's
value—otherwise it will be set to an empty string.
This section describes the script API Squish offers on top of the DOM API to make it easy to perform common user actions such as clicking a link, entering text, etc. All the functions provided by the API are listed in the Web Object API (Section 14.10) section in the Tools Reference Manual (Chapter 15). Here we will show a few examples to illustrate how the API is used.
In the example below, we click a link, select an option, and enter some text.
clickLink(":{tagName='A' innerText='Advanced Search'}")
selectOption(":{tagName='INPUT' id='sel' form='myform' " +
"type='select-one'}", "Banana")
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
"Some Text")
clickLink(":{tagName='A' innerText='Advanced Search'}");
selectOption(":{tagName='INPUT' id='sel' form='myform' " +
"type='select-one'}", "Banana");
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
"Some Text");
clickLink(":{tagName='A' innerText='Advanced Search'}");
selectOption(":{tagName='INPUT' id='sel' form='myform' " .
"type='select-one'}", "Banana");
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
"Some Text");
invoke clickLink ":{tagName='A' innerText='Advanced Search'}"
invoke selectOption ":{tagName='INPUT' id='sel' form='myform' \
type='select-one'}" "Banana"
invoke setText ":{tagName='INPUT' id='input' form='myform' \
type='text'}" "Some Text"
In these cases we identified the object using real (multi-property)
names; we could just have easily used symbolic names, or even object
references, instead. Note also that the full API contains far more
functions than the three mentioned here (clickLink, selectOption, and setText), although all of them are just as easy
to use.
In many simple cases, just waiting for a particular object to become
available using the waitForObject function
is sufficient.
However, in some cases we need to ensure that the page has loaded before
we attempt to access its objects. The special isPageLoaded function makes it possible to
synchronize a test script with a Web application's page loaded status.
We could use this function to wait for a Web page to be fully loaded before clicking a particular button on the page. For example, if a page has a button, we could ensure that the page is loaded before attempting to click the button, using the following code:
loaded = waitFor("isPageLoaded()", 5000)
if loaded:
clickButton(waitForObject(
":{tagName='INPUT' type='button' value='Login'}"))
else:
test.fatal("Page loading failed")
var loaded = waitFor("isPageLoaded()", 5000);
if (loaded)
clickButton(waitForObject(
":{tagName='INPUT' type='button' value='Login'}"));
else
test.fatal("Page loading failed");
my $loaded = waitFor("isPageLoaded()", 5000);
if ($loaded) {
clickButton(waitForObject(
":{tagName='INPUT' type='button' value='Login'}"));
}
else {
test::fatal("Page loading failed");
}
set loaded [waitFor "[invoke isPageLoaded]" 5000]
if {$loaded} {
invoke clickButton [invoke waitForObject \
":{tagName='INPUT' type='button' value='Login'}"]
} else {
test fatal "Page loading failed"
}
It is necessary to use the isPageLoaded
function to ensure that the page is loaded and its web objects are
potentially accessible. To access a particular
object we must still use the waitForObject
function—and we may even have to specify a longer timeout than the
default 20 000 milliseconds to allow for network latency.
In advanced AJAX applications, waiting for a page to be loaded and using
the waitForObject function is often
insufficient, since parts of the page will be loaded using asynchronous
AJAX requests that occur in the background. In such cases we must take
more sophisticated approaches to synchronization where we wait until the
background loading has finished in addition to waiting for particular
objects to become available.
When background loading is taking place, most Web toolkits display a visual cue—for example, a box which says "loading..."—to indicate to the user that the application is loading. We can use such a visual cue to synchronize our script by waiting until the loading cue has disappeared.
To show how to handle situations where AJAX is used for background loading we will develop an AJAX synchronization function for test scripts used for testing applications based on the Backbase AJAX toolkit. Backbase is just one of many Web toolkits that Squish supports, and although the example is specific to Backbase, it should translate for use with other toolkits without too much trouble.
Backbase uses a text box that displays the text "loading..." when loading is taking place, so we must develop a function that will tell us if loading is in progress:
def isBackbaseLoading():
if not object.exists("{tagName='DIV' id='loading'}"):
return False
div = findObject("{tagName='DIV' id='loading'}")
if isNull(div) or isNull(div.parentElement()):
return False
div = div.parentElement()
if div.style().value("display") == "none":
return False
return True
function isBackbaseLoading()
{
if (!object.exists("{tagName='DIV' id='loading'}"))
return false;
var div = findObject("{tagName='DIV' id='loading'}");
if (isNull(div) || isNull(div.parentElement()))
return false;
div = div.parentElement();
if (div.style().value("display") == "none")
return false;
return true;
}
sub isBackbaseLoading
{
if (!object::exists("{tagName='DIV' id='loading'}")) {
return 0;
}
my $div = findObject("{tagName='DIV' id='loading'}");
if (isNull($div) || isNull($div->parentElement())) {
return 0;
}
$div = $div->parentElement();
if ($div->style()->value("display") eq "none") {
return 0;
}
return 1;
}
proc isBackbaseLoading {} {
if {![object exists "{tagName='DIV' id='loading'}"]} {
return false
}
set div [findObject "{tagName='DIV' id='loading'}"]
if {[invoke isNull $div] || \
[invoke isNull [invoke $div parentElement]]} {
return false
}
set div [invoke $div parentElement]
if {[string equal [invoke $div style [invoke value "display"]] "none"]} {
return false
}
return true
}
The isBackbaseLoading function checks to see if
there is a DIV element with an id set to
“loading”, and if there is, whether it is displayed.
In practical testing we will always want to synchronize with the loading state after performing certain operations—for example, after the test script has clicked particular items which trigger some background loading. However, the loading might not begin immediately, but instead might occur after some small delay after the operation that made the loading necessary. So we need a function which first waits for the "loading..." cue to appear, and if it does, that then goes on to wait for a short amount of time (to allow the loading to actually begin), and finally that waits until the loading cue disappears again:
def syncBackbase():
loading = waitFor("isBackbaseLoading()", 2000)
if not loading:
return
waitFor("not isBackbaseLoading()")
function syncBackbase()
{
var loading = waitFor("isBackbaseLoading()", 2000);
if (!loading)
return;
waitFor("!isBackbaseLoading()");
}
sub syncBackbase
{
my $loading = waitFor("isBackbaseLoading()", 2000);
if (!$loading) {
return;
}
waitFor("!isBackbaseLoading()");
}
proc syncBackbase {} {
set loading [waitFor "[invoke isBackbaseLoading]" 2000]
if {!$loading} {
return
}
waitFor "![invoke isBackbaseLoading]"
}
Here we wait for up to two seconds to see if loading is taking place,
and if it is, we then wait indefinitely for the loading to be
finished (i.e., for isBackbaseLoading to return
false).
With the syncBackbase function available, we can call it
just after every operation that could cause loading to take place. But
if we were to do that our code would end up littered with calls to
syncBackbase—making it less clear—and also
it would be quite easy to forget to call it in some places. Fortunately,
Squish provides a nice solution to this problem. If we implement a
special function called waitUntilObjectReady, Squish
will call it automatically from every waitForObject call.
Here is a simple implementation that will ensure that background loading is always finished:
sub waitUntilObjectReady(obj):
syncBackbase()
function waitUntilObjectReady(obj)
{
syncBackbase();
}
sub waitUntilObjectReady
{
syncBackbase();
}
proc waitUntilObjectReady {obj} {
invoke syncBackbase
}
Now, whenever we call waitForObject,
Squish will call our waitUntilObjectReady function, and
this in turn will ensure that the AJAX application has finished loading.
This allows us to synchronize the test even if some of our test script's
actions cause asynchronous AJAX requests to take place.
We can put these functions into a shared script and include it in all of our test cases—this will allow us to use this advanced synchronization technique for all of our tests. (See also, How to Create and Use Shared Data and Shared Scripts (Section 13.21).)
Although the functions here are specific to the Backbase toolkit,
similar functions can be implemented for any other AJAX toolkit.
Squish's examples include the
examples/web/suite_examples/tst_backbase_pim
example which shows these functions in action. (See also
How to Create and Use Synchronization Points (Section 13.9).)
![]() | Squish for Web—Windows and Microsoft Internet Explorer-specific |
|---|---|
This section describes a limitation and workaround that only applies to Squish for Web with Microsoft Internet Explorer. |
When using Squish for Web with Microsoft Internet Explorer you might encounter a problem with
web sites that automatically redirect their users to a URL that has a
different protocol or hostname to the original URL. For example, if you
were recording on the site http://www.example.com and
completed a login form that redirected to either
https://www.example.com (i.e., a site with a different
protocol), or to http://www.example-secured.com (i.e., a
site with a different hostname), recording and subsequent replaying
would fail.
The reason for this limitation is that some parts of Squish run inside the browser itself and for Squish to communicate amongst its components it uses a technique called “cross-site-scripting”. As a security measure, Internet Explorer disallows this kind of communication—unless the source URL (i.e., the web site being interacted with) is in Microsoft Internet Explorer's list of trusted sites.
In the case of a change of protocol the easiest solution is to change
the URL that the loadUrl function uses
so that it doesn't mention the protocol. For example, change
"http://www.example.com" to
"www.example.com". This should work in most cases since
Internet Explorer uses wildcard matching for its trusted sites, so if it
has an entry for *.example.com, any URL whose site name
ends in that text will match—for example both
http://www.example.com and
https://secure.example.com will match.
In the case of a hostname that changes the easiest solution is to add
the other hostname to Microsoft Internet Explorer's list of trusted sites. Alternatively,
manually add a suitable call to the loadUrl function with the relevant hostname
when it is needed.
In this section we will cover how to test specific HTML elements in a Web application. This will allow us to verify that elements have properties with the values we expect and that form elements have their expected contents.
One aspect of testing that can be quite challenging is the creation of test verifications. As shown in the section Inserting Additional Verification Points (Section 7.4) in Tutorial: Starting to Test Web Applications (Chapter 7), most of this can be done using the Spy and its point & click interface. But in some cases it is actually more convenient—and more flexibile—to implement verification points directly in code.
To test and verify a widget and its properties or contents in code,
first we need access to the widget in the test script. To obtain a
reference to the widget, the waitForObject
function is used. This function finds the widget with the given name
and returns a reference to it. For this purpose we need to know the name
of the widget we want to test, and we can get the name using the Spy
tool (see How to Use the Spy (Section 13.19.3)) and adding the object to the Object Map (Section 15.10) (so that Squish will remember it) and then
copying the object's name (preferably its symbolic name) to the
clipboard ready to be pasted into our test. If we need to gather the
names of lots of widgets it is probably faster and easier to record a
dummy test during which we make sure that we access every widget we want
to verify in our manually written test script. This will cause Squish
to add all the relevant names to the Object Map (Section 15.10),
which we can then copy and paste into our code.
One of the most common test requirements is to verify that a particular
element is enabled or disabled at some point during the test run. This
verification is easily made by checking an element's
disabled property.
entry = waitForObject("{tagName='INPUT' id='input' " +
"form='myform' type='text'}")
test.verify(not entry.disabled)
var entry = waitForObject("{tagName='INPUT' id='input' " +
"form='myform' type='text'}");
test.verify(!entry.disabled);
my $entry = waitForObject("{tagName='INPUT' id='input' " .
"form='myform' type='text'}");
test::verify(!$entry->disabled);
set entry [waitForObject "{tagName='INPUT' id='input' \
form='myform' type='text'}"]
test verify [expr ![property get $entry disabled]]
Here we have verified that a text entry element is enabled (i.e., that
its disabled property is false). To check that the element
is disabled, we would eliminate the negation (not or
! depending on language).
To verify that a radiobutton or checkbox is checked, we just need to
query its checked property.
radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " +
"form='myform' type='radio' value='Radio 1'}")
test.verify(radiobutton.checked)
var radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " +
"form='myform' type='radio' value='Radio 1'}");
test.verify(radiobutton.checked);
my $radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " .
"form='myform' type='radio' value='Radio 1'}");
test::verify($radiobutton->checked);
set radiobutton [waitForObject ":{tagName='INPUT' id='r1' name='rg' \
form='myform' type='radio' value='Radio 1'}"]
test verify [property get $radiobutton checked]
The coding pattern shown here—get a reference to an object, then verify the value of one of its properties—is very common and can be applied to any element.
Both the text and textarea form elements have
a value property, so it is easy to check what they
contain.
entry = waitForObject("{tagName='INPUT' id='input' " +
"form='myform' type='text'}")
test.compare(entry.value, "Ternary")
var entry = waitForObject("{tagName='INPUT' id='input' " +
"form='myform' type='text'}");
test.compare(entry.value, "Ternary");
my $entry = waitForObject("{tagName='INPUT' id='input' " .
"form='myform' type='text'}");
test::compare($entry->value, "Ternary");
set entry [waitForObject "{tagName='INPUT' id='input' \
form='myform' type='text'}"]
test compare [property get $entry value] "Ternary"
This follows exactly the same pattern as we used for the earlier examples.
Web forms usually present single selection lists (of element type
select-one) in comboboxes and multiple selection lists (of
element type select) in listboxes. We can easily check
which items are selected.
selection = waitForObject(":{tagName='INPUT' id='sel' " +
"form='myform' type='select-one'}")
test.compare(selection.selectedIndex, 2)
test.compare(selection.selectedOption, "Cavalier")
var selection = waitForObject(":{tagName='INPUT' id='sel' " +
"form='myform' type='select-one'}");
test.compare(selection.selectedIndex, 2);
test.compare(selection.selectedOption, "Cavalier");
my $selection = waitForObject(":{tagName='INPUT' id='sel' " .
"form='myform' type='select-one'}");
test::compare($selection->selectedIndex, 2);
test::compare($selection->selectedOption, "Cavalier");
set selection [waitForObject ":{tagName='INPUT' id='sel' \
form='myform' type='select-one'}"]
test compare [property get $selection selectedIndex] 2
test compare [property get $selection selectedOption] "Cavalier"
Here we retrieve the selected item from a single selection list box and verify that the third item (the item at index position 2), is selected, and that it has the text “Cavalier”.
selection = waitForObject(":{tagName='INPUT' id='sel' " +
"form='myform' type='select'}")
test.verify(selection.optionAt(0).selected)
test.verify(not selection.optionAt(1).selected)
test.verify(selection.optionAt(2).selected)
test.compare(selection.optionAt(1).text, "Round Head")
var selection = waitForObject(":{tagName='INPUT' id='sel' " +
"form='myform' type='select'}");
test.verify(selection.optionAt(0).selected);
test.verify(!selection.optionAt(1).selected);
test.verify(selection.optionAt(2).selected);
test.compare(selection.optionAt(1).text, "Round Head");
my $selection = waitForObject(":{tagName='INPUT' id='sel' " .
"form='myform' type='select'}");
test::verify($selection->optionAt(0)->selected);
test::verify(!$selection->optionAt(1)->selected);
test::verify($selection->optionAt(2)->selected);
test::compare($selection->optionAt(1)->text, "Round Head");
set selection [waitForObject ":{tagName='INPUT' id='sel' \
form='myform' type='select'}"]
test verify [property get [invoke selection optionAt 0] selected]
test verify [expr ![property get [invoke selection optionAt 1] selected]]
test verify [property get [invoke selection optionAt 2] selected]
test.compare [property get [invoke selection optionAt 1] text] \
"Round Head"
In this example, we retrieve a reference to a mulitple selection list—normally represented by a listbox—and then retrieve its option items. We then verify that the first option (at index position 0) is selected, that the second option (at index position 1) is not selected, and that the third option (at index position 2) is selected. We also verify the second option's text is “Round Head”.
See also the HTML_Select Class (Section 14.10.27) class, its HTML_Select.optionAt function, and its
text and selected properties.
Another common requirement when testing Web applications is to retrieve the text contents of particular cells in HTML tables. This is actually very easy to do with Squish.
All HTML elements retrieved with the findObject function and the waitForObject function have an HTML_Object.evaluateXPath method that can be
used to query the HTML element, and which returns the results of the
query. We can make use of this to create a generic custom
getCellText function that will do the job we want. Here's
an example implementation:
def getCellText(tableObject, row, column):
return tableObject.evaluateXPath("TBODY/TR[%d]/TD[%d]" % (
row + 1, column + 1)).stringValue
function getCellText(tableObject, row, column)
{
return tableObject.evaluateXPath("TBODY/TR[" + (row + 1) +
"]/TD[" + (column + 1) + "]").stringValue;
}
sub getCellText
{
my ($tableObject, $row, $column) = @_;
++$row;
++$column;
return $tableObject->evaluateXPath(
"TBODY/TR[$row]/TD[$column]")->stringValue;
}
proc getCellText {tableObject row column} {
incr row
incr column
set argument "TBODY/TR[$row]/TD[$column]"
return [property get [invoke $tableObject \
evaluateXPath $argument] stringValue]
}
An XPath is kind of like a file path in that each component is separated
by a /. The XPath used here says, “find every
TBODY tag, and inside each one find the row-th
TR tag, and inside that find the column-th
TD tag”. The result is always an object of type
HTML_XPathResult Class (Section 14.10.35);
here we return the result query as a single string value using the
result's stringValue property. (So if there was more than
one TBODY tag in the document that had a cell at the row
and column we wanted, we'd actually get the text of all of them.) We must
add 1 to the row and to the column because XPath queries use 1-based
indexing, but we prefer our functions to have 0-based indexing since
that is the kind used by all the scripting languages that Squish
supports. The function can be used like this:
table = waitForObject(htmlTableName) text = getCellText(table, 23, 11)
var table = waitForObject(htmlTableName); var text = getCellText(table, 23, 11);
my $table = waitForObject($htmlTableName); my $text = getCellText($table, 23, 11);
set table [waitForObject $htmlTableName] set text [getCellText $table 23 11]
This code will return the text from the cell at the 22nd row and 10th
column of the HTML table whose name is in the htmlTableName
variable.
Squish's XPath functionality is covered in How to Use XPath (Section 13.3.2).
Of course it is also possible to verify the states and contents of any other element in a Web application's DOM tree.
For example, we might want to verify that a table
with the ID
result_table contains the text—somewhere in the
table, we don't care where—“Total: 387.92”.
table = waitForObject("{tagName='TABLE' id='result_table]'}")
contents = table.innerText
test.verify(contents.find("Total: 387.92") != -1)
var table = waitForObject("{tagName='TABLE' id='result_table]'}");
var contents = table.innerText;
test.verify(contents.indexOf("Total: 387.92") != -1);
my $table = waitForObject("{tagName='TABLE' id='result_table]'}");
my $contents = $table->innerText;
test::verify(index($contents, "Total: 387.92") != -1);
set table [waitForObject "{tagName='TABLE' id='result_table]'}"]
set contents [property get $table innerText]
test verify [expr [string first "Total: 387.92" $contents] != -1]
The innerText property gives us the entire table's text as
a string, so we can easily search it.
Here's another example, this time checking that a DIV tag with the ID
syncDIV is hidden.
div = waitForObject(":{tagName='DIV' id='syncDIV'}")
test.compare(div.style().value("display"), "hidden")
var div = waitForObject(":{tagName='DIV' id='syncDIV'}");
test.compare(div.style().value("display"), "hidden");
my $div = waitForObject(":{tagName='DIV' id='syncDIV'}");
test::compare($div->style()->value("display"), "hidden");
set div [waitForObject ":{tagName='DIV' id='syncDIV'}"]
test compare [invoke $div style [invoke value "display"]] "hidden"
Notice that we must use the HTML_Object.style function (rather than
writing, say div.style.display).
Often such DIV elements are used for synchronization. For example, after a new page is loaded, we might want to wait until a particular DIV element exists and is hidden—perhaps some JavaScript code in the HTML page hides the DIV, so when the DIV is hidden we know that the browser is ready because the JavaScript has been executed.
def isDIVReady(name):
if not object.exists(":{tagName='DIV' id='%s'}" % name):
return False
return waitForObject(":{tagName='DIV' id='syncDIV'}").style().value(
"display") == "hidden":
# later on...
waitFor("isDIVReady('syncDIV')")
function isDIVReady(name)
{
if (!object.exists(":{tagName='DIV' id='" + name + "'}"))
return false;
return waitForObject(":{tagName='DIV' id='syncDIV'}").style().value(
"display") == "hidden";
}
// later on...
waitFor("isDIVReady('syncDIV')");
sub isDIVReady
{
my $name = shift @_;
if (!object::exists(":{tagName='DIV' id='$name'}")) {
return 0;
}
return waitForObject(":{tagName='DIV' id='syncDIV'}")->style()->value(
"display") eq "hidden";
}
# later on...
waitFor("isDIVReady('syncDIV')");
proc isDIVReady {name} {
if {![object exists ":{tagName='DIV' id='${name}'}"]} {
return false
}
set div [waitForObject ":{tagName='DIV' id='syncDIV'}"]
set display [invoke $div style [invoke value "display"]]
return [string equal $display "hidden"]
}
# later on...
[waitFor "isDIVReady('syncDIV')"]
We can easily use the waitFor function to
make Squish wait for the code we give it to execute to complete.
(Although it is designed for things that won't take too long.)
This tutorial takes you through the process of migrating a pre-existing test script into a data-driven one. It expects you to know how to set up simple test scripts and how to use the Squish IDE for recording, editing, and running test scripts. Creating whole test cases from scratch is not covered here, so if you're just starting out using Squish for Web for web testing you are better off starting with the tutorial (Tutorial: Starting to Test Web Applications (Chapter 7)).
After briefly explaining the test script that this tutorial is based on
(Starting with an Ordinary Test Script (Section 13.3.10.1)), we will set up some test data in Setting up the Test Data (Section 13.3.10.2) and then integrate the test data
into our test script in Making a Test Script Data Driven (Section 13.3.10.3). The
complete JavaScript version of the test script can be found in
squish/examples/web/suite_examples/tst_icefaces_addressbook_datadriven.
Our starting point for this tutorial will be the following test script:
def main():
loadUrl(":http://address.icefaces.org/address/")
# wait for the first entry object to become available
waitForObject(":_id0:title_select-one")
# check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, True)
# enter some data
selectOption(":_id0:title_select-one", "Mr.")
setText(":_id0:firstName_text", "Reginald")
setText(":_id0:lastName_text", "Stadlbauer")
setText(":_id0:city_text", "San Francisco")
# check that after entering a city, the state is automatically and
# correctly chosen
setFocus(":_id0:lastName_text");
if not test.verify(waitFor(
"findObject(':_id0:state_text').value == 'CA'", 10000)):
closeWindow(":[Window]")
return
# input ZIP
selectOption(":_id0:zipSelect_select-one", "94103")
# check that the submit button is now enabled
setFocus(":_id0:lastName_text")
if not test.verify(waitFor(
"findObject(':_id0:Submit_image').disabled == false", 10000)):
closeWindow(":[Window]")
return
# submit
clickButton(":_id0:Submit_image")
# wait for results page
waitForContextExists(":response.iface")
waitForObject(":_id1:_id3_SPAN")
# verify that data is stored and displayed correctly
test.compare(findObject(":_id1:_id3_SPAN").innerText, "Reginald")
test.compare(findObject(":_id1:_id6_SPAN").innerText, "CA")
# close browser
closeWindow(":[Window]");
function main()
{
loadUrl(":http://address.icefaces.org/address/");
// wait for the first entry object to become available
waitForObject(":_id0:title_select-one");
// check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, true);
// enter some data
selectOption(":_id0:title_select-one", "Mr.");
setText(":_id0:firstName_text", "Reginald");
setText(":_id0:lastName_text", "Stadlbauer");
setText(":_id0:city_text", "San Francisco");
// check that after entering a city, the state is automatically and
// correctly chosen
setFocus(":_id0:lastName_text");
if (!test.verify(waitFor(
"findObject(':_id0:state_text').value == 'CA'", 10000))) {
closeWindow(":[Window]");
return;
}
// input ZIP
selectOption(":_id0:zipSelect_select-one", "94103");
// check that the submit button is now enabled
setFocus(":_id0:lastName_text");
if (!test.verify(waitFor(
"findObject(':_id0:Submit_image').disabled == false", 10000))) {
closeWindow(":[Window]");
return;
}
// submit
clickButton(":_id0:Submit_image");
// wait for results page
waitForContextExists(":response.iface");
waitForObject(":_id1:_id3_SPAN");
// verify that data is stored and displayed correctly
test.compare(findObject(":_id1:_id3_SPAN").innerText, "Reginald");
test.compare(findObject(":_id1:_id6_SPAN").innerText, "CA");
// close browser
closeWindow(":[Window]");
}
sub main
{
loadUrl(":http:#address.icefaces.org/address/");
# wait for the first entry object to become available
waitForObject(":_id0:title_select-one");
# check that the submit button is disabled
test::compare(findObject(":_id0:Submit_image")->disabled, 1);
# enter some data
selectOption(":_id0:title_select-one", "Mr.");
setText(":_id0:firstName_text", "Reginald");
setText(":_id0:lastName_text", "Stadlbauer");
setText(":_id0:city_text", "San Francisco");
# check that after entering a city, the state is automatically and
# correctly chosen
setFocus(":_id0:lastName_text");
if (!test::verify(waitFor(
"findObject(':_id0:state_text')->value eq 'CA'", 10000))) {
closeWindow(":[Window]");
return;
}
# input ZIP
selectOption(":_id0:zipSelect_select-one", "94103");
# check that the submit button is now enabled
setFocus(":_id0:lastName_text");
if (!test::verify(waitFor(
"findObject(':_id0:Submit_image')->disabled == 0", 10000))) {
closeWindow(":[Window]");
return;
}
# submit
clickButton(":_id0:Submit_image");
# wait for results page
waitForContextExists(":response.iface");
waitForObject(":_id1:_id3_SPAN");
# verify that data is stored and displayed correctly
test::compare(findObject(":_id1:_id3_SPAN")->innerText, "Reginald");
test::compare(findObject(":_id1:_id6_SPAN")->innerText, "CA");
# close browser
closeWindow(":[Window]");
}
This test script goes to http://address.icefaces.org/address/, a web page with an address form example. The test waits until the page has been loaded completely, and then enters some data into the form's fields and presses the submit button. At various points some of the web application's states are verified. For example, the test script checks the submit buttons' state in two different places, and after entering a city it checks whether the web application chooses the correct state. In addition, once the the form has been submitted, the test script checks whether the web application correctly received the data that was entered and which is displayed on the page that appears after the submit is complete.
This test script is fully functional, but it only tests the web application with a single address. If we want the address form to be tested using multiple addresses, we must adapt our test script. One obvious approach would be to duplicate the code as many times as necessary but changing the addresses in each copy. This is easy to do, but extremly difficult to maintain, for example, if we wanted to make a change in the test to correspond with some change in the web application we'd have to change every copy of the code. Another approach is to put most of the code into a separate function, passing the address details as parameters, and calling the function in a loop that reads addresses from an array of addresses. This would work fine and also be perfectly maintainable, but it still means that we must mix the data with the code. What would be better is if we could separate the data and have the test script operate on whatever address data we made available. This would allow us to change the data, for example, adding to it, without having to change the test script at all. This is the essence of data-driven testing, and we will see how to do it in the following sections.
The first step in migrating the test script into a data-driven one is to set up some test data.
We have chosen to store the test data in a file with the format of one record per line, and fields separated by tabs. The file's first line is not a data line, but instead holds the column captions. Here's what a tiny example data file might look like (with the → symbol used to signify tabs):
Title→FirstName→LastName→City→State→ZIP Mr.→Reginald→Stadlbauer→San Francisco→CA→94103 Mrs.→Tanja→Rondi→New York→NY→10004
Data files can either be created separately, for example, using a spreadsheet or even a plain text editor, or using Squish's built-in functionality (which is what we'll use for this example). [16]
We can create a test data file using the Squish IDE. Test data files can be
added to a single test case, or—for data that we want several test
cases to use—added to the test suite. To do either, click the test
case or the test suite to select it, and then choose
|| from the menu (or right click
the test case or test suite and click
| from the context menu). This will result
in the New Squish Test Data dialog (Section 16.3.7) appearing.
We can enter the name of the test data file and then create the test
data in the Squish IDE by adding columns, renaming columns, and adding rows
of data. We'll call the test data file,
addresses.tsv; once it has been created it will be
opened automatically ready to be populated with data.
Our test script uses six values (Title, First Name, Last Name, City, State, and ZIP code) to represent one address. So we will create six columns, one for each of these. And since the column names will be used in the test script it is best to give them simple names—here, we'll call them "Title", "FirstName", "LastName", "City", "State" and "ZIP".
With the columns created and named we can now populate the test data file with data. Enter whatever names and addresses you like, or use those shown above.
Now we must adapt our original test script to make use of our test data rather than using the single hard-coded address it has at present. This is done by making the test script open the test data and iterate over every record (i.e., address) in the test data, and for each record performing the operations we performed on the one original address on each of the addresses in the test data. We can either do this by changing our code manually, or by asking Squish to apply the necessary changes for us.
If we want Squish to do the changes, once the test data has been
created, we must go to the editor that has the code and select the code
that we want to make data driven. With the code selected we then
right-click to pop up the context menu and choose the menu option. In this example,
we must select almost all of the lines in the file, starting with the
waitForObject(":_id0:title_select-one") statement up to and
including the clickButton(":_id1:Return_image") call.
In response, Squish will pop up a dialog from which you can choose
which test data file you want to use. After clicking
in the dialog, the start of your script
should now look something like this:
def main():
# open the URL
loadUrl(":http://address.icefaces.org/address/")
for record in testData.dataset("addresses.tsv"):
# read record fields
title = testData.field(record, "Title")
firstName = testData.field(record, "FirstName")
lastName = testData.field(record, "LastName")
city = testData.field(record, "City")
state = testData.field(record, "State")
zip = testData.field(record, "ZIP")
# wait for the first entry object to be available
waitForObject(":_id0:title_select-one")
# check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, True)
function main()
{
// open the URL
loadUrl(":http://address.icefaces.org/address/");
var address_set = testData.dataset("addresses.tsv");
for (var index in address_set) {
// read record fields
var title = testData.field(address_set[index], "Title");
var firstName = testData.field(address_set[index], "FirstName");
var lastName = testData.field(address_set[index], "LastName");
var city = testData.field(address_set[index], "City");
var state = testData.field(address_set[index], "State");
var zip = testData.field(address_set[index], "ZIP");
// wait for the first entry object to be available
waitForObject(":_id0:title_select-one");
// check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, true);
sub main
{
# open the URL
loadUrl(":http://address.icefaces.org/address/");
my @address_set = testData::dataset("addresses.tsv");
foreach (@address_set) {
# read record fields
my $record = $_;
my $title = testData::field($record, "Title");
my $firstName = testData::field($record, "FirstName");
my $lastName = testData::field($record, "LastName");
my $city = testData::field($record, "City");
my $state = testData::field($record, "State");
my $zip = testData::field($record, "ZIP");
# wait for the first entry object to be available
waitForObject(":_id0:title_select-one");
# check that the submit button is disabled
test::compare(findObject(":_id0:Submit_image")->disabled, 1);
The inserted code uses Squishs testData.dataset function to read the test data
file. Afterwards it loops through all the records in the test data and for
each one uses the testData.field function
to retrieve the data. The column caption can be used to identify the
individual fields. Conveniently, Squish creates a separate variable
for each field's data, something that we can make use of to make the
code fully data driven.
The next step is to replace all occurences of hard-coded data with the
test data values. Squish also has a convenience function to do this.
To use it, select a hard-coded value you want to replace—for
example, select the second parameter, "Mr." in
selectOption(":_id0:title_select-one", "Mr."). Now
right-click to pop up the context menu and this time choose the
. This opens
a new submenu from which you can choose the field to use when replacing
the selected text. Choose and
Squish will automatically replace the hard-coded value with something
like testData.field(address_set[index], "Title"). Repeat
this for all other hard-coded values so that the script now reads all of
its data from the test data file.
Since we are now testing multiple addresses rather than just one, the
original behavior where in the event of a test.verify failing the script closed the browser
and cancelled is no longer appropriate. Instead, in the event of a
failure we want the form to be reset and the loop continued from the top
with the next address. To accomplish this, we need to replace the two
return statements with continue statements
(which is valid since the relevant bits of the code are now inside a
loop).
Although it is convenient to use Squish's menu options to perform the conversion, it can be done just as easily by hand. Either way, at the end the first part of the test case should now look something like this (the second part follows):
def main():
# open the URL and open the dataset
loadUrl(":http:#address.icefaces.org/address/")
for record in testData.dataset("addresses.tsv"):
# read record fields
title = testData.field(record, "Title")
firstName = testData.field(record, "FirstName")
lastName = testData.field(record, "LastName")
city = testData.field(record, "City")
state = testData.field(record, "State")
zip = testData.field(record, "ZIP")
# wait for the first entry object to be available
waitForObject(":_id0:title_select-one")
# check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, True)
# enter some data
selectOption(":_id0:title_select-one",
testData.field(record, "Title"))
setText(":_id0:firstName_text",
testData.field(record, "FirstName"))
setText(":_id0:lastName_text", testData.field(record, "LastName"))
setText(":_id0:city_text", testData.field(record, "City"))
# check that after entering a city, the state is automatically
# chosen correctly
setFocus(":_id0:lastName_text")
if not test.verify(waitFor(
"findObject(':_id0:state_text').value == state", 10000)):
clickButton(":_id0:Reset_image")
continue
# input ZIP
selectOption(":_id0:zipSelect_select-one",
testData.field(record, "ZIP"))
function main()
{
// open the URL and open the dataset
loadUrl(":http://address.icefaces.org/address/");
var address_set = testData.dataset("addresses.tsv");
// go through all records
for (var index in address_set) {
// read record fields
var title = testData.field(address_set[index], "Title");
var firstName = testData.field(address_set[index], "FirstName");
var lastName = testData.field(address_set[index], "LastName");
var city = testData.field(address_set[index], "City");
var state = testData.field(address_set[index], "State");
var zip = testData.field(address_set[index], "ZIP");
// wait for the first entry object to be available
waitForObject(":_id0:title_select-one");
// check that the submit button is disabled
test.compare(findObject(":_id0:Submit_image").disabled, true);
// enter some data
selectOption(":_id0:title_select-one",
testData.field(address_set[index], "Title"));
setText(":_id0:firstName_text",
testData.field(address_set[index], "FirstName"));
setText(":_id0:lastName_text",
testData.field(address_set[index], "LastName"));
setText(":_id0:city_text",
testData.field(address_set[index], "City"));
// check that after entering a city, the state is automatically
// chosen correctly
setFocus(":_id0:lastName_text");
if (!test.verify(waitFor(
"findObject(':_id0:state_text').value == state", 10000))) {
clickButton(":_id0:Reset_image");
continue;
}
// input ZIP
selectOption(":_id0:zipSelect_select-one",
testData.field(address_set[index], "ZIP"));
sub main
{
# open the URL and open the dataset
loadUrl(":http:#address.icefaces.org/address/");
my @address_set = testData::dataset("addresses.tsv");
foreach (@address_set) {
# read record fields
my $record = $_;
my $title = testData::field($record, "Title");
my $firstName = testData::field($record, "FirstName");
my $lastName = testData::field($record, "LastName");
my $city = testData::field($record, "City");
my $state = testData::field($record, "State");
my $zip = testData::field($record, "ZIP");
# wait for the first entry object to be available
waitForObject(":_id0:title_select-one");
# check that the submit button is disabled
test::compare(findObject(":_id0:Submit_image")->disabled, 1);
# enter some data
selectOption(":_id0:title_select-one",
testData::field($record, "Title"));
setText(":_id0:firstName_text",
testData::field($record, "FirstName"));
setText(":_id0:lastName_text",
testData::field($record, "LastName"));
setText(":_id0:city_text",
testData::field($record, "City"));
# check that after entering a city, the state is automatically
# chosen correctly
setFocus(":_id0:lastName_text")
if (!test::verify(waitFor(
"findObject(':_id0:state_text')->value eq $state", 10000))) {
clickButton(":_id0:Reset_image");
continue;
}
# input ZIP
selectOption(":_id0:zipSelect_select-one",
testData::field($record, "ZIP"));
And the second part of the test script should look like this:
# check that the submit button is now enabled
setFocus(":_id0:lastName_text")
if not test.verify(waitFor(
"findObject(':_id0:Submit_image').disabled == False", 10000)):
clickButton(":_id0:Reset_image")
continue
# submit
clickButton(":_id0:Submit_image")
# wait for the results page
waitForContextExists(":response.iface")
waitForObject(":_id1:_id3_SPAN")
# verify that the data was stored and displayed correctly
test.compare(findObject(":_id1:_id3_SPAN").innerText,
testData.field(record, "FirstName"))
test.compare(findObject(":_id1:_id6_SPAN").innerText,
testData.field(record, "State"))
# go back to enter another address
clickButton(":_id1:Return_image")
# close browser
closeWindow(":[Window]")
// check that the submit button is now enabled
setFocus(":_id0:lastName_text");
if (!test.verify(waitFor(
"findObject(':_id0:Submit_image').disabled == false", 10000))) {
clickButton(":_id0:Reset_image");
continue;
}
// submit
clickButton(":_id0:Submit_image");
// wait for the results page
waitForContextExists(":response.iface");
waitForObject(":_id1:_id3_SPAN");
// verify that the data was stored and displayed correctly
test.compare(findObject(":_id1:_id3_SPAN").innerText,
testData.field(address_set[index], "FirstName"));
test.compare(findObject(":_id1:_id6_SPAN").innerText,
testData.field(address_set[index], "State"));
// go back to enter another address
clickButton(":_id1:Return_image");
}
// close browser
closeWindow(":[Window]");
}
# check that the submit button is now enabled
setFocus(":_id0:lastName_text");
if (!test::verify(waitFor(
"findObject(':_id0:Submit_image')->disabled == 0", 10000))) {
clickButton(":_id0:Reset_image");
continue;
}
# submit
clickButton(":_id0:Submit_image");
# wait for the results page
waitForContextExists(":response.iface");
waitForObject(":_id1:_id3_SPAN");
# verify that the data was stored and displayed correctly
test::compare(findObject(":_id1:_id3_SPAN")->innerText,
testData::field($record, "FirstName"));
test::compare(findObject(":_id1:_id6_SPAN")->innerText,
testData::field($record, "State"));
# go back to enter another address
clickButton(":_id1:Return_image");
}
# close browser
closeWindow(":[Window]");
}
The complete JavaScript version of the test script can also be found in
squish/examples/web/suite_examples/tst_icefaces_addressbook_datadriven.
This example demonstrates how to load test a Web server using Squish. The following set up is assumed: Machine L controls the execution of the load testing scripts and has Squish installed along with a Python interpreter. Machine L also has the test suite to be executed. Machines C1 to Cn are the ones where the the Web browsers will be running. They all need to the squishserver executable installed and running. Machine W is the Web server that will be put under load.
As machine W (the Web server) and machine L controlling the tests are physically different machines we need a way to retrieve system information over the network. The Simple Network Management Protocol (SNMP) is ideal for this task.
The load test is done by a Python script
(loadtest.py) which is supplied along with
Squish's examples:
examples/loadtesting/loadtest.py. The script starts
all the squishrunner processes and makes them connect to the
squishserver processes and the machines C1 to Cn.
All the details about the number of instances, start delays, the target
host etc., are defined and documented at the top of the script. Simply
adapt them to your needs.
The script will record the start and end times of all squishrunner runs. And in a secondary thread the script polls for system information from the webserver and records this too. Data about both the loads and the runs are written to tab separated values files and can therefore be easily evaluated in succeeding steps. (Or you can modify the script to produce the output in another format if you prefer.)
To make squishrunner be able to connect to remote machines, on each
remote machine the configuration option ALLOWED_HOSTS in
the squishrunnerrc file must be set to the host
starting the squishrunner processes on all those machines C1 to
Cn. (see Distributed Tests (Section 15.1.2)).
Machine W (the Web server) needs to run a SNMP daemon. It can be installed and set up easily. For configuring, the snmpconf command may prove useful. For example:
snmpconf -g basic_setup
Sources, binaries, documentation, and tutorials for this tool can be found at http://net-snmp.sourceforge.net. In addition the load testing script uses the command line SNMP utilities, which are also needed on Machine L. It would also be possible to use a Python SNMP module from its standard library or a third-party SNMP module—that's something you might want to do for yourself if you find you use the script a lot.
So in parallel with the squishrunners we will record system
information using the command line SNMP utilities. At the end of the
script the information is written to a file. Be aware that you may need
to adjust the usage of the snmpget program to
match your SNMP settings. The cutAvg function is a
helper function for extracting what we need from the
snmpget program's output.
def cutAvg(snmpString):
return snmpString.strip().rsplit("STRING: ", 1)[1]
loads = []
def sysinfo():
def loadOne(number):
cmd = "snmpget -v 1 -c commro %s laLoad.%d" % (
WEBSERVER_HOST, number)
tmp = os.popen(cmd, "r")
reply = tmp.read()
reply = cutAvg(reply)
tmp.close()
return reply
while 1:
l1 = loadOne(1)
l5 = loadOne(2)
l15 = loadOne(3)
loads.append({'ts': time.time(), '1m': l1, '5m': l5, '15m': l15})
time.sleep(5)
We will store information (host, start time, and end time), related to
every squishrunner run in a SquishrunnerRun object.
Taking a SquishrunnerRun object as parameter the
runsuite function sets the starttime
variable, initiates the connection to the squishserver on the given
host, and stores the endtime after the squishrunner has
finished.
class SquishrunnerRun:
id = 0
starttime = 0
endtime = 0
host = ''
def __init__(self, id, host):
self.id = id
self.host = host
def duration(self):
return self.endtime - self.starttime
def runSuite(srr):
srr.starttime = time.time()
srrCmd = " ".join([SQUISHRUNNER_EXEC, "--host", srr.host,
"--testsuite", TEST_SUITE,
"--reportgen xml,%s%d.xml" % (REPORTS_DIR, srr.id)])
os.system(srrCmd)
srr.endtime = time.time()
print "call %d finished; needed %s" % (
srr.id, srr.endtime - srr.starttime)
Having defined the SquishrunnerRun class and the
runSuite function we are now able to start the
testing itself. For each of the RUN_COUNT runs, the next
host will be associated with a newly created
SquishrunnerRun object. The next
runSuite function call will be started within a new
thread. After waiting a specified amount of time we will continue. In
addition we store all the SquishrunnerRun objects in a
list.
runs = []
for i in range(RUN_COUNT):
tmp = SquishrunnerRun(i,
SQUISHSERVER_HOSTS[i % len(SQUISHSERVER_HOSTS)])
runs.append(tmp)
thread.start_new_thread(runSuite, (tmp,))
time.sleep(RUN_DELAY)
Now having started all the squishrunner processes, the script must
wait until all of them are finished. If a squishrunner process has
finished, its endtime is set. So we must wait until none of
the SquishrunnerRun's endtimes are set to 0.
def allRunnersFinished():
for runner in runs:
if runner.endtime != 0:
return False
return True
while not allRunnersFinished():
pass
Once the testing itself has finished we must store the results
of the test. They will be written to the two files defined as
RUNS_FILE and LOADS_FILE.
fh = open(RUNS_FILE, "w")
for e in runs:
fh.write("%s\t%s\t%s\n" % (e.id, e.starttime, e.endtime))
fh.close()
fh = open(LOADS_FILE, "w")
for e in loads:
fh.write("%(ts)s\t%(1m)s\t%(5m)s\t%(15m)s\n" % e)
fh.close()
The RUNS_FILE contains time stamps marking the
start and end of each individual squishrunner run. The
LOADS_FILE contains server load averages measured
every 5 seconds. The measurement of other information (traffic, number
of processes, disk I/O) can easily be configured using suitable SNMP
commands. Graphical presentation of the data can be produced with
standard charting software.
At some point we hope to provide a ready-made front-end that will make it possible to configure, schedule, and execute test runs, as well provide visual representations of the results.
[16]
Squish can also import
.tsv (tab-separated values format),
.csv (comma-separated
values format), and .xls (Microsoft® Excel™
spreadsheet format—but
not .xlsx format) files.
Both .csv and .tsv files are
assumed to use the Unicode UTF-8 encoding—the same encoding used
for all test scripts.