WebDriver testing support for qooxdoo desktop and mobile applications.
This is an open source project, led by one of the world's largest web hosts 1&1, with a vibrant community.
The goal of this project is to provide an API that facilitates writing WebDriver-based interaction tests for qx.Desktop and qx.Mobile applications by abstracting away the implementation details of qooxdoo widgets. Here's a quick example:
QxWebDriver driver = new QxWebDriver(new FirefoxDriver());
// Open the page and wait until the qooxdoo application is loaded
driver.get("http://demo.qooxdoo.org/current/apiviewer/index.html");
// Find the 'content' button in the tool bar
By button = By.qxh("apiviewer.Viewer/*/[@label=Content]");
Widget contentButton = driver.findWidget(button);
// Click the button if it's not already selected
if (!contentButton.isSelected()) {
contentButton.click();
}
// Select the "data" item from the package tree
By tree = By.qxh("apiviewer.Viewer/*/apiviewer.ui.PackageTree");
Selectable packageTree = (Selectable) driver.findWidget(tree);
packageTree.selectItem("data");
Clone the repo, then run
mvn verify
This will run the included example tests against the Widget Browser using Firefox, so you must have Firefox installed and a working internet connection.
QxWebDriver provides a set of Widget classes similar to WebDriver's support classes, each of which implements org.oneandone.qxwebdriver.ui.Widget or one or more of the interfaces inheriting from it, such as Touchable, Selectable or Scrollable. These interfaces allow complex actions to be performed by relatively few API calls.
Widgets are obtained by calling QxWebDriver.findWidget(by). where by is any locator strategy that finds a DOM element which is part of a qooxdoo widget. findWidget will determine the qooxdoo class of the widget, its inheritance hierarchy and the interfaces it implements, and use this information to decide which Widget implementation to return.
Similar to WebElement.findElement, Widget.findWidget will restrict the search to children of the current widget.
The best way to learn about the various Widget interfaces is to check out the sample integration tests in src/test/java.
WebDriver's built-in "By" strategies like By.xpath or By.className generally don't work too well with the entangled and dynamic DOM structures generated by qx.Desktop applications. The By.qxh strategy (qx for qooxdoo, h for hierarchy) provides an alternative approach that searches for elements by using JavaScript to traverse the application's widget hierarchy.
For example, the qooxdoo Feed Reader's UI hierarchy looks like this (easily determined by opening the Feed Reader in the Inspector):
qx.ui.root.Application
- qx.ui.container.Composite
- feedreader.view.desktop.Header
- feedreader.view.desktop.ToolBar
- qx.ui.toolbar.Button
[...]
A qxh locator that finds a toolbar button with the label Reload could look like this:
By by = By.qxh("child[0]/feedreader.view.desktop.ToolBar/[@label=Reload]");
As you can see, the syntax is similar to XPath, consisting of location steps separated by slashes. While searching, each location step selects a widget which will be used as the root for the rest of the search.
-
foo.bar.Baz A string containing dots will be interpreted as the class name of a widget. Uses instanceof internally so inheriting widgets will be found as well.
-
child[N] Signifies the Nth child of the object returned by the previous step.
-
[@attrib{=value}] Selects a child that has a property attrib which has a value of value. "Property" here covers both qooxdoo as well as generic JavaScript properties. As for the values, only string comparisons are possible, but you can specify a RegExp which the property value will be matched against. toString() is used to compare non-String values.
-
* is a wildcard operator. The wildcard can span zero to multiple levels of the object hierarchy. This saves you from always specifying absolute locators, e.g. the example above could be rewritten as
By by = By.qxh("*/[@label=Reload]");
This will recursively search from the first level below the search root for an object with a label property that matches the regular expression /Reload/. As you might expect, these recursive searches take more time than other searches, so it is good practice to be as specific in your locator as possible.
Note that the qxh strategy will only return the first match for the locator expression, so it can't be used with WebDriver.findElements.
The root node where a By.qxh locator will begin searching is determined by its context: When used with QxWebDriver.findWidget, the children of the qooxdoo application's root widget will be matched against the first step. When used with Widget.findWidget, the widget itself will be the root node for the search.
Inline applications extending qx.application.Inline can have multiple root widgets. To locate a child widget, first find the DOM node to which the inline root is attached, then search within it:
WebElement root = driver.findElement(By.id("inlineRoot"));
// Within a WebElement, we can't use findWidget, so we use findElement...
WebElement buttonEl = root.findElement(By.qxh("qx.ui.container.Composite/qx.ui.form.Button"));
// ...and get a Widget for it
Widget button = (Widget) driver.getWidgetForElement(buttonEl);
The Getting Started tutorial explains how to set up and run qxwebdriver tests using Maven and Firefox.
findWidget uses a WidgetFactory to determine which Widget class to instantiate. An alternative class implementing WidgetFactory and probably extending DefaultWidgetFactory can be passed to the QxWebDriver constructor to support custom widgets.
In theory, QxWebDriver should work with any WebDriver that implements JavascriptExecutor. In practice, FirefoxDriver, ChromeDriver and IEDriver (with IE9) all work well. The current OperaDriver frequently throws exceptions when trying to execute JavaScript, rendering it basically useless for the purpose of testing qx applications.
qxwebdriver is still experimental. The API is subject to change without notice, not all of qooxdoo's built-in widgets are supported and there is much still to be optimized. Don't let that stop you from playing with it, giving us feedback on the usual channels and sending pull requests. Thanks!
- Better support for qx.ui.table.Table
- Additional support for qx.Mobile widgets, e.g. qx.ui.mobile.dialog.Picker