Gbru
Gbru

Reputation: 1087

WebDriver Wait '.until(ExpectedConditions.elementToBeClickable' only works when i add Thread.sleep(500) to the Code?

  1. I'm trying to click on a button which is visible on a page by using webdriver wait but webdriver is only able to click on the button once I add a Thread.sleep to the code.

  2. I have also checked the button to see whether its visible (True) before I execute my code and it in turn returns = true.

//Button Visiblity check:

List<WebElement> signOutList = driver.findElements(.xpath(".//*[starts-with(@id,'fulfillment-time-type-section-')]/div[2]//button"));
Assert.assertTrue(signOutList.size() > 0);

//The code below Doesn't click on the button

By timeDropdownButton = By.xpath(".//*[starts-with(@id,'fulfillment-time-type-section-')]/div[2]//button");
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
              .until(ExpectedConditions.elementToBeClickable(timeDropdownButton));
myDynamicElement.click();

//The code below Does click on the button:

Thread.sleep(500);
By timeDropdownButton = By.xpath(".//*[starts-with(@id,'fulfillment-time-type-section-')]/div[2]//button");
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
              .until(ExpectedConditions.elementToBeClickable(timeDropdownButton));
myDynamicElement.click();


Please note I have also tried to click the button using JS code and WebDriver actions etc

I don't know why 'Wait Clickable' only works when I combine with 'Thread.sleep'?

The button I'm trying to click

Upvotes: 3

Views: 10817

Answers (5)

Prasetyo Budi
Prasetyo Budi

Reputation: 96

have you tried using urlContains ? i facing same problem that you asked, somehow thread sleep proccesed normal

detail like below

(new WebDriverWait(driver, 50)).until(ExpectedConditions.urlContains("/completing-profile?step=skin_type"));

Upvotes: 0

Jeremiah
Jeremiah

Reputation: 1145

Try a PageFactory implementation to see if the selenium API solves the problem for you.

public class OrderMyPizzaPage {
        @FindBy(css="form[action='/menu/startyourorder']")
        WebElement startMyOrder;

        public void startOrder() {
            startMyOrder.click();
        }
    }

Great, and now if we assume that everything else has happened successfully and we're to the point where we can click on 'start your order'

 public void myExecutingMethod(WebDriver driver) {
        //previous steps occur and we've just performed interactions which should cause the 'start your order' button to be on the dom

        OrderMyPizzaPage orderPage = PageFactory.initElements(driver, OrderMyPizzaPage.class);
        orderPage.startOrder();

        //And now maybe it worked?
    }

If that doesn't work, try something a little more brute-force. Create a custom predicate for your WebDriverWait that checks for a post-click condition to be true before releasing. If it's not true, then have the predicate re-execute the click!

Because of time-zone issues I wasn't able to get the lookup for a successful post click content, so there's a missing reference in the snippet. It's flagged as FIXME

public void startOrder(WebDriver driver) {
    WebDriverWait wait = new WebDriverWait(driver, 30);
    //Throttle the polling!
    wait.pollingEvery(1500, TimeUnit.MILLISECONDS);
    wait.until(new Predicate<WebDriver>() {
        public boolean apply(WebDriver input) {
            boolean successfulClick = false;
            //FIXME:  Need to check for something that should occur after the click happens successfully
            By lookupAfterClick = By.cssSelector("");
            WebElement nextElement = ExpectedConditions.visibilityOfElementLocated(lookupAfterClick).apply(input);
            if (nextElement == null) {
                System.out.println("Not there yet, Keep trying!");
                By startOrderLookup = By.cssSelector("form[action='/menu/startyourorder']");
                WebElement startOrder =ExpectedConditions.visibilityOfElementLocated(startOrderLookup).apply(input);
                if (startOrder != null) {
                    System.out.println("Clicking Start Order!");
                    startOrder.click();                        
                } else {
                    System.out.println("Start Order isn't visible!");
                }
            } else {
                System.out.println("Hey, That worked!");
                successfulClick = true;
            }
            return successfulClick;
        }
    });        
}

Upvotes: 0

Aaron Davis
Aaron Davis

Reputation: 1731

You want to avoid Thread.sleep in tests as much as possible generally. It might not seem so important with just a few tests but there are a lot of problems inherent in their use. First off, if you have a bunch of tests the run time of the test suite can become unmanageable. Second, they are sometimes not enough. For instance, waiting 500 milliseconds might be enough with production machines in general, but if the web server is under heavy load or in a test environment it might take 750 milliseconds to be ready. Then you are dealing with non-deterministic failures. It is best to use constructs like WebDriverWait and give them a sane (but overly-generous) maximum value so that you don't have to wait longer than necessary but if it fails it means there was something seriously wrong with the environment under test.

Also the Pizza Hut site here is making heavy use of asynchronous java script and floating panels, etc. All of that requires a bit more care when using Selenium since elements need to be ready to go before interacting with them but they are often already in the DOM. This means that Selenium by default will probably find them quickly before the JavaScript has completed and they are not actually in a ready to use state. You will want to make use of ExpectedConditions as you were already trying to do. The button you have been having troubles with needs the wait for JavaScript to finish that was already suggested but it needs to not be on page load but rather right before clicking the button. Example that worked on my machine:

@Test
public void foo() {
    WebDriver driver = new FirefoxDriver();
    driver.manage()
          .window()
          .maximize();
    // Move through the page to the point you are interested in
    driver.get("https://www.pizzahut.co.uk/");
    waitForElement(driver, By.cssSelector(".hidden-xs [title='Pizza']")).click();
    waitForElement(driver, By.cssSelector("form[action='/menu/startyourorder']")).submit();
    WebElement postElement = waitForElement(driver, By.id("ajax-postcode-txt"));
    postElement.sendKeys("TS1 4AG" + Keys.ENTER);
    // Wait for the java script to finish executing
    waitForJavaScript(driver);
    // Finally you should be able to click on the link
    waitForElement(driver, By.partialLinkText("Start Your Order")).click();
    // continue on ... then quit the driver
    driver.quit();
}

private WebElement waitForElement(WebDriver driver, By locator) {
    return new WebDriverWait(driver, 10).until(ExpectedConditions.elementToBeClickable(locator));
}

private void waitForJavaScript(WebDriver driver) {
    new WebDriverWait(driver, 10).until(new Predicate<WebDriver>() {
                                            public boolean apply(WebDriver driver) {
                                                return ((JavascriptExecutor) driver).executeScript("return document.readyState")
                                                                                    .equals("complete");
                                            }
                                        }
    );
}

In a WebDriverWait the given int argument is the number of seconds it will keep trying the given check. If it gets to the set amount (10 seconds in the above example) it will throw a TimeoutException.

Upvotes: 2

Gbru
Gbru

Reputation: 1087

Following method seems to work correctly:

public void waitForPageLoad() {
    ExpectedCondition<Boolean> expectation = new
            ExpectedCondition<Boolean>() {
                public Boolean apply(WebDriver driver) {
                    return ((JavascriptExecutor) driver).executeScript("return document.readyState").toString().equals("complete");
                }
            };
    try {
        Thread.sleep(1000);
        WebDriverWait wait = new WebDriverWait(driver, 30);
        wait.until(expectation);
    } catch (Throwable error) {
        Assert.fail("Timeout waiting for Page Load Request to complete.");
    }
}

Upvotes: 0

Parker Beck
Parker Beck

Reputation: 195

There is still probably active Javascript being executed on the page. In the first split second it might be modifying it in a way that the ExpectedCondition can't tell. When I have problems with unexpected behavior, I find waiting for the page to load (javascript scripts included) usually solves most of the problems.

To wait for the page to load, you can call some Javascript of your one to check the document's readyState. Try waiting for the page to load and THEN waiting for the element to be clickable.

A quick search returns this (code taken from here):

wait.until( new Predicate<WebDriver>() {
            public boolean apply(WebDriver driver) {
                return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
            }
        }
    );

Upvotes: 0

Related Questions