2

I am using Webdriver in Java and I encountered an issue repeatedly that I can't find a proper solution yet.

It is to do with doing actions on a page that will cause this page DOM to change (for example, Javascript lightbox), then my JUnit test is expecting new elements after the DOM change but my test is getting the old DOM element.

To give you an example, I have a scenario as below.

First of all click “Add item” button in the below image and the light box appears: enter image description here

Then fill in all the item details and click "Add & Close". You will see the screen below: enter image description here

Notice that now there is an info message Your item ... has been added.

Now I put keywords in the Search text box and hit enter and the info message will be changed to below: enter image description here

In my JUnit test, the flow is like below:

....
    itemDetailsPage.clickAddAndClose();
    itemDetailsPage.searchItemBy("Electricity");
    assertEquals("Your search for 'electricity' returned 2 results.",
        itemDetailsPage.getInfoMsg());
....

Now this test is not very robust, because if the network is slow, most of the times, getInfoMsg() will return the previous info message Your item ... has been added instead of the latest info message, which causes the test to fail. Just a side note that these two info message have share the same html element id.

The solution I am trying to implement here are:

  1. add explicit wait in clickAddAndClose() So it looks something like:

    public void clickAddAndClose() {
        ...
        clickWhenReady(driver, By.id(addAndCloseButtonId));
        ...
        waitForElementByLocator(driver,By.id(itemInfoMsgId),10);
    }
    

    The second wait proves to be useless because, itemInfoMsgId already exist when the user added the item from the add item lightbox.

  2. add waitForPageLoaded() method at the end of clickAddAndClose() to try to wait for the page to finish reloading. The generic method for waitForPageLoaded() below:

    public void waitForPageLoaded(WebDriver driver) {
        ExpectedCondition<Boolean> expectation = new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver driver) {
                return ((JavascriptExecutor) driver).executeScript(
                "return document.readyState").equals("complete");
            }
        };
        Wait<WebDriver> wait = new WebDriverWait(driver, 30);
        try {
            wait.until(expectation);
        } catch (Throwable error) {
            assertFalse("Timeout waiting for Page Load Request to complete.",
            true);
        }
    }
    

I am expect at the end of clickAddAndClose(), it will see this page is still being updated so it will wait until the info message has been updated. But this does not seem to work either.

That leaves me to the last choice will is to add a thread sleep at the end of clickAddAndClose(). I want to avoid using it.

Is there a generic way of solving this kind of problem? How do I detect that the page DOM is still changing and tell Webdriver to wait until it finishes refreshing?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
nzsquall
  • 395
  • 1
  • 10
  • 27
  • Try to use [stalenessOf](http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html#stalenessOf(org.openqa.selenium.WebElement)) method from expected conditions. It returns True, when the element is no longer present in DOM. After that use you second Wait statement – Furious Duck Dec 02 '13 at 09:30

2 Answers2

1

Waiting for the page to be loaded won't work if (as it seems to be the case) your page is being modified by AJAX operations.

Instead of waiting for the page to load, wait for the condition you are testing to become true. This way, you give the AJAX operation time to execute and if your there is a problem you will get an error when the time out occurs.

I usually use the Python bindings for Selenium and it has been quite a while since I wrote Java code but I believe it would look something like this, with X being replaced with a type appropriate for the itemDetailsPage object:

new FluentWait<X>(itemDetailsPage)
    .until(new Function<X, Boolean>() {
        public Boolean apply(X itemDetailsPage) {
            return "Your search for 'electricity' returned 2 results." == itemDetailsPage.getInfoMsg();
        };
    });
Louis
  • 146,715
  • 28
  • 274
  • 320
  • Hi Louis, thanks very much for your solution. Yes by the look of it, this can work, but it is not an ideal solution. The reason for it is because this is I don't think hard coded in message in the wait is an ideal solution because this is only one of the many scenarios. And in the test cases, I don't put any wait at all so the only places I can put it is in the page object. – nzsquall Dec 02 '13 at 20:38
  • There's no need to have the expected string be hardcoded in the ``apply`` method. – Louis Dec 02 '13 at 20:47
0

Seems like you need to wait until ajax has finished its job. In a similar situation I've used a method similar to waitForJQueryProcessing described here. Take a look, it might help.

Illia
  • 110
  • 1
  • 1
  • 8
  • I tried your `waitForJQueryProcessing` method and have error as below: org.openqa.selenium.WebDriverException: jQuery is not defined Command duration or timeout: 15 milliseconds – nzsquall Dec 05 '13 at 20:51
  • [This question](http://stackoverflow.com/questions/17208935/org-openqa-selenium-webdriverexception-referenceerror-jquery-is-not-defined) may be relevant. See answer by Ardesco there. – Illia Dec 06 '13 at 08:12