8

I am trying to customize the behavior of Selenium's click command, (via user-extentions.js), by intercepting calls to doClick(locator). Basically I need to delay click actions whenever our application's "busy indicator" is being displayed.

(Now the standard answer for this kind of thing is to insert a waitFor into the script for those situations. Indeed, we currently have zillions of them throughout our scripts. I'm trying to eliminate those.)

Detecting the page element is the trivial part. The tricky part is getting the script to actually wait. My promising looking, but failed attempt looks like this:

var nativeClick = Selenium.prototype.doClick;
Selenium.prototype.doClick = function(locator) {
  this.doWaitForCondition("!selenium.browserbot.findElementOrNull('busy-indicator')", 5000);
  return nativeClick.call(this, locator);
}

The doWaitForCondition gets called before every click, but it does not wait when the condition evaluates to false. nativeClick always gets called immediately, and so no delay is introduced. I suspect that the doWaitForCondition function doesn't actually do any waiting per se, but rather establishes the conditions for it within the command execution loop. And in this case the click command is already in play, and I'm trying to run a command within a command.

Can somebody shed some light on how Selenium command execution and waitFor works, or offer suggestions on how this might be done?

Chris Noe
  • 36,411
  • 22
  • 71
  • 92

2 Answers2

5

I have finally solved this. And with an approach that is much better than trying to intercept click processing in its various forms. My refined goal is: to delay execution of script command completion when our application is "busy".

How Selenium command processing works:

Upon completion, each selenium command returns an ActionResult object, (see ActionHandler.prototype.execute). The terminationCondition attribute on this object is a function that determines when it is okay for selenium to proceed to the next command, (TestLoop.prototype.continueTestWhenConditionIsTrue). Basically, selenium repeatedly executes the condition function until it yields true. The result object it quite trivial:

function ActionResult(terminationCondition) {
  this.terminationCondition = terminationCondition;
}

Customizing it:

I want to delay execution any time myAppIsBusy() returns true. Of course all of the standard delays need to remain in place as well, like waiting for page loads, and explicit waitFor conditions as scripted. The solution is to redefine the selenium result object in my user-extensions.js, as follows:

function ActionResult(terminationCondition) {
  this.terminationCondition = function() {
    // a null terminationCondition means okay to continue
    return (!terminationCondition || terminationCondition()) && !myAppIsBusy();
  }
}

The great thing is that this is at a low enough level that it works for the IDE, as well as for RC.

Note that this does not affect Accessor or Assert command types, which return different result objects. But that should be fine, because those commands don't effect the state of the application.

Chris Noe
  • 36,411
  • 22
  • 71
  • 92
  • Can you elaborate as to how myAppIsBusy() and Selenium can talk to each other? I have a similar problem in http://stackoverflow.com/questions/14263403/how-to-use-dowaitforcondition and I'm trying to implement your solution. – brunobg Jan 11 '13 at 17:01
  • myAppIsBusy() is a function that I define in user-extensions.js. It returns true while I want selenium to wait. E.g., while our busy indicator is displayed to our user. – Chris Noe Jan 14 '13 at 02:24
  • So, suppose I want to this check of the busy indicator. myAppBusy would then be something like function myAppIsBusy() { return this.page().findElement('#wait'); }. And I have another function that I'm creating such as Selenium.prototype.doWaitAjax(). What do I have to do in doWaitAjax()? Do I just call this.doVerifyElementIsPresent()? How do I check for a timeout? – brunobg Jan 14 '13 at 18:17
  • mpAppIsBusy (or whatever you choose to call your function) should return true or false. But yes, you're basically on the right track. Selenium is still doing its regular timeout checking. – Chris Noe Jan 15 '13 at 02:26
  • Thanks for all the help, but I just can't make it work. It seems that Selenium never yields no matter what I do, so the browser does not execute the ajax success call until Selenium returns from the user extension function. I could call another function later, but this is exactly what I'm trying to avoid. If you could provide a small example, perhaps in the other question I linked, I'd be forever grateful ;) – brunobg Jan 15 '13 at 13:07
0

Well, a look at the java drivers com.thoughtworks.selenium.Wait class reveals this:

public void wait(String message, long timeoutInMilliseconds, long intervalInMilliseconds) {
    long start = System.currentTimeMillis();
    long end = start + timeoutInMilliseconds;
    while (System.currentTimeMillis() < end) {
        if (until()) return;
        try {
            Thread.sleep(intervalInMilliseconds);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    throw new WaitTimedOutException(message);
}

I am not to deep into selenium but I excpect that every waitXXX Method points to this.

So, Selenium is working with Thread.sleep(). While this might not look like an ideal solution it shows at least that you cant make it worse by using Thread.sleep() on your own if neccessary. ;-)

gevorg
  • 4,835
  • 4
  • 35
  • 52
tasel
  • 629
  • 5
  • 15
  • Yep, that works great from java, but there's no such thing as suspending an execution thread in javascript, (cause there's only one thread). I need a Selenium client-side solution here. But thanks. – Chris Noe Nov 17 '10 at 18:03