public interface AutomatedTesting
Smart GWT includes a free, custom Selenium extension for robust record and playback of tests, including the ability to record on one browser and play back on others, support for Selenium Remote Control allowing tests to be written in a variety of programming languages and run as scripts, as well as Smart GWT-specific enhancements to the Selenium IDE.
These extensions can be found in the
selenium/
directory and a user guide can be found here
.
Selenium supports writing test code in any programming language via Seleniun RC. By writing Selenium RC test cases in Java, you can drive them from JUnit, hence creating automated tests that can be run from the command line or via Continuous Integration servers such as Hudson, allowing for running tests on checkins to source control or in overnight batch runs.
Services such as SauceLabs OnDemand allow you to run the actual browsers in the cloud, tunneling back to a private network via an encrypted channel, so that you do not need to set up Selenium RC servers with appropriate browsers installed.
For apps requiring load testing, also take a look at BrowserMob, which allows you to run Selenium tests with thousands of browsers at once against a test deployment.
JUnit + Selenium RC
Explore JUnit + Selenium RC
, where we walk
through a JUnit test built
using Selenium IDE and targeting a Smart GWT Showcase example.
SOASTA's CloudTest product includes special support for Smart GWT with capabilities similar to our Selenium extensions, with special emphasis on load testing. Find out more at http://soasta.com.
GWT includes a way to run a GWT application under JUnit, running your GWT application in a "headless" browser. This is a very limited testing approach appropriate for certain unit tests only - it cannot replace events such as clicks, and it doesn't run in actual browser (instead it runs in a simulator called HtmlUnit), which can lead to false failures in a variety of areas, including network communication and XML processing, where HtmlUnit's behaviors do not correspond to any real browser.
For these reasons, Isomorphic recommends performing substantially all of your tests via Selenium, including unit tests. In particular, if a test fails under HtmlUnit but would not fail in a real browser, this will not be regarded as a bug.
If you use GwtTestCase, note that it has a bug where it does not run onModuleLoad() for
included GWT modules. To make sure SmartGWT's onModuleLoad() runs, add a
gwtSetUp()
implementation like so:
public class SgwtTest extends GWTTestCase { public void gwtSetUp() { new SmartGwtEntryPoint().onModuleLoad(); } ...
You may need to add similar manual calls for other GWT modules you inherit which expect to
have their onModuleLoad()
method called normally.
WebDriver, which is now part of Selenium 2, uses a different basic architecture in which extensions are added to each browser in order to drive tests, instead of doing so from JavaScript.
Support for WebDriver-based testing for Smart GWT is now available with the same custom locator strategies and custom commands as we provide for Selenium 1.0. However, we continue to recommend Selenium 1.0 rather than WebDriver-based Selenium 2, because:
TestRunner
. Most ways of running WebDriver tests involve Java
coding
skills or at least the ability to work with a Java IDE. This tends to mean that all QA
personnel must either have Java skills or drain the time of Java developers on repetitive
tasks.
Ultimately, our current recommendation is to use Selenium 1.0 and Selenium RC exclusively or at least primarily. If there are critically important tests that you can only build via WebDriver (rare: the most common such case is testing file upload - see below), use WebDriver for those tests only, or use manual testing for those tests.
WebDriver Usage
When using WebDriver, we recommend using Selenum IDE to record tests, and storing tests in
Selenese (as with Selenium RC / 1.0). WebDriver is not normally able to execute Selenese
tests, but we provide a Java class SeleneseRunner
that can be used to:
NOTE: Selenium IDE has an option to export tests as WebDriver-compatible code. Do not use this feature, it exports useless code that doesn't understand custom commands, custom locators, or other key features of Selenium IDE. Use SeleneseRunner instead.
WebDriver Classes overview
Storing and executing Selenese tests recorded in the Selenium IDE is recommended as the primary approach for using WebDriver. However, for certain rare tests it can make sense to use WebDriver Java support directly.
Smart GWT support for WebDriver is based around 3 different Java classes:
UsingSelenium
for more
background on Locator strings and how to obtain them. Given a locator String, example usage
is:
ByScLocator.scLocator("//ListGrid[ID=\"countryList\"]/body/row[countryCode=US||0]/col[fieldName=countryCode||0]")
These classes are packaged in the library isomorphic_webriver.jar, which can be found in WEB-INF/lib-WebDriverSupport (along with several 3rd-party supporting libraries).
General information regarding WebDriver can be found here. Setup for WebDriver is more complex than for classic Selenium: The basic Java package includes drivers for FireFox (subject to important version limitations as described above), but additional drivers must be downloaded for Google Chrome and Internet Explorer.
File Upload Example Test
As discussed above, one advantage which WebDriver does have over Classic Selenium is the ability to test file upload. It is still limited in that if a click is triggered on the file selection button an OS native file selection dialog will be triggered in which case the test will be suspended until the file is manually selected. To avoid this, the sendKeys() method can be used to enter the file location. Two examples of this are given below - one for the Smart GWT showcase, and one for SmartGWT:
/** * The following test runs against localhost and requires a small (< 50k) image to be in /tmp/image.jpg */ public void fileUploadSC() throws Exception { Smart GWTFirefoxDriver driver = new Smart GWTFirefoxDriver(); driver.setBaseUrl("http://localhost:8080/"); driver.get("isomorphic/system/reference/Smart GWT_Explorer.html#upload"); driver.manage().window().maximize(); final int origSize = driver.findElements(ByScLocator.scLocator("//TileGrid[ID=\"mediaTileGrid\"]/tile")).size(); By titleInput = ByScLocator.scLocator("//DynamicForm[ID=\"uploadForm\"]/item[name=title||title=Title||index=0|" +"|Class=TextItem]/element"); driver.click(titleInput); driver.sendKeys(titleInput, "test image: " + origSize); By uploadForm = ByScLocator.scLocator("//DynamicForm[ID=\"uploadForm\"]/"); WebElement form = driver.findElement(uploadForm); WebElement findElement = form.findElement(By.xpath("//input[@type='FILE']")); /* * The following causes a native dialog to be created which prevents further progress. Do NOT uncomment! * We just have to sendKeys() to it */ //findElement.click(); findElement.sendKeys("/tmp/image.jpg"); // A local file. Please change accordingly By saveButton = ByScLocator.scLocator( "//DynamicForm[ID=\"uploadForm\"]/item[title=Save||index=2||Class=ButtonItem]/button/"); driver.waitForElementClickable(saveButton); driver.click(saveButton); /* * Note the following fails once the grid contains more than 3 rows of data * as the index becomes inconsistent as tiles scrolled out of site are removed * and the indices change */ By tile = ByScLocator.scLocator("//TileGrid[ID=\"mediaTileGrid\"]/tile[Class=SimpleTile||index=" +(origSize)+"||length="+(origSize+1)+"||classIndex="+(origSize)+"||classLength="+(origSize+1)+"]/"); driver.waitForElementClickable(tile); WebElement tile1 = driver.findElement(tile); assertEquals("test image: " + origSize, tile1.getText()); assertEquals(origSize + 1, driver.findElements(ByScLocator.scLocator("//TileGrid[ID=\"mediaTileGrid\"]/tile")).size()); driver.close(); driver.quit(); } /** * The following test runs against localhost and requires a small (< 50k) image to be in /tmp/image.jpg */ public void fileUploadGWT() throws Exception { final String basePath = "//VLayout[ID=\"isc_Showcase_1_0\"]/member[Class=HLayout||index=0||length=2|" +"|classIndex=0||classLength=1]/member[Class=HLayout||index=0||length=2||classIndex=0|" +"|classLength=1]/member[Class=Canvas||index=1||length=2||classIndex=0||classLength=1]" +"/child[Class=TabSet||index=0||length=1||classIndex=0||classLength=1]/paneContainer/" +"member[Class=VLayout||index=1||length=2||classIndex=0||classLength=1]/" +"member[Class=VLayout||index=1||length=2||classIndex=0||classLength=1]/" +"member[Class=HLayout||index=1||length=2||classIndex=0||classLength=1]/" +"member[Class=HLayout||index=0||length=1||classIndex=0||classLength=1]/"; final String formPath = basePath + "member[Class=DynamicForm||index=0||length=3||classIndex=0||classLength=1]"; final String tilesPath = basePath + "member[Class=VLayout||index=2||length=3||classIndex=0||classLength=1]/" + "member[Class=TileGrid||index=2||length=4||classIndex=0||classLength=1]/tile"; Smart GWTFirefoxDriver driver = new Smart GWTFirefoxDriver(); driver.setBaseUrl("http://localhost:8888/"); driver.get("index.html#upload_sql"); driver.manage().window().maximize(); final int origSize = driver.findElements(ByScLocator.scLocator(tilesPath)).size(); By uploadForm = ByScLocator.scLocator(formPath); WebElement form = driver.findElement(uploadForm); By titleInput = ByScLocator.scLocator(formPath + "/item[name=title||title=Title||index=0||Class=TextItem]/element"); driver.click(titleInput); driver.sendKeys(titleInput, "test image: " + origSize); WebElement findElement = form.findElement(By.xpath("//input[@type='FILE']")); /* * The following causes a native dialog to be created which prevents further progress. Do NOT uncomment! * We just have to sendKeys() to it */ //findElement.click(); findElement.sendKeys("/tmp/image.jpg"); // A local file. Please change accordingly By saveButton = ByScLocator.scLocator(formPath + "/item[title=Save||index=2||Class=ButtonItem]/button/"); driver.waitForElementClickable(saveButton); driver.click(saveButton); /* * Note the following fails once the grid contains more than 3 rows of data as the index becomes inconsistent * as tiles scrolled out of site are removed and the indices change */ By tile = ByScLocator.scLocator(tilesPath + "[Class=SimpleTile||index="+(origSize)+"||length="+(origSize+1) + "||classIndex="+(origSize)+"||classLength="+(origSize+1)+"]/"); driver.waitForElementClickable(tile); WebElement tile1 = driver.findElement(tile); assertEquals("test image: " + origSize, tile1.getText()); assertEquals(origSize + 1, driver.findElements(ByScLocator.scLocator(tilesPath)).size()); driver.close(); driver.quit(); }
Other tools
Smart GWT supports a special JavaScript API to allow other test tools to integrate in the same manner as Selenium, WebDriver and SOASTA. This API allows the test tool to record an abstract "locator" string representing the logical name for an interactive DOM element, and then during test playback, retrieve a DOM element given a locator.
This is critical because, like many modern Ajax systems, Smart GWT generates different DOM elements in different browsers, in different skins, and in different versions of Smart GWT. Testing tools that try to directly record the generated Smart GWT DOM produce extremely brittle tests because they are effectively recording undocumented internals.
Using the "locator" API allows you to record or write tests that will run in any browser supported by Smart GWT, in any version of Smart GWT, and in any skin. It also makes tests more readable and easier to understand and maintain.
Different testing tools vary in how easily they can be configured to use the locator API, and in some older tools it can be a large effort. We highly recommend using our Selenium extensions - it often makes sense to use them even if you have to use them in parallel with another, older testing tool. If you are forced to use another tool exclusively: