Web Object Extension API
This section shows how to use the Web Object Extension API to support the testing of custom AJAX/DHTML/JavaScript widgets.
Squish provides access to Web DOM elements, giving tests the potential for complete low-level access and control. However, working at this level is the Web equivalent of using hard-coded screen coordinates when testing GUI applications—that is, it is a rather fragile approach.
To create robust test scripts, a test framework and the automated tests should interact with the application and the HTML on high-level widgets, instead of interacting with low-level DOM elements. This way tests work on an abstract GUI level without needing any knowledge of the application's internals (i.e., without needing to directly access the application's DOM).
The advantage of using high-level tests which work on widgets rather than directly on the DOM, is that the tests will not break just because the DOM representation or widget implementation changes. And such changes are quite common as applications, and the frameworks they use, evolve.
So in addition to providing low-level access, Squish also provides a way of creating high-level tests that are likely to be a lot more reliable. This is possible because Squish comes with built-in support for popular AJAX and DHTML frameworks and recognizes their widgets. But given the amount of available AJAX, DHTML, and JavaScript frameworks and custom widgets that currently exist, and also the new ones that keep appearing, it is not possible for Squish to support all of them out-of-the box.
Fortunately, even for Web frameworks that are not currently supported, one can add support for their framework to Squish. This is done by using Squish's Web Object Extension API, which makes it possible to extend Squish's widget support to recognize custom AJAX, DHTML, and JavaScript widgets, to identify them properly, and to interact with them, and of course to make their APIs accessible to test scripts.
In this section we begin with the Squish Object API that is available to extension scripts, then we present an overview and examples that show you how to implement support for your custom widgets yourself. Alternatively, use the Qt Support Center if you want us to implement the extension for your custom widgets or framework for you.
API to access properties that make up an object name. | |
API for extending Squish to support custom Web Objects. |
Concepts and Setup
This extension is enabled by specifying the location of the JavaScript file that implements it. When Squish hooks into the Web browser, it will evaluate the JavaScript in the extension file inside the browser.
Inside the JavaScript extension file it is possible to use Squish's programming interface to hook into the object recognition, name generation, and other functionality provided by the framework. Also, by implementing certain JavaScript hook-functions, it is possible to expose the APIs of the framework's custom widgets to make them accessible to test scripts.
To specify a JavaScript extension file (and assuming for the sake of example, that the file is called C:\squishext\myextension.js
), add a line such as this to the squish.ini
file located in your Squish installation's etc
subdirectory:
Wrappers/Web/ExtensionScripts="C:\\squishext\\myextension.js"
If you do distributed testing, this only needs to be done on the machine where squishrunner or the Squish IDE are used.
The API which can be used to implement the extensions is documented in the Squish Object—this object provides methods that support Squish's JavaScript extension. The following sections explain the object's API, and show some examples of its use.
How to set up Support for Simple Widgets
To avoid recording superfluous mouse clicks, Squish only records clicks on DOM elements which are known to respond to clicks. To decide whether an element responds to clicks, Squish checks to see if the element has a mouse event handler set (such as an onClick
function), or if the element is a known clickable widget—such as a form input element, a link, and so on.
If your JavaScript library comes with custom clickable widgets such as custom buttons, you can tell Squish about them using the Squish.registerWidget(argumentObject) JavaScript function. This function expects a named argument list (i.e., an object) which specifies the DOM class of the element and the type of events that should be recorded.
For example, let's assume that you have a special button implementation which is represented in the DOM as:
<span class='ajaxbutton'>Click Me</span>
To make Squish record mouse click events for this type of widget, add the following line to your extension JavaScript file:
Squish.registerWidget({Class: "ajaxbutton", Event: "click"});
Now, when you record a script and click this button, Squish will record mouseClick(objectOrName) statements.
To identify widgets when replaying a test, Squish defaults to using its own built-in name generation algorithm. In this specific example the innerText
will be used since no id
or name
is set, and this is sufficient in this case. So, the generated real name for this object will be, {tagName='SPAN' innerText='Click Me'}
.
It is possible to supplement Squish's name generator with your own name generation algorithm—this is particularly useful if you want to make use of custom properties from a web framework's custom elements when names are generated. This is covered in the next section.
How to Extend the Name Generator and Identification
To produce a name for a widget, Squish generates a list of property pairs identifying the object. Squish uses a set of pre-defined properties such as tagName
, id
, name
, title
, innerText
, and a few others.
If the properties do not uniquely identify the object, an occurrence
property is added to the name which specifies which of the objects matching the other properties is supposed to be selected.
With the exception of tagName
—which is the one mandatory property that all names must have— the properties are optional and can be chosen freely.
In some cases it might be desirable to use custom properties in the names of certain types of objects. To do this, it is possible to specify your own hook function which will then be called to generate names for your own widgets. And if necessary, a hook function that performs the property matching that is done when objects are searched for can also be installed.
How to Implement a Custom Name Generator
To show how to implement a custom name generator, we will start with an example menu element:
<SPAN class='menu' id='fileMenu'> <SPAN class='menuItem' menuID='fileOpen'>Open</SPAN> <SPAN class='menuItem' menuID='fileQuit'>Quit</SPAN> </SPAN class='menu'>
First, we must register the menu items as clickable widgets using the Squish.registerWidget(argumentObject) function:
Squish.registerWidget({Class: "menuItem", Event: "click"});
(We already saw an example of using this function earlier: How to set up Support for Simple Widgets.)
In this example we are going to use the menuID
attribute, along with the DOM class, and the parent element's ID, to give our menu elements (i.e., each menuItem
<span>
tag), their unique identifying names.
var myUiExtension = new Object; myUiExtension.nameOf = function(obj) { if (obj.tagName == "SPAN" && Squish.hasClassName(obj, "menuItem")) { var name = '{' + Squish.propertiesToName(obj, ["tagName", "menuID"]) + " parentID='" + obj.parentNode.id + "'" + " className='menuItem'" + "}"; return escape(Squish.uniquifyName(name, obj)); } return undefined; }
We have added this function as a property of a custom myUiExtension
object. In the implementation we check if the object is an object of the type we want to handle, that is, menuItem
<span>
tag. If the type is right we use custom code to create a unique name for it; otherwise we return undefined
—this isn't a valid name value, so Squish will try each of the other registered name generation hook functions (if any), and if all of them return undefined
(or if there aren't any), Squish will fall back to using its own default name generator.
To determine if the element is one of those we want to handle, we check to see if its tag name is SPAN
and if its DOM class is menuItem
. For this second check we use the Boolean Squish.hasClassName(objectOrName, className) helper function, which returns a true value if any of the DOM object's DOM classes has the same name as the given class name.
If the element is of the right type we create a name for it using our own custom name generation algorithm. A real (multi-property) name consists of one or more property name–value pairs, all contained in braces. Here we start by using the String Squish.propertiesToName(objectOrName, listOfPropertyNames) function to create a name string containing the given property names and values; then we add two additional property pairs, one for the parent ID and another for the class name. Finally, we use the String Squish.uniquifyName(name, objectOrName) function to ensure that we produce a unique name. This function accepts a real (multi-property) name as a string and the object the name is supposed to identify, and will return the name unchanged if possible, or will return it with the addition of an occurrence
property if it is necessary to distinguish the name further to ensure its uniqueness.
So, given the example shown above, if the object was the fileOpen
menu item, the String Squish.propertiesToName(objectOrName, listOfPropertyNames) function would give us the string "tagName='SPAN' menuID='fileOpen'"
, and with the addition of the parent ID and class name, the final name we would end up with is "{tagName='SPAN' menuID='fileOpen' parentID='fileMenu' className='menuItem'}"
.
We could not use the String Squish.propertiesToName(objectOrName, listOfPropertyNames) function for either the parent ID or for the class name properties. In the case of the parent ID this is because the parentID
is not a regular property, but rather a pseudo-property, so we must handle it ourselves. Once part of a real name though, Squish will be able to work with it like any other property. In the case of the class name the only reason we cannot use the String Squish.propertiesToName(objectOrName, listOfPropertyNames) function is because the DOM property name is class
while Squish uses the JavaScript className
name. Also, a DOM class may contain multiple values while we only want to specify one—menuItem
.
At the end we make sure that any special characters are escaped properly—for this we use the standard JavaScript escape
function. And then we return the result.
Once our name generator function is ready, we must inform Squish of its existence so that it is used when names are needed. This is done by registering it as a hook function:
Squish.addNameOfHook(myUiExtension.nameOf);
Here we have used the Squish.addNameOfHook(function) function to register the myUiExtension.nameOf
function as a name generator function. From now on Squish will use this function whenever it needs to generate a real name, and will fall back to using any other generator function (and its own built-in default name generator function as a last resort), if the function returns undefined
.
How to Implement a Custom Name Matcher
When searching for an object by name, Squish iterates over all objects in the DOM tree and queries the specified object properties to see if their values match those of the name.
Using a custom name matcher hook makes it possible to consider a name's pseudo-properties (such as the parentID
we saw earlier), when searching for a matching object. Since Squish has no knowledge about any pseudo-properties we have created, we must implement a custom function to perform the lookup and name matching and that accounts for our pseudo-properties.
myUiExtension.matchObject = function(obj, property, valueObject) { if (property == "parentID") return Squish.matchProperty(valueObject, obj.parentNode.id); return undefined; }
Again, we have implemented this function as a property of our custom myUiExtension
object. Squish will pass the object, the property name and the expected value as specified in the name to this function.
If the given property is our pseudo-property, we use the built-in Boolean Squish.matchProperty(valueObject, property) function to determine if there is a match. Otherwise we return undefined
; in such cases Squish will try all the other match objects that are registered (if any), and if none of these gives a definitive true or false (or if there aren't any), it falls back on its own internal name matching function. Note that the valueObject
is not simply a string but rather an object which contains a string value and a flag denoting whether the matching should be based on a literal string comparison, wildcard matching, or regular expression matching. (See also Improving Object Identification.)
In this example we have chosen to handle the parentID
pseudo-property ourselves; and by returning undefined
for any other property we pass on the matching work to Squish for anything other than the pseudo-property we are interested in.
The custom name matching setup is completed by installing the hook:
Squish.addMatchObjectHook(myUiExtension.matchObject);
The Squish.addMatchObjectHook(function) function can be used to register as many name matching functions as we want.
How to set up Support for Complex Widgets
Using the extension mechanism it is also possible to add dedicated support for complex widgets—that is, widgets which contain items—such as tree widgets, tables, menus, calendar controls, and other item-based widgets. (For example, trees contain nodes, tables contain cells, menus contain items, and calendars contain date cells.) We will generally refer to such complex widgets as "item views", and will often refer to their nodes, cells, and items, simply as "items".
To make high-level interactions with such widgets possible, user actions such as mouse clicks on cells or items, and expanding and collapsing tree nodes, must be recognized and replayed—and in a way that is independent of the underlying HTML representation. In addition, certain states and properties, such as the current selection, ought to be queryable from test scripts, to allow for robust and automatic verifications.
Using the item and item view abstractions and the necessary JavaScript hooks and APIs, Squish supports the addition of any custom item view DHTML/AJAX/JS widget so that test scripts can access the widget's and its items' states, properties and functions.
This section explains how to implement such dedicated item view support so that Squish will correctly be able to record test scripts using custom item views and accurately replay such tests. This is done by using Squish's Web Object Extension API.
We will illustrate the explanation by using an example AJAX widget—the tree control from Google's Web Toolkit (GWT). We will implement all the necessary support to properly record and replay clicks on items and item handles, to identify the tree and items independent of the DOM, and to allow querying the tree's selection.
Once the necessary hooks are implemented, calls to the clickItem(objectOrName, itemText) function and to the clickTreeHandle(objectOrName, itemText) function will be recorded when interacting with the supported item view widget. In addition, using the HTML_CustomItemView Class and HTML_CustomItem Class abstraction API, the test scripts will be able to access the widget and work with its items, states, and properties.
DOM structure of the widget
Before we can start implementing the necessary support, we need to look at the widget's DOM structure since our custom support will use the Web application's DOM internally to provide the abstraction and encapsulation that test scripts will rely on.
Below is a screenshot of the GWT Tree widget as it appears in a Web browser:
The GWT Tree widget
The DOM hierarchy that GWT uses to represent this tree widget has the following structure (with irrelevant details, and most of the data, omitted):
<DIV class="gwt-Tree"> <TABLE> <TR> <TD> <IMG src="tree_open.gif"/> </TD> <TD> <SPAN class="gwt-TreeItem">Beethoven</SPAN> </TD> </TR> </TABLE> <SPAN> <DIV> <TABLE> <TR> <TD> <IMG src="tree_closed.gif"/> </TD> <TD> <SPAN class="gwt-TreeItem gwt-TreeItem-selected">Concertos</SPAN> </TD> </TR> </TBODY> </TABLE> </DIV> <DIV> <TABLE> <TR> <TD> <IMG src="tree_closed.gif"/> </TD> <TD> <SPAN class="gwt-TreeItem">Quartets</SPAN> </TD> </TR> </TBODY> </TABLE> </DIV> </SPAN> ... </DIV>
So we can see that a tree widget is represented using a DIV
element with the class name of gwt-Tree
. The tree itself is contained inside this DIV
element, in a TABLE
.
Each tree item is held in a SPAN
element that has a class of gwt-TreeItem
. The item's text is the element's inner text, and selected items have an additional class of gwt-TreeItem-selected
.
Item handles (typically shown as + or - symbols to indicate that the item can be expanded or is already expanded), are represented by an IMG
element in the TD
element above the item—specifically, the item's parent's previous sibling's first child. The IMG
's src
property can be used to determine if the item is expanded (src="tree_open.gif"
) or collapsed src="tree_closed.gif"
).
The relationship between the items (i.e., the actual tree structure), is modeled using nested SPAN
elements which contain a set of siblings relative to their parent.
This information is sufficient for us to be able to detect a GWT Tree element, the tree's item elements, and the items' handles, in the web page's DOM structure. Armed with this knowledge we can create a high-level API that our test scripts can use to interact with a GWT Tree and its items, using Squish's uniform "item view" API.
Note that all the code used in the following subsections is taken from the file lib/extensions/web/jshook_gwt.js
.
Hooks for recording high-level GUI operations
Functions to implement in the toolkit's extension object:
The first step is to implement the JavaScript hooks for recording high-level GUI operations on the GWT Tree. This means, if the user clicks an item, a (treeName, itemName)
function call should be recorded. Similarly, a click on a tree item's handle should be recorded as (treeName, itemName)
.
To achieve this, we must implement three different hooks:
- Event object hook: A function that returns the DOM element that is the source of an event.
- Type of object hook: A function that returns a DOM object's high-level type name (as a string).
- Name of object hook: A function that returns a DOM object's Squish object name (as a string).
Once these functions are implemented, they must be registered with Squish so that they are called by Squish's event recorder. When Squish calls them, it will pass the event's DOM source object as an argument, and based on this we can decide whether we will respond or not. (We can safely ignore events we don't want to handle and leave them for Squish to deal with.)
For the GWT Tree when we implement these functions we must handle three different high-level objects:
- Tree objects
- Tree items
- Tree item handles
So for all three functions we need a way to determine which type of object we are dealing with. To do this we will start by defining a gwtExtension
object to contain all the GWT Tree-related code we write. Then we'll define some constants, and then we will create a getType
function.
var gwtExtension = new Object; gwtExtension.Tree = 0; gwtExtension.TreeItem = 1; gwtExtension.TreeItemHandle = 2;
We will use these constants to distinguish between the objects we are interested in handling. Now we need a function that can match DOM class names to these constantts:
gwtExtension.getType = function(obj) { if (Squish.hasClassName(obj, 'gwt-TreeItem')) return gwtExtension.TreeItem; else if (obj.tagName == 'IMG' && (obj.src.indexOf('tree_open') != -1 || obj.src.indexOf('tree_closed') != -1) && Squish.hasClassName(gwttreeExtension.itemOfHandle(obj), 'gwt-TreeItem')) return gwtExtension.TreeItemHandle; else if (Squish.hasClassName(obj, 'gwt-Tree')) return gwtExtension.Tree; return undefined; }
The custom getType
function uses the Boolean Squish.hasClassName(objectOrName, className) function to see if the element is a tree item, and if it is, the appropriate constant is returned. Similar code is used to handle trees themselves, but for tree handles we must check that the tag is IMG
and that it has one of the appropriate images and that the element associated with the image is an item with the correct class (gwt-TreeItem
). We will see the creation of the gwttreeExtension
object and of the gwttreeExtension.itemOfHandle
function shortly.
If the element is not one of those we are interested in, we return undefined
which indicates to Squish that it should try whatever type functions it hasn't yet tried, or to fall back on its own built-in functionality.
Event Objects
Now that we have a suitable getType
function, it is quite easy to implement the three hook functions that are needed:
gwtExtension.eventObject = function(obj) { if (!obj) return undefined; switch (gwtExtension.getType(obj)) { case gwtExtension.TreeItem: // fallthrough case gwtExtension.TreeItemHandle: return obj; } return undefined; }
If the high-level object type is a tree item or a tree item's handle, we return this object. This tells Squish that the object is one we want to record events on, and so should not be ignored by the event recorder. As usual, if we don't recognize the object, we return undefined
which tells Squish to handle it for us.
Object Types
If Squish encounters an object for which events should be recorded, it will need know the object's type. For this we must implement a suitable hook function:
gwtExtension.typeOf = function(obj) { switch (gwtExtension.getType(obj)) { case gwtExtension.TreeItem: return 'custom_item_gwttree'; case gwtExtension.TreeItemHandle: return 'custom_itemhandle_gwttree'; case gwtExtension.Tree: return 'custom_itemview_gwttree'; } return undefined; }
The type names returned from this function follow a convention that must be followed! Squish provides a common item view abstraction, and for it to work correctly, we must return type names of the correct form.
Object Names
The three main components used by the item view API for custom trees, and that correspond to components that the DOM object represents, are items, handles, and views—a view is a list or table or tree widget. The naming convention we must follow for custom type names is very simple: Item type names must begin with custom_item_
; Item handle type names must begin with custom_itemhandle_
; and view type names must begin with custom_itemview_
. The rest of the name must identify our custom view (so in this example, each type name ends with _gwttree
).
The third hook function that we must implement returns the name of a specific high-level view object:
gwtExtension.nameOf = function(obj) { switch (gwtExtension.getType(obj)) { case gwtExtension.TreeItem: return escape(gwtExtension.nameOfTreeItem(obj)["object"]); case gwtExtension.TreeItemHandle: return escape(gwtExtension.nameOfTreeItem( gwttreeExtension.itemOfHandle(obj))["object"]); case gwtExtension.Tree: return Squish.uniquifyName(gwtExtension.nameOfTree(obj), obj); } return undefined; }
As usual, we tell Squish to handle any objects that we are not interested in by returning undefined
.
This function makes use of two other functions, gwtExtension.nameOfTree
and gwtExtension.nameOfTreeItem
to create suitable real (multi-property) names for the custom objects. Here are their implementations:
gwtExtension.nameOfTree = function(obj) { return '{' + Squish.propertiesToName(obj, ["tagName", "id", "name"]) + "className='gwt-Tree'}"; }
This function creates a unique name for each GWT Tree object that Squish encounters. (See How to Implement a Custom Name Generator for more information about creating names.)
Here is the function which generates a name for a tree item:
gwtExtension.nameOfTreeItem = function (obj) { var tree = obj; while (tree && (!Squish.hasClassName(tree, 'gwt-Tree'))) tree = tree.parentNode; var treeName = Squish.nameOf(tree); var item = obj; return {"object": treeName, "itemText": item.innerText}; }
An item's name consists of the name of the item view containing the item (the tree widget) and the actual name of the item (usually the item's text—i.e., its innerText—is used). The both parts are returned separately to allow reusing this for the nameOf function and the itemTextForEventObject function further down
First we find the tree widget that the item belongs to. This is done by going to the item's parent, and if that isn't the tree, the parent's parent, and so on until we get an object of class gwt-Tree
. Once we have located the tree object, we can call the String Squish.nameOf(objectOrName) function to get its real (multi-property) name. The item's text (which we get from the item's innerText
) is returned separately.
The fourth function to implement is a callback that will provide the actual items text whenever a click is found to happen on an item. This allows Squish to generate nicely-looking clickItem
invocations for clicks on items.
gwtExtension.itemTextForEventObject = function( eventObject ) { switch (gwtExtension.getType(eventObject)) { case gwtExtension.TreeItem: return gwtExtension.nameOfTreeItem(eventObject)["itemText"]; case gwtExtension.TreeItemHandle: return gwtExtension.nameOfTreeItem(gwttreeExtension.itemOfHandle(eventObject))["itemText"]; } return undefined; }
This function reuses the nameOfTreeItem function but selects the item text calculated by that to return if the given object is referring to a tree item or tree item handle.
Register Hook Functions
Now that we have implemented all the necessary hook functions we must register them to make them take effect:
Squish.addNameOfHook(gwtExtension.nameOf); Squish.addTypeOfHook(gwtExtension.typeOf); Squish.addEventObjectHook(gwtExtension.eventObject); Squish.addItemTextForEventObjectHook(gwtExtension.itemTextForEventObject);
With these hooks in place, if we now record events on a GWT Tree, the expected high-level operations will be correctly recorded.
Hooks for replaying high-level GUI operations
Functions to implement in the view's extension object:
Once the recording hooks are implemented and recording works, the next step is to implement the hooks to enable Squish to recognize and correctly replay the recorded GUI operations.
Find Item
To support replaying clicks on items (clickItem(objectOrName, itemText)), only one additional function needs to be implemented. This function has to search for an item by name in the tree, and should return a reference to the DOM object representing the item. Squish will use this function to get the item to send click events to.
The name of this function has to follow a certain naming convention. In particular, it must be called findItem
and it must exist as a function property of an object called typenameExtension
where typename
is exactly the same name as we appended to the type names of the objects in our typeOf
hook function.
For the GWT Tree example we have typenames custom_item_gwttree
and so on, so the name we must use is gwttree
. This means that we have to create an object called gwttreeExtension
and give it a findItem
function, as the following code illustrates:
var gwttreeExtension = new Object; gwttreeExtension.findItem = function(tree, name) { var node = tree.firstChild; while (node) { if (node.firstChild) { var node2 = gwttreeExtension.findItem(node, name); if (node2) { return node2; } } if (node.className && Squish.hasClassName(node, 'gwt-TreeItem') && Squish.cleanString(node.innerText) == name) { return node; } node = node.nextSibling; } return undefined; }
The tree
is a reference to the tree DOM element and the itemText
is the item's text (actually its innerText
).
The function iterates through all child elements of the tree until it finds an element of class gwt-TreeItem
whose innerText
matches the requested item text.
Item Handle
For list and table widgets it is sufficient to provide a findItem
function, but for tree widgets an additional function is required to deal with item handles.
For this purpose an itemHandle
function must be implemented in the same object that returns the handle belonging to the given item. Here is an implementation of the function that works for GWT Tree items:
gwttreeExtension.itemHandle = function(node) { return node.parentNode.previousSibling.firstChild; }
This function returns the GWT Tree element that corresponds to an item handle (an IMG
element in the TD
element above the item—i.e., the item's parent's previous sibling's first child.)
The implementation of these two functions is all that's needed to enable the replaying of high-level operations on items and item handles.
Exposing the Item View API
Functions to implement in the view's extension object:
- itemText
- isItemSelected
- isItemOpen
setItemSelected
(see isItemSelected)setItemOpen
(see isItemOpen)childItem
(see Item Traversal)parentItem
(see Item Traversal)nextSibling
(see Item Traversal)itemView
(see Item Traversal)numColumns
(see Columns)columnCaption
(see Columns)
For Squish to be able to detect an item view and its items, and be able to record and replay high-level GUI operations involving them, what we have done so far is sufficient.
But Squish's item view abstraction also provides the tester with APIs to iterate through the items and to query and set the selection and other properties. To support such functionality, various additional functions need to be implemented—all of which we will cover here, again providing examples based on the GWT Tree example.
Item Text
Squish's test script item-view API allows us to retrieve a tree item (e.g., using tree.findItem(...)
), and to query the item's text
property.
By default, Squish returns the item's innerText
, but if this is inappropriate for the items you are working with you can implement your own itemText(item)
function in the view's extension object, and which will then be called instead of Squish's default function for your items:
gwttreeExtension.itemText = function(item) { return item.innerText; }
If the typenameExtension
has an itemText
function property, Squish will call this function and pass a reference to the DOM object representing the relevant item. This principle applies to the other functions too: if we provide the function it is used for our extension types; otherwise Squish falls back to its own built-in functionality.
In this particular case (i.e., for GWT Tree items) there is no need to implement this function because the built-in version returns the innerText
anyway.
Item Selection
Squish's test script item-view API allows us to retrieve a tree item (e.g., using tree.findItem(...)
), and to query the item's selected
property.
To support the querying of an item's selection state, we must provide an isItemSelected
function in the view's extension object. Here is an example that works for GWT Trees:
gwttreeExtension.isItemSelected = function(item) { return Squish.hasClassName(item, "gwt-TreeItem-selected"); }
If the typenameExtension
has an isItemSelected
function property, Squish will call this function and pass a reference to the DOM object representing the relevant item.
To check if the item is selected we simply return true
or false
depending on whether one of the item's class names is gwt-TreeItem-selected
.
Squish's item-view API also supports the changing of an item's selection state. This can be achieved by implementing a setItemSelected(item, selected)
function in the same extension object. The item
is a reference to the item's DOM object and selected
is a Boolean value, with true
meaning that the item should be selected and false
meaning that it should be unselected.
Item Handle's State
Squish's test script item-view API allows us to access a tree item's handle and query it to see if the item is expanded (its children are visible) or collapsed (its children are hidden). The handle's state is held in the opened
property, and to support this property we must implement an isItemOpened
function in the tree's extension object. (Note that this functionality doesn't make sense for list or table views.)
Here is an example implementation for the GWT Tree:
gwttreeExtension.isItemOpen = function(item) { try { var obj = item; while (obj && obj.tagName != 'TABLE') { obj = obj.parentNode; } if (!obj || !obj.nextSibling) { return undefined; } obj = obj.nextSibling; return Squish.isElementVisible(obj); } catch (_e) { return false; } }
If the typenameExtension
has an isItemOpen
function property, Squish will call this function and pass a reference to the DOM object representing the relevant item.
Here, we simply retrieve the item's handle and return a Boolean that reflects whether it has the opened image.
The item-view API also supports the changing of an item's expanded/collapsed state. This can be achieved by implementing a setItemOpen(item, expand)
function in the same extension object. The item
is a reference to the item's DOM object and expand
is a Boolean value, with true
meaning that the item should be expanded (opened) and false
meaning that it should be collapsed (closed).
Item Traversal
The item view API also includes functions for traversing items in a tree view. The traveral functions are: childItem(treeOrItem)
, nextSibling(item)
, and parentItem(item)
. There is also an itemView(item)
function; this returns the view that the given item is in.
Using these APIs it is possible to iterate over the items in a tree, and using the functions shown earlier, it is then possible to retrieve items' texts, select or unselect items, or expand or collapse items.
If we want our custom view to provide traversal then we must implement our own versions of these four functions as properties of our custom view's extension object. And, just the same as for the functions we have already discussed, if we create our own versions of these functions, Squish will call them and pass a reference to the DOM object representing the relevant item. Note that exceptionally, in the case of the childItem
function, Squish might call it with either a tree or an item.
Here are implementations of these functions for the GWT tree:
gwttreeExtension.childItem = function(parent) { if (Squish.hasClassName(parent, "gwt-Tree")) { return gwttreeExtension._findFirstTreeItem(parent); } else { while (parent && parent.tagName != 'TABLE') parent = parent.parentNode; if (!parent || !parent.nextSibling) return undefined; parent = parent.nextSibling; return gwttreeExtension._findFirstTreeItem(parent); } } gwttreeExtension._findFirstTreeItem = function(baseNode) { obj = Squish.getElementByClassName(baseNode, 'gwt-TreeItem', 'SPAN'); if(!obj) { obj = Squish.getElementByClassName(baseNode, 'gwt-TreeItem', 'DIV'); } return obj; } gwttreeExtension.nextSibling = function(node) { node = node.parentNode; while (node && node.tagName != 'DIV') node = node.parentNode; if (!node || !node.nextSibling) return undefined; node = node.nextSibling; return gwttreeExtension._findFirstTreeItem(node); } gwttreeExtension.parentItem = function(node) { node = node.parentNode; while (node && node.tagName != 'DIV') node = node.parentNode; if (!node || !node.parentNode || !node.parentNode.previousSibling) return undefined; node = node.parentNode.previousSibling; return gwttreeExtension._findFirstTreeItem(node); } gwttreeExtension.itemView = function(node) { var obj = node; while (obj && (!Squish.hasClassName(obj, 'gwt-Tree'))) obj = obj.parentNode; return obj; }
The implementations are quite self-explanatory, especially if you look at the DOM structure that the GWT Tree uses. (Note though, that the GWT implementation has changed so we must account for both the old version and up to date versions—this is why we have the gwttreeExtension._findFirstTreeItem
function.)
Columns
If the item view supports multiple columns, the functions columnCaption(view, column)
and numColumns(view)
can be implemented in the view's extension object to allow the test script to query the column information using the API calls with the same names through the item view API.
Testing and Conclusion
A couple of test scripts which use the GWT Tree APIs developed in this section can be found in the examples/web/suite_gwt
suite.
Writing HTML Class-Specific Extensions
To make Squish able to record and playback specific objects of custom HTML widget types we can implement extensions (like the one for GWT Trees described earlier) for the particular types we are interested in. Each type has its own specific requirements. Here are some quick links to the supported types:
Button Support
To make Squish recognize a custom button widget a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "custom_button_name"
(e.g., "custom_button_CustomButton"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomButtonExtension
). This object must provide the following methods:
click(buttonElement)
— emulate a click on the button; this should use the Squish.mouseClick(objectOrName) function or the Squish.clickButton(buttonElement) function as appropriate.getText(buttonElement)
— returns the button's textgetTooltip(buttonElement)
— returns the button's tool tip text (which may be empty)getValue(buttonElement)
— return the button element's data valueisDisabled(buttonElement)
— return true if the button element is disabled; otherwise (i.e., if the button element is enabled), return falseisTextShown(buttonElement)
— returns true if the button is showing text or false if the button is showing an imagesetDisabled(buttonElement, disable)
— enable the button element ifdisable
is false; otherwise disable itsetText(buttonElement, text)
— sets the button's text to the giventext
setTextShown(buttonElement, showText)
— sets the button to show text (taken from itstext
property) ifshowText
is true; otherwise sets the button to show an imagesetTooltip(buttonElement, tip)
— sets the button's tool tip to the giventip
textsetValue(buttonElement, value)
— change the button element's data value to the givenvalue
In addition to the above, extensions could also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should make sure that the value
parameter is added to the returned string and contains the value of the text field.
See also the Squish.clickButton(buttonElement) function and the HTML_CustomButtonBase Class and the HTML_CustomButton Class.
Combobox Support
To make Squish recognize a custom combobox a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "custom_combobox_name"
(e.g., "custom_combobox_CustomComboBox"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomComboBoxExtension
). This object must provide the following methods:
hasOption(comboboxElement, optionValue)
— returns true if the given combobox element has an entry with the givenoptionValue
getSelectedOption(comboboxElement)
— returns the combobox element's currently selected entrysetSelectedOption(comboboxElement, optionValue)
— selects the givenoptionValue
in the given combobox element
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should ensure that the option value parameter is added to the returned string and that it contains the value of the combobox's currently selected entry.
Furthermore, extensions should also implement an event type hook using the Squish.addEventTypeHook(function) function if the combobox doesn't generate "change" events. For such comboboxes, the extension can hook into mouse events or similar and return "change"
from the hook function. This is important for recording, since when Squish sees a change-event
with a value parameter it will record a call to the selectOption(objectOrName, text) function rather than generic mouse clicks.
See also the HTML_CustomComboBox Class.
Calendar Event Support
To make Squish recognize a custom calendar event a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "calendarevent_name"
(e.g., "calendarevent_CustomCalendarEvent"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomCalendarEventExtension
). This object must provide the following methods:
getDescription(calendarEventElement)
— return the calendar event's description text orundefined
if it doesn't have any.getEndDateTime(calendarEventElement)
— return a JavaScriptDate
object with the calendar event's end date/time.getStartDateTime(calendarEventElement)
— return a JavaScriptDate
object with the calendar event's start date/time.getTitle(calendarEventElement)
— return the calendar event's title text.setDescription(calendarEventElement, text)
— set the calendar event's description to the giventext
.setEndDateTime(calendarEventElement, jsDate)
— set the calendar event's end date/time to the givenjsDate
(which must be a JavaScriptDate
object).setStartDateTime(calendarEventElement, jsDate)
— set the calendar event's start date/time to the givenjsDate
(which must be a JavaScriptDate
object).setTitle(calendarEventElement, text)
— set the calendar event's title to the giventext
.
See also, HTML_CalendarEvent Class and HTML_CalendarView Class.
Calendar View Support
To make Squish recognize a custom calendar view widget a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "calendarview_name"
(e.g., "calendarview_CustomCalendarView"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomCalendarViewExtension
). This object must provide the following methods:
getVisibleEventCount(calendarViewElement)
— return the number of HTML_CalendarEvent Class's that are visible in the calendar view.getVisibleEventAt(calendarViewElement, index)
— return the calendar view'sindex
-th visible HTML_CalendarEvent Class.getDate(calendarViewElement)
— return the calendar view's currently selected date/time as a JavaScriptDate
object.setDate(calendarViewElement, jsDate)
— set the calendar view's current date/time (i.e., the date/time that is shown) to the givenjsDate
which must be a JavaScriptDate
object.
See also, HTML_CalendarEvent Class and HTML_CalendarView Class.
CheckBox Support
To make Squish recognize a custom checkbox a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "custom_checkbox_name"
(e.g., "custom_checkbox_CustomCheckBox"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomCheckBoxExtension
). This object must provide the following methods:
isChecked(checkboxElement)
— return whether the element is checked or not.setChecked(checkboxElement, checked)
— check the element ifchecked
istrue
; otherwise uncheck it.
See also the HTML_CustomCheckbox Class.
Color Picker Support
To make Squish recognize a custom color picker widget a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "colorfield_name"
(e.g., "colorfield_CustomColorPicker"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomColorPickerExtension
). This object must provide the following methods:
getFieldName(colorFieldElement, fieldName)
— returns the color field element's label or an empty string if it hasn't got onegetRgbColor(colorFieldElement)
— returns the color field element's color in HTML format (i.e.,"#RRGGBB"
)isEnabled(colorFieldElement)
— returns whether the color field element is enabledsetEnabled(colorFieldElement, enabled)
— enable the color field element ifenabled
is true; otherwise disable itsetFieldName(colorFieldElement, fieldName)
— set the color field's label to the givenfieldName
setRgbColor(colorFieldElement, htmlRGBvalue)
— set the color field's color to the givenhtmlRGBvalue
(which must be an HTML color string of the form"#RRGGBB"
)
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should return the information necessary to record a call to the chooseColor(objectOrName, htmlRGBvalue) function, so should at least provide the color itself (in HTML form, i.e., "#RRGGBB"
), and optionally a label. Specifying a label is useful if the object on which the event occurred isn't a color field but something else such as a color picker's OK button; this allows interactions with the color dialog to be ignored.
Furthermore, extensions should also implement an event type hook using the Squish.addEventTypeHook(function) function. This function should return a chooseColor event type (e.g., if the choosing of a color was done by typing or by clicking a color or by clicking a color picker's OK button), or a chooseColorIncomplete event type if interactions with the color picker should not be recorded.
See also the chooseColor(objectOrName, htmlRGBvalue) function and the HTML_ColorField Class.
Date Picker Support
To make Squish recognize a custom date picker widget a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "datechooser_name"
(e.g., "datechooser_CustomDatePicker"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomDatePickerExtension
). This object must provide the following methods:
getDate(dateChooserElement)
— return the date chooser element's current date as a JavaScriptDate
object.setDate(dateChooserElement, jsDate)
— set the date chooser element's current date to the given date. ThejsDate
is a JavaScriptDate
object.
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should return the information necessary to record a call to the chooseDate(objectOrName, date) function as returned by the String Squish.createChooseDateInformation(jsDate) function.
It may also be useful to implement an event type hook using the Squish.addEventTypeHook(function) function. This hook should return the special "ignoreEvent"
type when clicks on the date picker are being used for navigation (e.g., changing the year or month), rather than actually picking a date.
See also the chooseDate(objectOrName, date) function and the HTML_DateChooser Class.
Expandable Section Header Support
Expandable sections are sometimes called "accordions" or "toolboxes".
To make Squish recognize a custom expandable section header widget, a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "expandablesectionheader_name"
(e.g., "expandablesectionheader_CustomAccordion"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomAccordionExtension
). This object must provide the following methods for expandable section header widgets:
click(headerElement)
— click on the header element and toggle its state (i.e., expand an unexpanded header, or unexpand an expanded header)getTitle(headerElement)
— return the header element's title textisEnabled(headerElement)
— return whether the header element is enabledisExpanded(headerElement)
— return whether the header element is expandedsetEnabled(headerElement, enable)
— enable the header element ifenabled
is true; otherwise disable itsetExpanded(headerElement, expand)
— expand the header element ifexpand
is true; otherwise unexpand itsetTitle(headerElement, title)
— set the header element's title text to the giventitle
text
See also, the toggleExpandable(objectOrName) function and the HTML_ExpandableSectionHeader Class .
Menu Support
To make Squish recognize a menu or menu bar widget, a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "menu_name"
(e.g., "menu_CustomMenu"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomMenuExtension
). This object must provide the following methods for menu widgets:
findItemByIconUrl(menuElement, iconUrl)
— return the HTML menu item element for the menu item that has the giveniconUrl
.findItemByText(menuElement, text)
— return the HTML menu item element for the menu item that has the giventext
.getMenuItemAt(menuElement, index)
— return the HTML menu item element at the givenindex
position.getMenuItemCount(menuElement)
— return the number of HTML menu item elements in the givenmenuElement
.
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should return the information necessary to record a call to the clickItem(objectOrName, itemText) function as returned by the String Squish.createClickItemInformationForMenuItem(menuElement, menuText) function. (Without the hook function, no calls to the clickItem(objectOrName, itemText) function will be recorded.)
See also Menu Item Support, the String Squish.createClickItemInformationForMenuItem(menuElement, menuText) function, and the HTML_Menu Class.
Menu Button Support
To make Squish recognize a menu button widget, a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "menubutton_name"
(e.g., "menubutton_CustomMenuButton"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomMenuButtonExtension
). This object must provide the following method for menu button widgets:
getMenu(menuButtonElement)
— return the HTML menu element associated with the givenmenuButtonElement
, orundefined
if there isn't one.
See also the HTML_MenuButton Class.
Menu Item Support
To make Squish recognize a menu item, a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "menuitem_name"
(e.g., "menuitem_CustomMenuItem"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomMenuItemExtension
). This object must provide the following methods for menu items:
getMenuItemIconUrl(menuItemElement)
— return this menu item element's icon URL orundefined
if it doesn't have an icon.getMenuItemText(menuItemElement)
— return this menu item element's text.getParentMenu(menuItemElement)
— return this menu item element's parent menu.getSubMenu(menuItemElement)
— return this menu item element's submenu orundefined
if it doesn't have one.isMenuItemChecked(menuItemElement)
— return whether this menu item element is checked.isMenuItemEnabled(menuItemElement)
— return whether this menu item element is enabled.isMenuItemSeparator(menuItemElement)
— return whether this menu item element is just a separator.setMenuItemChecked(menuItemElement, check)
— checks this menu item ifcheck
is true; otherwise unchecks it.setMenuItemEnabled(menuItemElement, enable)
— enables this menu item ifenable
is true; otherwise disables it.setMenuItemIconUrl(menuItemElement, iconUrl)
— sets this menu item's icon URL to the giveniconUrl
.setMenuItemSeparator(menuItemElement, separator)
— sets this menu item to be a separator ifseparator
is true; otherwise makes it a normal menu item.setMenuItemText(menuItemElement, text)
— sets this menu item's text to the giventext
.
See also Menu Support, the String Squish.createClickItemInformationForMenuItem(menuElement, menuText) function, and the HTML_MenuItem Class.
Progress Bar Support
To make Squish recognize a progress bar widget, a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "progressbar_name"
(e.g., "progressbar_CustomProgressBar"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomProgressBarExtension
). This object must provide the following methods for progress bar widgets:
getMaximum(progressBarElement)
— return an integer that corresponds to the progress bar's maximum valuegetMinimum(progressBarElement)
— return an integer that corresponds to the progress bar's minimum valuegetValue(progressBarElement)
— return an integer that corresponds the amount of progress shown by the progress barsetValue(progressBarElement, value)
— set the progress bar's value to the givenvalue
which should be between the progress bar's minimum and maximum values inclusive
See also the HTML_ProgressBar Class .
RadioButton Support
To make Squish recognize a custom radiobutton a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "custom_radiobutton_name"
(e.g., "custom_radiobutton_CustomRadioButton"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomRadioButtonExtension
). This object must provide the following methods:
isSelected(radiobuttonElement)
— return whether the element is selected (checked) or not.setSelected(radiobuttonElement, selected)
— select (check) the element ifselected
istrue
; otherwise unselect (uncheck) it.
See also the HTML_CustomRadioButton Class.
Select List Support
To make Squish recognize a custom select list a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "custom_selectlist_name"
(e.g., "custom_selectlist_CustomSelectList"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomSelectListExtension
). This object must provide the following methods:
hasOption(selectlistElement, optionValue)
— returns true if the given select list element has an entry with the givenoptionValue
getSelectedOptions(selectlistElement)
— returns the select list element's currently selected entries using the format described in the the HTML_CustomSelectList.selectedOptions property.setSelectedOption(selectlistElement, multioptionString)
— replaces the current option string with the givenmultioptionString
which must be in the format described in the HTML_CustomSelectList.selectedOptions property.
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should make sure that the "value" parameter is added to the returned string and contains the value of the currently selected entry in the select list or a string with the values of all the selected entries. For the latter the ArrayOfStrings Squish.getSelectedOptionTexts(selectElement) function can be used.
Furthermore, extensions should also implement an event type hook using the Squish.addEventTypeHook(function) function if the select list doesn't generate "change" events. For such select lists, the extension can hook into mouse events or similar and return "change"
from the hook function. This is important for recording, since when Squish sees a change-event
with a value parameter it will record a call to the selectOption(objectOrName, text) function rather than generic mouse clicks.
See also the HTML_CustomSelectList Class.
Tab Widget Support
To make Squish recognize a custom tab widget a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "tabwidget_name"
(e.g., "tabwidget_CustomWidgets"
) for the tab widget itself and of the form "tabitem_name"
for individual tabs.
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomWidgetsExtension
). This object must provide the following methods for tab widgets:
clickTab(tabWidgetElement, tabTitle)
— click on the given tab widget's tab with the given title text if there is one.getCurrentTab(tabWidgetElement)
— return the currently selected (i.e., top-most) tab element in the given tab widgetfindTab(tabWidgetElement, tabTitle)
— return the given tab widget's tab with the given tab title if there is one
The tab elements returned by the getCurrentTab
and findTab
methods must provide the following methods:
getIcon(tabElement)
— return the URL of the tab element's icon or an empty string if it hasn't got onegetTitle(tabElement)
— return the tab element's title textisEnabled(tabElement)
— return whether the tab element is enabledsetEnabled(tabElement, enabled)
— enable the tab element ifenabled
is true; otherwise disable itsetIcon(tabElement, iconUrl)
— set the tab element's icon URL to the giveniconUrl
setTitle(tabElement, tabTitle)
— set the tab element's title text to the giventabTitle
text
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should return the information necessary to record a call to the clickTab(objectOrName, tabTitle) function as returned by the String Squish.createClickTabInformation(tabWidgeElement, tabTitle) function. (Without the hook function, no calls to the clickTab(objectOrName, tabTitle) function will be recorded.)
See also, the clickTab(objectOrName, tabTitle) function and the String Squish.createClickTabInformation(tabWidgeElement, tabTitle) function, and the HTML_Tab Class and HTML_TabWidget Class classes.
Text Field Support
To make Squish recognize a custom text field a suitable custom typeOf
function must be defined and registered in an extension using the Squish.addTypeOfHook(function) function. The registered function should return a string of the form "customtext_name"
(e.g., "customtext_CustomTextField"
).
In addition to registering the hook function, the extension must provide a JavaScript object called nameExtension
(e.g., CustomTextFieldExtension
). This object must provide the following methods:
getValue(textFieldElement)
— return the element's current text.setFocus(textFieldElement)
— move the keyboard focus to the giventextFieldElement
.setValue(textFieldElement, text)
— set the text field element's current text to the given text.
In addition to the above, extensions should also implement an event to string hook using the Squish.addEventToStringHook(function) function. This function should make sure that the "value" parameter is added to the returned string and contains the value of the text field.
See also the HTML_CustomText Class.
API to access properties that make up an object name. | |
API for extending Squish to support custom Web Objects. |
© 2024 The Qt Company Ltd.
Documentation contributions included herein are the copyrights of
their respective owners.
The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation.
Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property
of their respective owners.