public interface AutoTestLocator
AutoTest
class to reliably identify an
interactive element in a Smart GWT application. These locators are designed to work across browsers, Smart GWT
versions, and even major changes to your application's UI. They do not rely on the DOM structure, and are intended to be
able to identify the target element reliably without requiring pervasive explicitly specified component IDs
.
The AutoTest
class is
responsible for creating locator strings corresponding to from DOM elements, Smart GWT components or other objects, and
for resolving locators back to those objects at runtime. See AutoTest.getLocator()
, AutoTest.getElement()
, AutoTest.getObject()
and related methods.
Regardless of which automated testing tools you are using, you should be using AutoTestLocators to locate interactive DOM elements in your tests.
When running an automated test, you can obtain a specific DOM element using an AutoTestLocator via AutoTest.getElement()
. When designing tests, a locator for any element
can be obtained using AutoTest.getLocator(element)
. Locators may
also be retrieved by clicking the "Show AutoTest Locators" link on the initial tab (titled "Results") of the Smart GWT
Developer Console
, or by holding down modifier keys while clicking elements
in an application after installing the locator
shortcut
.
Use Locators rather than DOM-based techniques to identify elements
Whatever testing tool you're using, AutoTestLocators should be used to identify elements generated by Smart GWT instead of css selectors or similar because the DOM structure generated by Smart GWT components may differ by browser, browser version, Smart GWT version, skin, and settings (such as auto-sizing). Different DOM structures are required to work around browser bugs, for performance reasons, or in order to render a different visual appearance for a functionality-equivalent component.
The DOM structure generated by Smart GWT is considered an internal detail, so, if you build tests that rely on any particular DOM structure instead of using AutoTestLocators, those tests will likely not work across browsers, and may also be broken by switching skins, changing settings, or even broken even by minor Smart GWT bug fixes within the same Smart GWT version.
AutoTestLocator structure
AutoTest locators consist of a series of segments, delineated by "/" characters. Each segment identifies a step in a hierarchy from a root component to the target element. Individual segments may represent a Canvas, a FormItem, an interior DOM element or some other construction specific to the locator in question.
Note that the segments in an AutoTestLocator do not necessarily match parent-child relationships in the widget hierarchy. For example:
AutoChildren
are typically identified by their autoChild name within locators, but are not guaranteed to be a
direct descendant of their auto-parent.Canvas.setLocatorParent()
. The AutoTest.getLocator()
method takes a settings
argument allowing develoeprs to configure some aspects of
how locators are constructed. See AutoTestLocatorConfiguration for details of the options available.
Developers may also make use of the following convenience methods to generate locators with particular characteristics:
AutoTest.getMinimalLocator()
: This method will return a
locator that is as compact as possible. Minimal locator segments do not include fallback attributes (described
below), which may make them less robust across changes to an application.AutoTest.getFullPathLocator()
: This method will return a locator
with no search segments. Full path locators explicitly identify every step of the locator hierarchy to the root
component, making them predictable and stable. They are also typically longer and less easy to read than normal
locators, and the lack of search-segments may also render them less robust across changes to an
application.Locating components by ID
If a component has an explicitly specified ID
this may be used along with the class of the widget to reliably identify
it, regardless of where it is in the page's widget hierarchy. For example a VLayout with ID "leftPane"
would be identified as simply:
//VLayout[ID="leftPane"]
The Canvas.locateByIDOnly
setting may also be set to locate by ID
without capturing the class name of the component. In this case the locator will be just the widget ID prefixed by three
slash characters. For example the above locator would become just this:
///leftPane
This will reliably identify the component based on its ID, without warnings even if the application is refactored and the component is implemented as an instance of another class.
TestRoot
The testRoot
attribute allows developers to specify a root component for
locators. This is useful for cases where a standard widget hierarchy may be dynamically rendered inside some component
(such as a screen
). The testRoot
mechanism allows you
to record locators that identify elements relative to a specified root component, and then resolve these locators back
in an environment where the same user interface structure is rendered inside a different component, by setting the
testRoot appropriately on that target environment.
See the PortableAutoTests
overview for more on creating tests that may be run in multiple environments.
Locator segment fallback attributes
Some locator segments identifying child components under a parent are simple identifiers (such as /autoChildName/). However, many child locator-segments include multiple attributes to allow the parent to resolve the locator segment correctly.
For example a locator segment identifying a member of a Layout might look like this:
"/member[Class=TreeGrid||index=1||length=3||classIndex=0||classLength=1||roleIndex=0||roleLength=1||scRole=tree]/"
When resolving this locator back to a target component, the AutoTest system can use multiple strategies to find the correct member.
This specific segment indicates that the target component is a member of a layout. The target is a
TreeGrid, and is the first of 3 members. It also indicates this is the only member TreeGrid in the members array
[indicated by the classIndex
and classLength
attributes], and that it is the only member with
role
set to "tree"
.
The parent layout will use these fallback attributes in the locator as necessary to find the appropriate member. If the layout has three members, and the first is a TreeGrid, the target may be resolved to this member with some confidence.
If this is
not the case, but there is exactly one TreeGrid in the members array, or exactly one element with role
set
to tree, it will fallback to those secondary locator strategies. If fallback locator strategies are used, a warning will
be logged so developers are aware of any potentially incorrect locator parsing.
Developers may influence how
recorded locator attribute are resolved via properties on the locator parent. For Layouts this would include Layout.locateMembersBy
and Layout.locateMembersType
.
For brevity and
readability, when generating locators, developers may choose whether or not to include multiple fallback attributes in
locator segments by setting useMinimalFallbackAttributes
to false globally, or on the settings
parameter passed to AutoTest.getLocator()
, or by using AutoTest.getMinimalLocator()
.
Search-Segments in locators
Locator segments that begin with "//"
(or "//:"
for the first segment in the
locator) are search segments.
Search segments allow some or all of the hierarchy to be skipped over by
searching for component(s) by definingProperty
values.
For example if there is only one visible grid in an application bound to some dataSource, a search segment of the format
//:ListGrid[dataSource="someDS"]/
may be used to find the component, wherever it is in the page's hierarchy.
For a locator to reliably identify a target, search segments must unambiguously resolve to
a single component. For example if an application contained more than one visible ListGrid bound to someDS
,
the above locator would not be able to determine which of the grids it referred to.
For this reason, locators may contain multiple search segments. A search segment that is not at the root of a locator string will search for a component by defining property within some ancestor identified by the previous segment. For example the locator
//VLayout[ID="leftPane"]/mainView//ListGrid[dataSource="someDS"]
would be resolved by finding the
"VLayout" with ID "leftPane", navigating to its "mainView" autoChild (or locator-child
), and performing a depth-first search of all visible
descendents of mainView
for a ListGrid bound to the "someDS"
dataSource.
Note that the
AutoTest.getLocator()
method will never return a locator with a
search segment that is ambiguous in the current application.
If it cannot uniquely identify a component by defining property across the app as a whole, it will find the highest-level ancestor containing only one descendant with the specified definingProperty, and ensure the previous segment in the locator identifies that ancestor. This ensures that the locator is as compact as possible while unambiguously identifying the target.
Searching for hidden components
By default locator search segments consider only visible components. If a component is hidden or undrawn, it will not be considered a viable match for a search segment in a locator. However in some cases a developer may need the ability to identify a widget by locator even if they don't know for certain whether it is currently visible or hidden.
Search segments which include a "?"
prefix will include both hidden and visible components
when searching for their target.
AutoTest.getLocator()
will
automaticlly add this prefix to any search segments it generates where the target it was passed is currently hidden.
Developers may also explicitly request the locator consider both hidden and visible components when generating search
locators via the searchSegmentsIncludeHidden attribute of the settings argument.
Note that to ensure uniqueness, locators with search segments that include hidden components may have a different structure from those that do not. For example, the locator
//:ListGrid[dataSource="someDS"]/
would resolve unambiguously if there is one
visible listGrid bound to the "someDS"
dataSource, even if there are multiple hidden grids bound to the
same dataSource, but the locator
//:?ListGrid[dataSource="someDS"]/
might not. Instead
getLocator()
would have to generate a longer locator string that to uniquely identify the target, hidden
or visible, based on its position in the page's widget hierarchy.