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:
PageFactory
andFindsByAttribute
aresealed
, so I can't force it to by just inheriting those.- Manually instantiating a
SelectElement
from anIWebElement
in each method is rather messy and duplicative. It also ignores the apparent built-in wait inPageFactory
and throwsNoSuchElementException
s 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. - Wrapping each
IWebElement
property with aSelectElement
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.
First, there's no "built-in wait" in the .NET
PageFactory
implementation. You can easily specify one in the call toInitElements
(more on that in a bit). At present, the best option for you would be your option 3, though I wouldn't expose theIWebElement
member; I'd make itprivate
, since thePageFactory
can enumerate over private members just as easily as public ones. So your page object would look like this:How do you get the actual
IWebElement
when you need it? SinceSelectElement
implementsIWrappedElement
, you can simply call theWrappedElement
property if you need access to the methods and properties of the element provided by theIWebElement
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: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 ofPageFactory.InitElements
takes an instance of an object implementingIPageObjectMemberDecorator
.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."