Search code examples
javaseleniumselenium-webdrivergroovypage-factory

Selenium How to wait multiple times for the same WebElement with different text


I'm attempting to wait for the WebElement to change from blank,to message 1, then message 2. The problem is I find the first message everytime, but i can't ever seem to wait for the second(it timesout looking for the text)

I've tried having the wait objects separate that didnt work. Ive tried a few of the expected condition methods(textToBePresent*), which after some reading (i found about the refreshed EC) to no avail.

@FindBy(xpath="//p[@class='statusText']")
WebElement statusMsg
public WebElement statusMsg(){

    String msg1="Logging in, please wait."
    String msg2="Login successful, please wait."
    String msg3="Login attempt exception, error code: "
    if(statusMsg.getText().contains(msg3)){
        log.error(statusMsg.getText())
        log.error("Something happened in the frontend")
        Assert.fail(statusMsg.getText())
    }else{
        log.info(statusMsg.getText())
    }
    WebDriverWait wait = new WebDriverWait(driver,45)
    wait.until(ExpectedConditions.textToBe(By.xpath("//p[@class='statusText']"), msg1))
    if(statusMsg.getText().contains(msg3)){
        log.error(statusMsg.getText())
        log.error("Something happened in the backend")
        Assert.fail(statusMsg.getText())
    }else{
        log.info(statusMsg.getText())
    }
    wait.until(ExpectedConditions.refreshed(ExpectedConditions.textToBe(By.xpath("//p[@class='statusText']"), msg2)))
    log.info("Found: "+msg2)
    return statusMsg
}

The result is testNG fails my test saying:

org.openqa.selenium.TimeoutException: Expected condition failed: waiting for condition (element found by By.xpath: //p[@class='statusText'] to have text "Login successful, please wait.". Current text: "Logging in, please wait.")

Yet I can see the msg2 while the test is running. Does this have to do because I've already initialised the page objects via PageFactory.initElements(driver, this)?


Solution

  • Here is code you can use to check error and success messages sequencenly. Logic:

    1. Put expected messages to the expectedMessages list
    2. Use Fluent Wait to check for custom conditions
    3. Set polling every 50 milliseconds to able to catch messages
    4. Check if status message element is visible, if so get text
    5. Fail if actual status message is "error"
    6. Check actual status message contains first text in the expectedMessages, if so remove first text from expectedMessages
    7. Repeat step 6 until there's no items left if expectedMessages


    WebDriverWait wait = new WebDriverWait(driver, 20);
    
    String errorMessage = "Login attempt exception, error code:";
    List<String> expectedMessages = Arrays.asList(
            "Logging in, please wait.",
            "Login successful, please wait.");
    
    wait.pollingEvery(Duration.ofMillis(50))
            .withMessage(String.format("Expecting %s login messages", expectedMessages))
            .until(d -> {
                if (!d.findElement(By.xpath("//p[@class='statusText']")).isDisplayed())
                        return false;
    
                String actualMessage = d.findElement(By.xpath("//p[@class='statusText']")).getText();
                if (actualMessage.contains(errorMessage))
                    Assert.fail(actualMessage);
    
                String expectedMessage = expectedMessages.get(0);
                if (expectedMessage.contains(actualMessage)) {
                    log.info(String.format("Message \"%s\" found", actualMessage));
                    expectedMessages.remove(expectedMessage);
                }
                return expectedMessages.size() == 0;
            });
    

    Second solution is to get all messages and after check.

    Use code below to get all messages and check what you get from the website:

    JavascriptExecutor js = (JavascriptExecutor) driver;
    List<String> actualMessages = new ArrayList<>();
    for (int i = 0; i < 30000; i++) {
        actualMessages.addAll(
                (ArrayList<String>) js.executeScript("return [...document.querySelectorAll('p.statusText')].map(e=>{return e.textContent})")
        );
    
        Thread.sleep(10);
    }
    // debug here to check message collected
    System.out.println(actualMessages);