Sunday, February 8, 2015

Waiting for Oracle ADF Partial Page Rendering in Selenium tests

One of the main reasons people fail to use Selenium (or any other tool) for automated web testing with Oracle ADF is timing issues. The test automation tool typically wants to execute its actions as quickly as possible. This can be a challenge in dynamic applications that load parts of the pages on demand or in response to user interactions. This is what ADF typically does with partial page rendering requests.

For example, the test could "click" on a tab in a af:pannelTabbed component. The content of the new tab will be retrieved with a partial page rendering request. If the test automation tool would continue immediately after "clicking" the tab and try to interact with elements on the new tab it would be too early and fail. Most automated testing tools have features to wait for new page DOM elements to appear before interacting with them. But even this can be too soon. When the response to the partial page rendering request is processed by the web browser client it adds the new elements to the page and then binds all sorts of javascript events and other goodies to them. Most automated testing tools will not wait for this to complete and will still fail to interact with the new elements.

One quick and dirty solution is to introduce sleep statements in your test and just wait a second or two for the request to complete. This will unnecessarily slow down your tests if the partial request is completed in less than your sleep time and will still break your test whenever the page load will take longer then normal and thus longer than your sleep time.

A much better way is to actually tell Selenium (or your other testing tool of choice) when the partial page rendering request is fully completed and the browser is ready for its next command. ADF includes a javascript method that does just that; AdfPage.isSynchronizedWithServer.

This has been available in older ADF versions as well. In version 12c Oracle even introduced AdfDhtmlPage.whyIsNotSynchronizedWithServer which tells you why the client is not ready yet. Please not this is part of the Oracle internal AdfDhtmlPage class and therefor not part of the public javascript API which means it might change or disappear in future versions.

Selenium WebDriver has the notion of explicit waits where you can instruct Selenium to wait for a certain condition. I figured it would be nice if we could just hook into that mechanism. As part of my effort to automate ADF testing I've created a subclass of org.openqa.selenium.support.ui.ExpectedCondition called ClientSynchedWithServer:
public class ClientSynchedWithServer implements ExpectedCondition<Boolean> {
    // return false if AdfPage object and functions do not exist
    // if they do exist return true if page is fully loaded and ready or reason why this is not completed yet
    String js =
        "return typeof AdfPage !== 'undefined' && " + 
        "typeof AdfPage.PAGE !== 'undefined' && " +
        "typeof AdfPage.PAGE.isSynchronizedWithServer === 'function' && " +
        "(AdfPage.PAGE.isSynchronizedWithServer() || " +
        "(typeof AdfPage.PAGE.whyIsNotSynchronizedWithServer === 'function' && " +
        "AdfPage.PAGE.whyIsNotSynchronizedWithServer()))";

    @Override
    public Boolean apply(WebDriver driver) {
        JavascriptExecutor jsDriver = (JavascriptExecutor) driver;
        Object result = jsDriver.executeScript(js);
        System.out.println("client ready: " + result);
        return Boolean.TRUE.equals(result);
    }
}

The javascript tries to be as careful as possible not to throw an exception when not on an ADF page by first checking if AdfPage, AdfPage.PAGE and AdfPage.PAGE.isSynchronizedWithServer are available. If so, it will execute AdfPage.PAGE.isSynchronizedWithServer() to see if the client is completely finished processing all events. If this is not the case it will check if the whyIsNotSynchronizedWithServer function is available and will invoke it if it is. This would return the reason why the client is not finished yet. If the whyIsNotSynchronizedWithServer function is not available it would just return the false result from isSynchronizedWithServer.
In the end the javascript function will only return true if isSynchronizedWithServer returned true, otherwise it might return the reason why the page is not finished or any other non-true value.

The apply method from this ExpectedCondition will invoke the javascript and would print the reason why the page is not finished yet. If you don't want any System.out.println in your tests you could just remove this. The important part is that it checks if the javascript returned true. If it did, the apply method will also return true and Selenium would know the condition has been met and it no longer needs to wait.

Using this class to wait for PPR after interacting with the page is similar to any explicit wait in Selenium WebDriver:
public void test() throws Exception {
    FirefoxProfile profile = new FirefoxProfile();
    profile.setEnableNativeEvents(true);
    profile.setPreference("app.update.enabled", false);
    WebDriver driver = new FirefoxDriver(profile);
    System.out.println("load demo page...");
    driver.get("http://jdevadf.oracle.com/adf-richclient-demo");
    System.out.println("wait for completion...");
    new WebDriverWait(driver, 10).until(new ClientSynchedWithServer());
    System.out.println("click search button...");
    driver.findElement(By.id("tmplt:gTools:glryFind:doFind")).click();
    System.out.println("wait for completion...");
    new WebDriverWait(driver, 10).until(new ClientSynchedWithServer());
    driver.quit();
}

This will setup a new Selenium session in lines 2 through 5. It will then navigate to the Oracle Rich Client Demo site. This already includes quite some client side javascript processing, so we need to wait for this to complete in line 9. We then click on the search button and again wait for that to complete in line 13. Both waits use a timeout of 10 seconds. If the condition is not met within that timeout Selenium will throw an exception.
Without the new waits this script would fail as it would try to click the search button before the page has completely rendered and attached all of its javascript listeners. With the new waits you can see the results in the console and why the page wasn't ready yet in lines 3-5 and 9-10:
.load demo page...
wait for completion...
client ready: WAITING_FOR_USER_INPUT_PHASE
client ready: WAITING_FOR_USER_INPUT_PHASE
client ready: Event queue is not empty
client ready: true
click search button...
wait for completion...
client ready: DTS is not ready
client ready: WAITING_FOR_USER_INPUT_PHASE
client ready: true

Time: 9.756

OK (1 test)

This post is part of a series on how to get Selenium to work with Oracle ADF.