Search code examples
c#seleniumselenium-webdriverwebdriverwaitpage-factory

Does fluent wait work with Page Factory Web Elements in Selenium?


I am thinking that page factory web elements don't work with fluent wait. I have the following case where I am waiting for the presense of an element and it doesn't seem to work when using page factory web elements

public class HomePage
{
    private IWebDriver driver;

    [FindsBy(How = How.Id, Using = "submitBtn")]
    public IWebElement SubmitBtn { get; private set; }

    public HomePage(IWebDriver driver)
    {
        this.driver = driver;
        PageFactory.InitElements(driver, this);
    }
}

Test class using Page Factory

public class HomePageTests : BaseTest
{
    [Test]
    public void SomeTest()
    {   
        base.Driver = base.InitDriver();
        HomePage homePage = new(Driver);
       
        WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30))
        {
            PollingInterval = TimeSpan.FromSeconds(5),
        };
        wait.IgnoreExceptionTypes(typeof(NoSuchElementException));  
        
        wait.Until(ExpectedConditions.ElementToBeClickable(homePage.SubmitBtn()));
        homePage.SubmitBtn.Click();
    }
}

Test class using By locator

public class HomePageTests : BaseTest
{
    [Test]
    public void SomeTest()
    {   
        base.Driver = base.InitDriver();
        HomePage homePage = new(Driver);
       
        WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30))
        {
            PollingInterval = TimeSpan.FromSeconds(5),
        };
        wait.IgnoreExceptionTypes(typeof(NoSuchElementException));            
        
        var submitBtn = wait.Until(ExpectedConditions.ElementToBeClickable(By.Id("submitBtn"));
        submitBtn.Click();
    }
}

It seems to work on not using the page factory web element but passing a locator. I've been testing it consistenty for a period of time and wondering if page factory web elements are initalized different to using by locator (I am aware of the lazy load but could that be part of the reason?).

EDIT The exception I get is NoSuchElementException


Solution

  • Finding elements using the PageFactory annotations immediately tries to find the element the moment you call the property. Depending on how you configured the web driver, it will return the IWebElement object or throw a NoSuchElementException immediately.

    You can configure the web driver with implicit waits, but that only works to prevent NoSuchElementExceptions the first time you try to find the element. It does not ensure Selenium can interact with that element. Nor does it ensure the element is still attached to the HTML document the next time you need to access a property or call a method on the IWebElement object.

    Instead, use the expression-bodied member syntax for properties that return an IWebElement or collection of IWebElement objects. Lastly, when you need to wait for something, use an explicit wait.

    You can combine all of these requirements into one property, making your page model simpler:

    public class HomePage
    {
        private readonly IWebDriver driver;
        private readnly WebDriverWait wait;
    
        private IWebElement SubmitButton => wait.Until(ExpectedConditions.ElementToBeClickable(By.Id("submitBtn")));
    
        public HomePage(IWebDriver driver)
        {
            this.driver = driver;
            wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
        }
    
        public PageAfterSubmit Submit()
        {
            SubmitButton.Click();
    
            return new PageAfterSubmit(driver);
        }
    }
    

    Some notes:

    1. The page model pattern should expose methods that encapsulate some user action. Do not expose IWebElement properties. This breaks the abstraction provided by the page model.

    2. When a user action should change the page, consider returning an instance of a different page model that represents the updated page.

    3. A method named "Submit" is very generic. It is not a meaningful name for the use case it models. Consider changing the name of this method to match the use case. The button might be named "submitBtn", but what use case does this represent? That should guide you to a better name.