public interface UsingSelenium
For an overview of Automated
Testing in Smart GWT, see the documentation here
.
Selenium executes tests against your running application in a browser emulating user interaction and asserting various conditions. Selenium provides a record/playback tool for authoring tests without learning a test scripting language - we refer to these as Selenium scripts or Selenese. You must be familiar with Selenium and use of Selenium IDE before proceeding. Refer to the documentation on the Selenium site.
Note that Selenium IDE 3 for Firefox Quantum has been released, but only recently added plugin APIs that should eventually allow us to add support for our our custom locators and commands (replacing our JavaScript user extensions from Selenium IDE 2.9). For now, if you want to write Selenese as this section describes, you must use Selenium IDE 2.9, which can be installed in Firefox 52 ESR, the extended-support release of Firefox which is still receiving updates at the time of this writing.
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 TestRunner
.
Setup Instructions
Smart GWT ships with two Selenium user extension Javascript files:
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 programmatically (e.g. from a test harness) using SeleneseRunner, a simulation tool that executes Selenese against Smart GWT's WebDriver wrappers. This is required because Selenium 3 no longer supports executing Selenese using custom user extensions, as it no longer contains much of the Selenium RC code base.
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 operation 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:
scLocator=//ListGrid[ID="filterGrid"]/body/row[pk=216||countryCode=CG||215]/col[fieldName=countryCode||0]
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:
waitForGridDone("//ListGrid[ID='filterGrid']");
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.
To do this, use RPCManager.requestsArePending() in combination with waitForCondition(). So, the JavaScript in your waitForCondition() operation would be:
!selenium.browserbot.getCurrentWindow().isc.RPCManager.requestsArePending()
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.
Keystroke Capturing
Our Selenium Extensions will automatically record the following type of keyboard activity in Smart GWT widgets, on a keystroke-by-keystroke basis:
auto child
of 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 Logs
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
By default, our user extensions automatically add a special URL variable, sc_selenium, to open command urls to allow JavaScript to detect it's being driven by Selenium in case special logic should be used. In the unlikely event that this causes a problem with your code or page loading and you don't need the feature, you may eliminate this special URL variable by changing Selenium.prototype.use_url_query_sc_selenium from true to false in user-extensions.js.
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]
Form Items
//DynamicForm[ID="autoTestForm"]/item[name=textField||title=textField||value=test||index=0||Class=TextItem]/element
This example is the data element (text entry box) for a text field
Special scLocator usage
Verifying icon/image loading
If you manually add
/imageLoaded to the end of the locator generated for a Button
or Img
, then AutoTest.getElement()
or AutoTest.getValue()
can be used to verify whether
the icon (in the case of a Button
) or image (for an Img
) have been
loaded succesfully.
Examples:
Verifying DrawItem attributes
If you record a locator to a DrawItem
, by
default its value will be the title
, or the contents
for a
DrawLabel
. However, other attributes, as listed
below, can be queried by adding a trailing suffix. (Those appearing as links call the
associated getter rather than accessing a property directly.) Note that not all properties
listed are available on all DrawItem
s, and that if
multiple values are present, they will be separated by a single space when the Framework passes
them to Selenium.
center
boundingBox
resizeBoundingBox
Examples:
Best Practices
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).
Known Limitations