Table of Contents
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 5.3.7)) 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 5.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. For testing web applications with Safari on macOS, Microsoft Internet Explorer on Windows, or Firefox 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 7.4.8). |
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 6.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 5.21.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); }
radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " + "type='radio' value='Radio 1'}" if Squish::Object.exists(radioName) radioButton = findObject(radioName) clickButton(radioButton) end
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);
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);
div = findObject("{tagName='DIV' id='mydiv'}") 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 6.10.29).
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 6.10.41) on which
you can query the result of the XPath evaluation.
How to Access Table Cell Contents (Section 5.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 4.6.1.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 6.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);
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);
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 6.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);
div = findObject("DOCUMENT.HTML1.BODY1......DIV") 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);
element = findObject( ":{tagName='INPUT' id='sel' form='myform' type='select-one'}") 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
customFunction
function with string argument
“an argument”, on a DIV
element, we could
write code like this:
div = findObject("DOCUMENT.HTML1.BODY1......DIV") div.invoke("customFunction", "an argument")
var div = findObject("DOCUMENT.HTML1.BODY1......DIV"); div.invoke("customFunction", "an argument");
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV"); $div->invoke("customFunction", "an argument");
div = findObject("DOCUMENT.HTML1.BODY1......DIV") div.invoke("customFunction", "an argument")
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"] invoke $div "customFunction" "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: ...
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 : ''");
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.
In addition to test scripts being able to run some JavaScript snippet and retrieve the string value of the result, Squish also can retrieve references to the actual JavaScript objects from the Web browser's interpreter. This is useful in cases where a JavaScript function does not return a simple value, like a string or number, but instead returns an object itself. In such a case, the reference allows retrieving properties from that object or calling methods on that object. For this purpose, Squish provides the retrieveJSObject function. Here is an example of its use:
jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;")
var jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;");
my $jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;");
jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;")
set jsobject [invoke retrieveJSObject {var globalObject = \{ 'id': 'obj1', 'name': function() \{ return 'name1'; \} \};globalObject;\} }]
The retrieveJSObject function returns the result of
the last statement executed—in this case the last statement is
globalObject;
so a reference to the just created globalObject is returned.
Now its possible to fetch the id
property of that object or call the name
function. Here is an example of logging both in the test results
test.log("id: " + jsobject.property("id")) test.log("name: " + jsobject.call("name"))
test.log("id: " + jsobject.property("id")); test.log("name: " + jsobject.call("name"));
test::log("id: " . $jsobject->property("id")); test::log("name: " . $jsobject->call("name"));
test.log("id: " + jsobject.property("id")) test.log("name: " + jsobject.call("name"))
test log "id: " [invoke $jsobject property "id"] test log "name: " [invoke $jsobject call "name"]
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 6.10) section in the Tools Reference Manual (Chapter 7). 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");
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"); }
loaded = waitFor("isPageLoaded", 5000) if loaded clickButton(waitForObject( ":{tagName='INPUT' type='button' value='Login'}")) else Test.fatal("Page loading failed") end
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.
Table of Contents
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 4.6.1.4) in Tutorial: Starting to Test Web Applications (Section 4.6.1), 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 5.21.3)) and adding the object to the Object Map (Section 7.11) (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 7.11),
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);
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);
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");
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");
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");
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 6.10.32) 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; }
def getCellText(tableObject, row, column) tableObject.evaluateXPath( "TBODY/TR[#{row + 1}]/TD[#{column + 1}]").stringValue end
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 6.10.41);
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);
table = waitForObject(htmlTableName) 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 5.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);
table = waitForObject("{tagName='TABLE' id='result_table]'}") contents = table.innerText Test.verify(contents.find("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");
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')");
def isDIVReady(name) if !Squish::Object.exists(":{tagName='DIV' id='#{name}'}") return false end waitForObject(":{tagName='DIV' id='syncDIV'}").style().value( "display") == "hidden" end # 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.)
Table of Contents
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 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 7.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 True: 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 endtime
s 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 = None try: fh = open(RUNS_FILE, "wt") for e in runs: fh.write("%s\t%s\t%s\n" % (e.id, e.starttime, e.endtime)) finally: if fh is not None: fh.close() fh = None try: fh = open(LOADS_FILE, "wt") for e in loads: fh.write("%(ts)s\t%(1m)s\t%(5m)s\t%(15m)s\n" % e) finally: if fh is not None: 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.