Sunday, December 16, 2012

Waiting for GWT RPCs in WebDriver Tests

When writing WebDriver tests for GWT applications, we often find ourselves writing WebDriverWait's to synchronize the tests with asynchronous GWT RPC's. These waits are often based on some DOM-observable result of the RPC completing -- for example, waiting until a label is changed by code in the onSuccess handler.

This has a couple of drawbacks. First, we would prefer to assert on the results of the RPC, not rely on them to control our tests. An assertion failure is preferable to a wait timeout when the onSuccess handler does something incorrectly. Second, if the behavior of RPC handling changes, our wait conditions will break and have to be maintained.

We would rather attach our wait condition to the RPC itself, rather than side-effects. Unfortunately, GWT's RPC mechanism does not build in anything for this, and we have to do it ourselves. In the remainder of this post, I will outline a simple (perhaps simplistic) way to do this.

The overall approach is to maintain a hidden field as the RPC goes out and comes back. In the .ui.xml:

<g:Hidden ui:field="ready" name="ready"/>

And the UiBinder declaration:

@UiField Hidden ready;

We'll create a helper method that updates this field based on whether we are "ready" or "busy" running an RPC:

public void setReady(boolean isReady) {
  if (isReady) {
    ready.setValue("ready");
  } else {
    ready.setValue("busy");
  }
}

Then our RPC call will follow this pattern:

public void onAction() {
  view.setReady(false);
  service.getData(new AsyncCallback<String>() {
    @Override
    public void onFailure(Throwable caught) {
      // Show failure message...
      setReady(true);
    }

    @Override
    public void onSuccess(String result) {
      // Show the result...
      setReady(true);
    }
  });  
}

Now in our WebDriver test, we can write a WebDriverWait like this:

WebDriverWait wait = new WebDriverWait(driver, 5000);
wait.until(ExpectedConditions.textToBePresentInElementValue(By.id("ready"), "ready");

There are some obvious drawbacks to this simplistic approach -- for example, it is not very extensible to an environment with multiple or batched RPC's. In a future post, I'll take on some of those challenges.

No comments: