20 February, 2011

Automated Test for Facebook Canvas Apps – Front-end automation with Selenium

There are a number of challenges encountered in establishing test automation for Facebook applications. Among them is the fact that web browsers consider Facebook canvas applications to live in a separate security sandbox from the top-level page. This blog post describes how to use the Selenium 2.0 beta WebDriver technology to automate integrated test scenarios where the application under test is embedded in the Facebook page. The same technique applies to other situations in which the test must interact with elements in an IFrame whose src is a different domain from the top-level page.

Get the right driver

Selenium 2.0 is in beta, and until Beta 2 was released (Feb 2011), the technique described here could only be used for Firefox. With Beta 2, one body of code works with both IE8 and Firefox 3.6 (at least). It's reasonable to hope that as Selenium 2 progresses to its final release, the same tests will be able to run with more browsers. The beta 2 release includes a driver for Chrome, but that driver is not able to execute the tests.

Selenium 2 merges WebDriver into the Selenium project. The tests here rely on the WebDriver API.

Identify the IFrame

In order to interact with the elements of your application, the test will need to get the IFrame name (or index). As of 25 Feb, 2011, the IFrame has a constant name:

 final def canvasName = "iframe_canvas"
The test could alternatively search through all IFrames in the window to locate the relevant item.

Create a driver instance

Selenium lets the test create a driver directly, i.e. new org.openqa.selenium.firefox.FirefoxDriver(). Writing the tests to use RemoteWebDriver allows the test framework to execute the test across platforms:
def seleniumDrivers = [
[browserName: "internet explorer", serverName: "windowshost:4444"],
[browserName: "firefox", serverName: "osxhost:4444"]
].collect {
def browserName = it.browserName
def driverCaps = new DesiredCapabilities()
driverCaps.javascriptEnabled = true
driverCaps.browserName = browserName
return new RemoteWebDriver(new URL("http://$it.serverName/wd/hub" as String), driverCaps)
}
The driver is required to enable JavaScript because it will allow the tests to probe Flash elements in the IFrame.

Write tests

It's not immediately apparent how to best encapsulate the aspects of the test setup where the driver is used to move through the initial flow required to start the application. For the moment, it's just one of the tests upon which the later tests depend.

Log in to Facebook

Nothing tricky about this:
def login(WebDriver selenium) {
//Defines a trivial subclass of WebDriverWait
def waitUntil = new GroovyWebDriverWait(new SystemClock(), selenium, 10, 50)
selenium.get("http://www.facebook.com")
// It seems to be a good idea to check that the field contains all typed characters before going to next test step
waitUntil.keysAccepted(selenium.findElement(By.name("email")), params.userEmail)
waitUntil.keysAccepted(selenium.findElement(By.name("pass")), params.userPassword)
// Click the Login button
selenium.findElement(By.xpath("//label/input")).click()
// Proceed only after Facebook displays the user's page
waitUntil.condition { selenium.findElement(By.id("navAccountLink")) }
}

Load the app

The test could drive the FB interface to locate the application in the UI and launch it from there, but if the objective is to test the workings of the app in the IFrame, send the browser directly to the application.
selenium.get(appURL)
// This is where the driver starts to interact with the application
selenium.switchTo().frame(canvasName)

Wait for rendering

The app probably has dynamically rendered elements. In particular if it uses SWFObject to initialize a Flash player the test should probe for the creation of the corresponding DOM element.

waitUntil.condition { selenium.findElement(By.id(swfElementId)) }

Interact with Flash

If the test is concerned with a Flash application, it will need to make JavaScript calls into function exposed via the Flash ExternalInterface. A convenient way to make a number of useful test functions available is to include FlexPilot. It's (currently) necessary to build a SWF using the bootstrap classes provided with FlexPilot.

Assuming the SWF has successfully loaded FlexPilot, the test can proceed to check that the SWF loading is complete.

waitUntil.condition {
// The parameter to executeScript is really a script - if there's no return, there's nothing to check.
selenium.executeScript("return document.getElementById('$swfElementId').
  fp_assertDisplayObject('$mainComponentName')")
}

That should give the general shape of the solution. Hopefully it'll be useful to some folks. Perhaps inspiration will strike once more and additional posts will elaborate on details. Please add a comment if you can suggest an improvement or idea for a following post.

1 comment:

  1. Update 25-Feb-2011: Edited the body to reflect the change observed in Facebook: the IFrame name used to include the appId.

    ReplyDelete

 

Copyright 2009-2010 John Bito. Creative Commons License
This work is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License.