9

I am building a Page Object Model in Selenium WebDriver for C#, using the PageFactory.

Unfortunately, I have discovered that the FindsByAttribute will not initialize a SelectElement (HTML <select> tag / dropdown menu). I've happened upon or come up with a few ideas to work around it so far, but none of them is ideal:

  1. PageFactory and FindsByAttribute are sealed, so I can't force it to by just inheriting those.
  2. Manually instantiating a SelectElement from an IWebElement in each method is rather messy and duplicative. It also ignores the apparent built-in wait in PageFactory and throws NoSuchElementExceptions unless I add a wait every time I do this -- which would require repeating the locator all over the place, defeating (part of) the purpose of the POM.
  3. Wrapping each IWebElement property with a SelectElement property is less messy, but still has the same waiting problem as above.

The best option so far is #3, and writing a wrapper for SelectElement that just adds a wait to every method. While this solution will work, it will bulk up the code of each page a lot, as instead of this (hypothetical) pretty code:

[FindsBy(How = How.Id, Using = "MonthDropdown")]
public SelectElement MonthDropdown;

I'm stuck with a wrapper wrapper (something I'd rather avoid), and:

[FindsBy(How = How.Id, Using = "MonthDropdown")]
private IWebElement _monthDropdown;
public Selector MonthDropdown
{
    get { return new Selector(MonthDropdown, Wait); }
}

With Selector being the SelectElement wrapper, that also has to take in the IWait<IWebDriver> so it can wait, and instantiating a new Selector every time I access it.

Is there a better way of doing this?

EDIT: Sleepily put in wrong access modifiers. Fixed. Thanks, @JimEvans.

MartinRosenberg
  • 189
  • 2
  • 10

1 Answers1

15

First, there's no "built-in wait" in the .NET PageFactory implementation. You can easily specify one in the call to InitElements (more on that in a bit). At present, the best option for you would be your option 3, though I wouldn't expose the IWebElement member; I'd make it private, since the PageFactory can enumerate over private members just as easily as public ones. So your page object would look like this:

[FindsBy(How = How.Id, Using = "MonthDropdown")]
private IWebElement dropDown;
public SelectElement MonthDropdownElement
{
    get { return new SelectElement(dropdown); }
}

How do you get the actual IWebElement when you need it? Since SelectElement implements IWrappedElement, you can simply call the WrappedElement property if you need access to the methods and properties of the element provided by the IWebElement interface.

Recent versions of the .NET bindings have restructured the PageFactory to be more extensible. To add the "built-in wait" you desire, you could do the following:

// Assumes you have a page object of type MyPage.
// Note the default timeout for RetryingElementLocator is
// 5 seconds, if unspecified.
// The generic version of this code looks like this:
// MyPage page = PageFactory.InitElements<MyPage>(new RetryingElementLocator(driver), TimeSpan.FromSeconds(10));
MyPage page = new MyPage();
PageFactory.InitElements(page, new RetryingElementLocator(driver, TimeSpan.FromSeconds(10))); 

Additionally, if you really need to customize how things work, you're always welcome to implement IPageObjectMemberDecorator, which allows you to fully customize how attributes are enumerated and values set to the properties or fields decorated with those attributes. One of the (non-generic) overloads of PageFactory.InitElements takes an instance of an object implementing IPageObjectMemberDecorator.

I'll leave aside that proper implementations of the Page Object Pattern as strictly defined shouldn't expose any WebDriver objects outside of each page object. Otherwise, all you're implementing is a "page wrapper," which is a perfectly valid approach, just not what one would call a "page object."

JimEvans
  • 27,201
  • 7
  • 83
  • 108
  • Always like your answer. Something new to learn. However, what is the logic behind `RetryingElementLocator`? In terms of heavily Ajax pages or Angular would it be helpful to locate element? – Saifur Jul 24 '15 at 16:54
  • Weird thing is, though I hadn't inspected the PageFactory code to look for a wait, I didn't expect there'd be one, and yet, it does still wait for elements to load. I know the strict Page Object Pattern doesn't expose elements. They're only exposed now because I'm pretty early in the process of building the POM and find it temporarily convenient. – MartinRosenberg Jul 24 '15 at 17:02
  • 2
    Thanks! The `RetryingElementLocator` did save me from having to wrap `SelectElement`. I was hoping there'd be a way to get something cleaner (like `FindsBy` directly on a `SelectElement`), but it doesn't surprise me that there's not. Not that I necessarily need it right now, but would you have any more information about usage of `IPageObjectMemberDecorator`? Even Google only has 10 results for it. – MartinRosenberg Jul 24 '15 at 19:16