Search code examples
seleniumpage-factory

Selenium fluent wait and page factories


So, I've been rattling my head with this challenge, so far I'm not winning. I'd highly appreciate if anyone could assist me with this. Details as follows.

Below is the code Example: 1,

1.In example 1, the incorrect xpath is this "//input[@id='identifierIdd']"

2.This is deliberate just to examine fluent wait. As expected after 1 minute test exited and with exception org.openqa.selenium.NoSuchElementException:

    import com.google.common.base.Function;
    import org.openqa.selenium.By;
    import org.openqa.selenium.NoSuchElementException;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.support.ui.FluentWait;
    import org.openqa.selenium.support.ui.Wait;
    import static java.util.concurrent.TimeUnit.MINUTES;
    import static java.util.concurrent.TimeUnit.SECONDS;

    public class Tester01 {

    public static void main(String[] args) {
    WebDriver webDriver = new ChromeDriver();
    webDriver.get("https://accounts.google.com/
      signin/v2/identifier? 
      continue=https%3A%2F%2Fmail.google.com%2Fmail%2F
      &service=mail&sacu=1&rip=1&flowName=GlifWebSignIn
      &flowEntry=ServiceLogin");

    webDriver.manage().window().maximize();
    WebElement webElement;

    Wait<WebDriver> fluentWaiter = new FluentWait<WebDriver> 
    (webDriver).withTimeout(1, MINUTES)
           .pollingEvery(5, SECONDS)
           .withMessage("element couldn't be found after 1 minutes")
           .ignoring(NoSuchElementException.class);


    webElement = fluentWaiter.until(new Function<WebDriver, 
    WebElement>()
    {
       public WebElement apply(WebDriver driver) {
          return driver.findElement(By.xpath
            ("//input[@id='identifierIdd']"));
        }
    });

    webElement.sendKeys("testemail.com");
    }
    }
  1. So, I've decided to experiment with fluentwait & pageFactory unfortunately I couldn't figure out how to achieve the below line from Tester01 class with pageFactory.

return driver.findElement(By.xpath("//input[@id='identifierIdd']"));

  1. Details for fluentwait & pageFactory experimentation. Below is the code Example: 2,

    import org.openqa.selenium.By;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.How;
    
    public class LocatorTest{
    
    @FindBy(how = How.XPATH, using="//input[@id='identifierIdd']")
    public WebElement elementTest;
    
    }
    

And Tester01 class with LocatorTest class

    public class Tester01 {

    public static void main(String[] args) {
    WebDriver webDriver = new ChromeDriver();
    webDriver.get("https://accounts.google.com/signin/v2/identifier?                        
       continue=https%3A%2F%2Fmail.google.
       com%2Fmail%2F&service=mail&sacu=1&rip=1
       &flowName=GlifWebSignIn&flowEntry=ServiceLogin");

    webDriver.manage().window().maximize();
    WebElement webElement;

    Wait<WebDriver> fluentWaiter = new FluentWait<WebDriver>(webDriver)
            .withTimeout(1, MINUTES)
            .pollingEvery(5, SECONDS)
            .withMessage("element couldn't be found after 1 minutes")
            .ignoring(NoSuchElementException.class);

    LocatorTest locatorTest = new LocatorTest();
    PageFactory.initElements(webDriver, locatorTest);

    webElement = fluentWaiter.until
    (new Function<WebDriver, WebElement>()
    {
        public WebElement apply(WebDriver driver) {
            return locatorTest.elementTest;
        }
    });

    webElement.sendKeys("testemail.com");
        }

    }

So Tester01 class with pagefactory doesn't wait fluently for 1 min, test fails immediately with org.openqa.selenium.NoSuchElementException:

I think I know what the issue is however doesn't really know how to overcome this issue,

This is my explanation of the issue, in Example: 2 Tester01 class the return statement in fluentWaiter instance doesn't use the appropriate driver instance.

This is the line I'm talking about return locatorTest.elementTest; because I think the method apply() accepts an Webdriver instance however the line return locatorTest.elementTest; doesn't utilise the driver instance.

Is my thinking correct? could someone please assist me with this issue please? Or propose an alternative solution please?

Please let me know if any of the above doesn't make sense or need more info.

Thanks in advance Niro


Solution

  • PageFactory.initElements() will create Proxy for all the WebElement and List<WebElement> fields. And also setup the locator strategies that are passed in FindBy annotation. No location of elements is performed. It is a lazy initialization. The actual location will be done when commands like click(), sendkeys() etc are sent to the webelement.

    Now in first case, you search for the actual element

    webElement = fluentWaiter.until(new Function<WebDriver, 
        WebElement>()
        {
           public WebElement apply(WebDriver driver) {
              return driver.findElement(By.xpath
                ("//input[@id='identifierIdd']"));
            }
        });
    

    In the second case, you are just making a java call to a page object which returns you back the Proxy object. No selenium business is involved.

    webElement = fluentWaiter.until
        (new Function<WebDriver, WebElement>()
        {
            public WebElement apply(WebDriver driver) {
                return locatorTest.elementTest;
            }
        });
    

    But when this code is run - webElement.sendKeys("testemail.com");, actual element location is done and it fails. There is no wait involved here. The PageFactory was initialized with the webDriver instance.

    Look at [ExpectedConditions] class which you can use along with the FluentWait or the specialized [WebDriverWait]. Use a method like visibilityOf(WebElement) from ExpectedConditions which return the ExpectedCondition<WebElement>.

    Or you can even look at a LocatorFactory which waits for a certain time period while searching for an element. AjaxElementLocatorFactory and AjaxElementLocator. Though this will be an overkill as these are meant for Ajax cases rather than a wrong locator.