public interface UsingSelenium
Selenium supports the concept of Locators in order to specify the element you'd like a given Selenium command to target. For example Selenium supports XPath based locators and DOM ID based locators. XPath based locators are extremely fragile due to complexity of certain highly nested DOM elements you need access to combined with the fact that XPath support varies across browsers and so your tests might not work across different browsers.
Use of Selenium with Smart GWT applications is no different than using Selenium to write and run test cases with any other application with the exception of one caveat: Smart GWT occasionally renders a different DOM structure depending on the browser for performance or rendering the UI such that it appears identical across various browsers. As a result using DOM ID or DOM XPath based locators with Smart GWT applications is not advisable.
Instead Smart GWT supports a new Selenium locator which is an XPath-like string used by Selenium to robustly identify DOM elements within a Smart GWT application. Smart GWT locators for Selenium are prefixed by "scLocator=" and have a readable XPath-like value even for cells in ListGrid's or TreeGrids. Typically these locators will not be hand-written and are generated by Selenium IDE, Selenium's test recording tool. One primary locator is based on the ID of the Smart GWT widget and has the syntax ID=<Canvas ID>. This simplifies the task of writing tests if you know the ID of the Canvas. For reference, the scLocator syntax for ListGrid cells and DynamicForm FormItems can be found at the end of this document.
You can automate the process of running Selenium tests and saving or reporting results using
These extensions (found in the
selenium/ directory) augment the Selenium tools to support Smart GWT locators. To integrate these extensions with Selenium, follow the steps below:
That's it, we're done configuring the environment.
Note: Tests recorded using Selenium IDE can be played back using Selenium Remote Control. The
user-extensions-ide.js file is not required for playback of Smart GWT-aware tests using Selenium RC, but the
user-extensions.js is. Instructions for using
user-extensions.js with Selenium RC can be found here.
Recording Selenium tests with Selenium IDE
Once you have your application running in Firefox, open Selenium IDE from the Tools ==> Selenium IDE menu option. If the Selenium IDE is in record mode, then clicking or carrying out other operations like typing in a text field with automatically record the appropriate Selenium commands with the Smart GWT locator. In most cases there's no need for you to manually enter the locator, the recorder does this for you! In fact, not only do the provided user extension files record your clicks, drag operations, and typing in the browser--they also try to ensure that your script executes each operaton only when the Smart GWT widgets it depends upon exist and are ready to be interacted with. This ensures that when the test script is executed, then even if one or more triggered operations are asynchronous (delayed), it behaves as expected.
In the screenshot below, note the waitForElementClickable() operation above the click operation; it was added automatically by our user extensions as the click itself was recorded:
Sometimes users may also want finer grain control of what Selenium command is created instead of having the Selenium IDE recorder do this automatically. For example if you want to verify the value of a particular cell in a ListGrid. Instead of typing in the command "verifyTable" and manually enter the Smart GWT Locator (scLocator), you can simply right click on the table cell or any other Smart GWT widget and the most suitable Selenium commands will appear in the context menu along with the scLocator path for the clicked element. See image below.
Solving Ordering Issues in Selenium Scripts
Fundamentally, the reason we add waitForElementClickable() calls before each click is to deal with asynchronous Smart GWT operations. Many operations on widgets or the network are asynchronous, and a correctly coded test should wait for such operations to complete as opposed to inserting an arbitrary delay or using Selenium's setSpeed() function. Using such delays runs the risk of the test failing if replay occurs on a loaded machine or slow network, and also makes the test run slower than needed.
Asynchronous operations include:
The following operations are synchronous and don't require waiting:
You may encounter cases where you have to manually insert a waitForElementClickable() or waitForElementNotPresent() to get a script to behave properly. Looking at the Smart GWT Showcase Example (Grids / Filtering / Advanced Filter), suppose we wanted to filter by country names containing "Za" and wait for the filter step to complete before proceeding. Since the ListGrid initially contains many entries and Zaire is not among them, it is not visible and thus we can solve the original problem by manually adding a waitForElementClickable() on the locator for Zaire's ListGrid entry:
Before the filter operation is issued, the locator is not clickable because the record is not visible:
When the filter operation completes, Zaire and the other search results become visible and the waitForElementClickable() returns successfully allowing the next script command to execute:
Finally, suppose you wanted to do another filter operation to look only at countries (from the previous search results) with populations under 30 million. Since Zaire is above this limit, it will be missing from the search results and you could wait for the filter operation to complete by adding a waitForElementNotPresent() on same locator that we previously used for waitForElementClickable(). It will return true and allow the script to proceed when the filter operation completes:
Waiting on Pending ListGrid Operations
There are cases where waitForElementClickable()/waitForElementNotPresent() will not work--for example if you're performing a sort that's rearranging existing elements on the screen, or if you're performing a filter operation where you're not sure of the results and thus cannot use the approach from the previous section. In such a situation, you may need to add a waitForGridDone() command into your script to ensure the pending operations are complete before you hit the next command.
The waitForGridDone() command guarantees it will not complete successfully unless all of the following potential pending operations on the widget are complete:
This command should be able to block a Selenium script until the ListGrid specified in the locator reaches a stable drawn state with no pending activity. So for a ListGrid names 'filterGrid', all you'd need to add to ensure all pending operations on it have completed is the command:
Waiting on All Pending Network Operations
Because of the waitForElementClickable commands which are automatically inserted during recording, your scripts will automatically wait for the completion of any network operations that block interactivity (via showPrompt, which is enabled by default). However in some cases you may want to wait for all pending network operations to complete, even if they don't block user interactivity.
When the call returns, you'd know that any previously initiated network operations--such as filter/sort operations on DataSources--are complete.
Automatically Waiting on All Pending Network Operations
If you need the functionality from the section above to wait on all pending network operations, but don't want to add extra calls to waitForCondition(), you may switch on automatic enforcement of the condition that isc.RPCManager.requestsArePending() is false. There are two ways to do this:
Like other Selenium IDE commands with a single argument, you'll want to use setImplicitNetworkWait() by passing true (or false) in the Target field of the Selenium IDE GUI (right under command). Without any modifications, the default value for isc.AutoTest.implicitNetworkWait of false will prevail.
Our Selenium Extensions will automatically record the following type of keyboard activity in Smart GWT widgets, on a keystroke-by-keystroke basis:
auto childof another form item (e.g.
MultiComboBoxItem). In this case, you can tap Alt (Option on Mac) to manually insert a "type" command with the right value.
Recording Movement-Driven Interactions
Our Selenium Extensions provide the capability to automatically record click-based interactions with the target page, and keystrokes (under certain circumstances). However, we don't automatically capture interactions based solely on movement, such as when a nested menu flyout is triggered by moving the mouse over the menu item of an existing menu. To avoid problems:
Capturing of client and server-side logs can be switched on by providing appropriate options to
TestRunner, but a few Selenium commands are provided to provide direct control over logging on a per-script basis. If server logging has been configured as "some," then server logs won't be captured for a given script unless you add the captureServerLogs() Selenium command after the open command; switching the mode to "all" will force server logs to be collected for all Selenium scripts, and no captureServerLogs() command is then required.
To configure logging levels, you can use the commands setClientLogLevel(category, level), or setServerLogLevel(category, level). For example:
Disabling the Selenium Smart GWT URL Query String
Common scLocator syntax
For more information on how locators are formed and how to influence them, see the
AutoTest class in the Smart GWT JavaDoc.
List Grid cells
//ListGrid[ID="itemList"]/body/row[itemID=1996||itemName=Sugar White 1KG||SKU=85201400||1]/col[fieldName=SKU||1]
This example is the data element (text entry box) for a text field
Note that currently some browsers will respond to events on offscreen widgets (IE will, Firefox will not) however, web standards are unclear on whether this should be allowed and the behavior may change in the future, so best practice is to maximize for all browsers.
Use setID() selectively to avoid this problem. Generally, it makes sense to use setID() on all top-level (parentless) widgets - at this point, locators for children that do not have a unique ID will be based on the parent's ID plus a relative path. This relative path will not incorporate auto-generated IDs and will generally continue to work even if the interior layout of the parent is significantly rearranged (such as adding a new intervening container widget).