Bundling ChromeDriver with your test code

This post will just exemplify one way of maintaining the chrome drivers used for running automated tests with WebDriver and the Chrome browser without having to update and install the ChromeDriver to all possible nodes where tests will be running. The ChromeDriver will simply be bundled with the running tests and put under source control as for all other test ware.

Downloading links
http://code.google.com/p/selenium/wiki/ChromeDriver

Bundling
Start by putting the the drivers under the resource folder so that they will be picked up by Maven per default.

<project root>/src/main/resources/chromedriver/mac/chromedriver
<project root>/src/main/resources/chromedriver/windows/chromedriver.exe

Implementation
There are of course different drivers for different OS types and this needs to be handled using the os.name system property.

As per the ChromeDriver usage instructions (here) a system property has to be set pointing to the ChromeDriver server to use for the Chrome browser bridging. We will not point to a fixed location in the file system, instead well get the path by using the Class.getResource which will enable us to bundle ChromeDriver inside our test framework even if it is bundled into a jar file.

Basically what should be done are the following steps.

  • Determine OS type
  • Get the Chrome Driver resource and make sure it is executable using File.setExecutable(true). This is due to when packaged in a jar the execution attributes ‘x’ will be removed on Mac (and assumed on Linux too).
  • Set the “web driver.chrome.driver” system property.
  • Check that a Chrome installation exists in the default location [OPTIONAL]
private static WebDriver driver = null;
// The ChromeDriver locations under the resource folder
private static String MAC_DRIVER = "/chromedriver/mac/chromedriver";
private static String WINDOWS_DRIVER = "/chromedriver/windows/chromedriver.exe";

public static void setupChromeDriver() {
   // OS type
   if (System.getProperty("os.name").contains("Mac")) {
      File cDriver = new File(Tester.class.getResource(MAC_DRIVER).getFile());

      // Is it executable
      if (!cDriver.canExecute()) {
         cDriver.setExecutable(true);
      }
      System.setProperty("webdriver.chrome.driver", Tester.class.getResource(MAC_DRIVER).getFile());

      // Now checking for existence of Chrome executable.'
      if (!new File("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").exists()) {
         throw new RuntimeException(errorMessage);
      }
   } else {
      System.setProperty("webdriver.chrome.driver", Tester.class.getResource(WINDOWS_DRIVER).getFile());

      // Now checking for existence of Chrome executable.'
      if (!new File(System.getProperty("user.home") + "/AppData/Local/Google/Chrome/Application/chrome.exe").exists()) {
         throw new RuntimeException(errorMessage);
      }
   }

   ChromeOptions options = new ChromeOptions();
   options.addArguments("--start-maximized");
   options.addArguments("--ignore-certificate-errors");
   driver = new ChromeDriver(options);
}

Test case example
Pretty straight on from here, setup WebDriver through the implemented method above and run a simple open page test to see that things worked out.

private static WebDriver driver = null;
public static void setupChromeDriver(){
   ...
}

@BeforeClass
public static void setupTestClass() throws Exception {
   setupChromeDriver();
}

@Test
public void demoTestCase() throws Exception {
   driver.get("http://code.google.com/p/selenium/wiki/ChromeDriver");
   Thread.sleep(1000);
}

WebDriver tricks of the trade

A post to be used as a reference for items/issues that has surfaced when using WebDriver with Java.

.

Muting WebDriver logs

At points where WebDriver is really verbose it is quite convenient to mute the output or at least certain levels of it.

// Java
// Set it before creating the WebDriver instance to avoid having any messages generated from the
// constructor call
RemoteWebDriver.setLogLevel(Level.WARNING); // java.util.logging.Level
WebDriver driver = new FirefoxDriver(profile);

.

Avoiding untrusted SSL cerificate errors when using Firefox

Typical test environment issue. A simple workaround that worked for us was to simply set the profile used to accept and assume any certificate issues.

// Java
FirefoxProfile profile = new FirefoxProfile();
profile.setAcceptUntrustedCertificates(true);
profile.setAssumeUntrustedCertificateIssuer(false);
WebDriver driver = new FirefoxDriver(profile);

.

Running FireFox in full screen

Especially handy when combining Sikuli and WebDriver, you need to be absolutely sure that as much as possible is visible on the screen.

((FirefoxDriver)driver).getKeyboard().pressKey(Keys.F11);

.

Relative xpaths from the current WebElement

Very useful when working with complex HTML-pages whose elements are very deep down and can only be found using xpath. Maintaining these long xpaths in multiple places in the test code base is a pain. The solution is to do the xpathing in parts to minimize the effort needed to maintain the xpath-strings.

<html>
  <body>
    <table>
	  <tbody>
	    <th></th>
        <tr>
		  <td>cell to find</td><td></td>
		</tr>
		<tr>
		  <td></td><td></td>
		</tr>
      </tbody>
	</table>
  </body>
</html>

Starting the xpath-string with the DOT is equivalent to starting the search from the current node.

WebElement tableElement = driver.findElement(By.xpath("//html/body/table"));
// Using relative xpath from the beginning of the table to find the first cell.
// Note that it has to start with the '.', DOT
WebElement cell = driver.findElement(By.xpath("./tbody/tr[1]/td[1]"));

.

Switching between open windows

Based on words in the page title.

Set<String> windows = driver.getWindowHandles();
for (String window : windows) {
  driver.switchTo().window(window);
  if (driver.getTitle().contains(pageTitle)) {
    break;
  }
}

.

Dismissing/accepting alert dialog windows

Whenever an alert window pops-up it has to be accepted or dismissed to avoid blocking the test execution, this is simply handled using Alert class (Selenium docs).

// WebDriver driver = new FirefoxDriver();
Alert alert = driver.switchTo().alert();
alert.accept();
// OR
// alert.dismiss();

Using Sikuli to test legacy Flash

With loads of legacy Flash/Flex code where test automation support is limited or in most cases even non-existing there is only so much that can be done in forms of automation. I cannot understand why I did not pick up Sikuli earlier but I should have, that is all I am saying. After two days of playing around and demoing QA engineers around me are so eager to get going.

The concept of image recognition and test case maintenance does not sound too tempting but for the case with legacy flash implementations where the graphics will surely not change it would be unfair not to try it out. So we did but it was not out of the box.

The simplest of test cases
In short, unless you want to read it from Project Sikuli. Sikuli can match a predefined image to a section of the screen that is VERY similar to predefined image, ‘best fit’. So for example if you take a small screenshot of an object in you Flash implementation and save it as an image Sikuli can then be used to interact with this object, clicking on it, dragging it, checking if it can find it on the page asf..

Sikuli can rather easily be tested out, for example against a common Flash application like the one in the clip below. To get a test cases running just crop out the correct pictures from the application under test.

The pictures below is found in the application under test without any problems for Sikuli.

Playing roulette using Sikuli.

The Junit test flow looks like this.


// Java
//
// Example Sikuli Test flow, no verifications in place it only reflects what is
// visible in the youtube clip above.
//
@Test
public void playRoulette() throws Exception {
  SikuliDemo sikuliDemo = new SikuliDemo();
  sikuliDemo.clickButton("bet5.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("betonred.PNG");
  sikuliDemo.clickButton("bet5.PNG");
  sikuliDemo.clickButton("betonred.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("cleartable.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("betonodd.PNG");
  sikuliDemo.clickButton("bet5.PNG");
  sikuliDemo.clickButton("betonodd.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("bet25.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("beton13.PNG");
  Thread.sleep(10000);
  sikuliDemo.clickButton("spin.PNG");
  Thread.sleep(300000);
}

Integrating into your Java test framework
So how to, using Java? In fact code wise, very little code is needed to get the basic functionality in place, click and verify. The code sample below simply looks for an image snapshotted from the application under test anywhere on the screen and clicks it as well as checking for the visibility of another. Just make sure that sikuli-script.jar is in the classpath.


// Java
import org.sikuli.script.Screen;

public class SikuliDemo {
  public boolean clickButton(String gameName, String imageName) {
    // Window is already opened by WebDriver
    Screen s = new Screen();
    try {
      s.exists(imageName, FLASH_TIMEOUT);
      s.click(imageName, FLASH_TIMEOUT);
    } catch (FindFailed ff) {
      return false;
    }
    return true;
  }

  public boolean verifyImage(String gameName, String imageName) {
    // Window is already opened by WebDriver
    Screen s = new Screen();
    try {
      s.wait(imageName, FLASH_TIMEOUT);
    } catch (FindFailed ff) {
      return false;
    }
    return true;
  }
}

Getting things to work
… not as easy as expected, the following issues are some that surfaced.

Running on a 64bit Windows system

Be thorough when configuring your system or it is very easy to get error messages relating to Win32Util.dll or VisionProxy.dll. I basically had to uninstall all previously installed Java versions to get it working.

A Java SDK/JDK 32bit installation is needed, these end with ‘i586’ on the Oracle download site. Then make sure the following environment variables are set accordingly on the running system.

  • SIKULI_HOME – Sikuli installation folder
  • JAVA_HOME – To the 32bit installation
  • PATH – Add %SIKULI_HOME%\libs;JAVA_HOME\bin

Running from Jenkins
The next obstacle, Sikuli needs access to the desktop to work. The most common way to run Jenkins is a Windows web service, yes it can be configured to get access to the desktop but no matter how we could not get it working properly. The solution that worked out in the end was to run Jenkins standalone from a command window using;

$ java -jar jenkins.war

By doing that Jenkins had access to the desktop as well as making it possible to watch the tests execute when logged in to the build agent.

Firefox in full screen
Some tests still had problems running smoothly on some machines due to screen resolutions and visibility. Since the Flash was embedded into a web page setting Firefox to expand to full screen was a fix that worked out quite smoothly. Using the WebDriver and to get the FirefoxDriver simulate an F11 key press was done with the single line of code below.

// Java
((FirefoxDriver)driver).getKeyboard().pressKey(Keys.F11);

Downloads