5

Selenium interaction with DOM seems extremely slow while doing couple of things in every page instantiation. Throughout the site we have visible spinner that indicates any outstanding API calls resolved or not. In summary I have three methods that make sure the stability of page before performing any action.

  1. Check for the DOM ready state
  2. Check for any outstanding JQuery calls
  3. Check for loading spinners

All of these three are done as a part of the page object instantiation with following methods.

    public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver){
    final WebDriverWait wait = new WebDriverWait(driver, timeout);
    
    wait.until(waitForDomReadyState());
    wait.until(waitForjQueryToBeInactive());
    List<WebElement> elements = wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(spinnersLoacator));
    
    for(WebElement element: elements){
        wait.until(invisibilityOfElementLocated(element));  
     }
    }

    private static ExpectedCondition<Boolean> waitForDomReadyState(){

        return new ExpectedCondition<Boolean>() {

            @Override
            public Boolean apply(WebDriver d){

                return ( ((JavascriptExecutor) d).executeScript("return document.readyState;").equals("complete"));
            }
        };
    }


    private static ExpectedCondition<Boolean> waitForjQueryToBeInactive(){

        return new ExpectedCondition<Boolean>() {

            @Override
            public Boolean apply(WebDriver d){

                return (Boolean) ( ((JavascriptExecutor) d).executeScript("return jQuery.active == 0;"));
            }
        };
    }

    public static ExpectedCondition<Boolean> invisibilityOfElementLocated(final WebElement element){

        return new ExpectedCondition<Boolean>() {

            @Override
            public Boolean apply(WebDriver driver){

                try{
                    return !element.isDisplayed();
                } catch (NoSuchElementException | StaleElementReferenceException e){
                    // Returns true because the element is not present in DOM.
                    // The
                    // try block checks if the element is present but is
                    // invisible or stale
                    return true;
                }
            }
        };
    }

Taking an example of a page(say patient page) which has good number of API calls and fetches a lot of data. For a initial class instantiation it takes about 17s(log below). My Selenium knowledge says, the subsequent page instantiation should not take same or more time to check DOM ready state, or JQuery call or spinner waits since there is nothing changing at all. However, every time new page instantiate I see it takes same amount of time taken to check all these three. What's happening there? Does Selenium actually tries to interact with Server every time I do these or just interaction with the client is slow for some reason? If so, what could be the possible answer?

Console log

==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]

==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]

==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]

==== Browser on [[[Patient]]]

==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]

==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]

Environment:

  1. Selenium 2.48
  2. Firefox 38

I also tried with Selenium 2.52 and firefox 44 with same result

Community
  • 1
  • 1
Saifur
  • 16,081
  • 6
  • 49
  • 73
  • 1
    Usually when my timer always return the same results I check what's wrong withe the timer. – Guy Feb 17 '16 at 05:37
  • @guy Not an issue with the timer because it does not necessarily return same time all the time it's 17-18 range – Saifur Feb 17 '16 at 14:31
  • Have you tried that in Chrome - same problem? Also, have you isolated the problem - it is the jquery that reports being active after 17 seconds or the spinners that remain visible for 17 seconds? Thanks! – alecxe Mar 01 '16 at 16:13
  • @alecxe I do not see same behavior on Chrome. Also, I tried disabling jquery not specific to jquery either. – Saifur Mar 04 '16 at 07:34
  • @Saifur good, this means using Chrome is a workaround solution in this case, right? – alecxe Mar 04 '16 at 14:59
  • @alecxe you are right. But unfortunately business wants to use Firefox first – Saifur Mar 04 '16 at 16:43

2 Answers2

6

Selenium handles all the waiting on the client side with a request sent to the server for every evaluation until the condition is met. It can quickly degenerate in case of high latency, especially if there are a lot of calls. Moreover some evaluations require a script injection which doesn't help either.

So the best way to improve the performance in your case would be to use a single asynchronous JavaScript call:

public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver) {
  const String JS_WAIT_SPINNERS = 
      "var callback = arguments[0]; " +
      "(function fn(){ " +
      "  if (document.readyState == 'complete' && jQuery.active == 0) { " +
      "    var elts = $('.spinners'); " +
      "    if (elts.length == 8 && !elts.is(':visible')) " +
      "      return callback(); " +
      "  } " +
      "  setTimeout(fn, 60); " +
      "})();";

   ((JavascriptExecutor)driver).executeAsyncScript(JS_WAIT_SPINNERS);
}

To initialize the timeout:

driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);
Florent B.
  • 41,537
  • 7
  • 86
  • 101
  • Seems like a very positive technique to me. But, why would you use 60 in `setTimeout()`? – Saifur Mar 08 '16 at 00:39
  • I think that checking the state every 60ms is enough. Polling the DOM too often would be counter productive. You can lower the value, but I wouldn't go under 10ms. – Florent B. Mar 08 '16 at 02:30
  • Sounds good. I will award you the bounty. Since, it provides the closest solution to my problem. But, I would accept djangofan's answer since his point of view is also correct. – Saifur Mar 08 '16 at 04:46
3

Your test seems to be all non-native calls and so Firefox should work for you but I am surprised that the Firefox native call to driver.navigate() even worked for you to get to the inital page if you were using 44 and 48. It is well known that 31.6.0 was the last supported native Firefox version. So, I would say you should use Chrome until you figure this out.

But, to answer your thing about slowness. The way you wrote your code, you are highly dependent on jQuery and I would imagine your are having an issue with your calls to jQuery code being delayed, which propagates out to your Selenium test, and further impacted by the fact that your looping through multiple spinners. One thing I have noticed before is that if a page is busy running ajax calls, then your Selenium calls with JavascriptExecutor might have to wait in line for those to give up bits of processor time.

What would I do differently? Well, I would write my spinner waits to operate on the DOM instead of calling JavascriptExecutors to jQuery. Maybe in your case, this is not an option, but I think a well thought out plan can improve the efficiency of your page ready workflow.

djangofan
  • 28,471
  • 61
  • 196
  • 289
  • I have tried disabling the Jquery and it does not help. I ended up having almost same result. Secondly, how the native and non-native calls of firefox would make difference in this case? Can you please explain? Any other option I can try without any issue. Ideas are welcome – Saifur Mar 04 '16 at 07:37