lmcphers
lmcphers

Reputation: 468

Selenium Testing For Element Existence Failure

Back again with another Selenium question!...

Here is what I have so far

public static void mobileAssessmentPage() {
    List<WebElement> elements = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER);
    List<WebElement> pageTitle;

    for (WebElement element : elements) element.click();

    SeleniumCommands.waitClickById(ASSESSMENTNEXTPAGEBUTTON);
    try { Thread.sleep(500); } catch (InterruptedException ie) { ie.getMessage(); }
    pageTitle = SeleniumCommands.findElementsByXpath(PAGETITLE);
    try { Thread.sleep(500); } catch (InterruptedException ie) { ie.getMessage(); }
    System.out.println(pageTitle.get(0));
    if (pageTitle.size() > 0) {
        if (pageTitle.get(0).getText().equals("Assessment"))
            mobileAssessmentPage();
    }
}

For some reason, on the page where I know it should return an empty list (since the page title should not be found), the two exceptions I keep getting are "StaleElementReferenceException" (or something like that) or "Element is no longer attached to the DOM". Is there a better way to go about this?

I am trying to make the method dynamic by calling itself for every page where it is needed; unfortunately, the Selenium methods don't make this convenient since they return an error if they can't find an element instead of a Boolean or -1 or even a null WebElement or something logical.

Upvotes: 0

Views: 111

Answers (2)

Aleksandr Andrienko
Aleksandr Andrienko

Reputation: 19

First of all you should not use Thread sleep! This bad practice for production tests. You should also use page-object pattern (it's very useful).

About your actual question: Elements need some time for attaching to parent elements. For example: when a developer used animation, or an application generates difficult user interfaces, then this process need some time for rendering. Otherwise the element will be attached whenever the application completes some task(s). You just need to wait when element will be attached. Webdriver has special classes and methods for this cases:

 WebElement webElement = new WebDriverWait(driver(), 3).until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("some css celector"))); //this code wait untill element will be attached about 3 seconds
webElement.click() //now you clicking on element

You can use many useful methods in the ExpectedConditions class. Remember, you should use Thread.sleep() very seldom, and only if you don't have another option. Good luck.

Upvotes: 1

nberger
nberger

Reputation: 3659

From the selenium docs:

A stale element reference exception is thrown in one of two cases, the first being more common than the second:

The element has been deleted entirely. The element is no longer attached to the DOM.

The latter include cases where an element is deleted and replaced by a new one with almost the same attributes and DOM location. Any JS library might be doing that in reaction to clicking one of the elements, so the rest of the elements get "stale".

How can you avoid this?

One solution that I used is to not keep a reference to each of the elements but find each of the single elements just before clicking it. As the elements share a selector, you can first get the count and then get them by subindex. Adapting your example:

public static void mobileAssessmentPage() {
    int q = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER).count();

    List<WebElement> pageTitle;

    for (int i = 0; i < q; i++) {
        WebElement element = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER)[i];
        element.click();
    }

    SeleniumCommands.waitClickById(ASSESSMENTNEXTPAGEBUTTON);
    try { Thread.sleep(500); } catch (InterruptedException ie) { ie.getMessage(); }
    pageTitle = SeleniumCommands.findElementsByXpath(PAGETITLE);
    try { Thread.sleep(500); } catch (InterruptedException ie) { ie.getMessage(); }
    System.out.println(pageTitle.get(0));
    if (pageTitle.size() > 0)
        if (pageTitle.get(0).getText().equals("Assessment"))
            mobileAssessmentPage();
}

If that's not enough (and many times it's not), you might want to use some waiting or retry in that for, something like:

    for (int i = 0; i < q; i++) {            
        WebElement element = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER)[i];
        element.click();
        Thread.sleep(200);
    }

or:

    for (int i = 0; i < q; i++) {            
        try {
            WebElement element = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER)[i];
            element.click();
        } catch (StaleElementReferenceException e) {
            Thread.sleep(200); // optional, but just to be safe...
            WebElement element = SeleniumCommands.findElementsByCss(ASSESSMENTANSWER)[i];
            element.click();
        }            
    }

Upvotes: 1

Related Questions