在Selenium C#中使用PageFactory / FindsBy时如何初始化SelectElements? [英] How to initialize SelectElements while using PageFactory / FindsBy in Selenium C#?
问题描述
不幸的是,我发现 FindsByAttribute
我在C#的Selenium WebDriver中使用PageFactory来构建一个页面对象模型/ code>不会初始化 SelectElement
(HTML < select>
标签/下拉菜单)。到目前为止,我已经发生了一些想法,但没有一个是理想的:
-
PageFactory
和FindsByAttribute
是密封
,所以我不能强制通过继承这些。 - 从
IWebElement
手动实例化SelectElement
在每种方法中是相当凌乱和重复的。它也忽略了PageFactory
中的明显内置等待,并抛出NoSuchElementException
,除非我每次添加一个等待这将需要在整个地方重复定位器,击败(部分)POM的目的。 - 包装每个
IWebElement
属性与SelectElement
属性不那么凌乱,但仍然有与上述相同的等待问题。
目前为止最好的选项是#3,并为 SelectElement
编写一个包装器,这只是为每个方法添加一个等待。虽然这个解决方案将会工作,但它将大量批量编写每个页面的代码,而不是这个(假设的)漂亮的代码:
[FindsBy(How = How.Id,Using =MonthDropdown)]
public SelectElement MonthDropdown;
我被一个包装纸包裹住了(我宁愿避免的东西),还有: p>
[FindsBy(How = How.Id,Using =MonthDropdown)]
private IWebElement _monthDropdown;
public Selector MonthDropdown
{
get {return new Selector(MonthDropdown,Wait);
}
使用选择器
作为 SelectElement
包装器,也必须使用 IWait< IWebDriver>
,以便它可以等待,并实例化新的选择器
每次我访问它。
有更好的方法吗?
编辑:错误的访问修饰符错误。固定。谢谢@JimEvans。
首先,在.NET 中没有内置等待 PageFactory
实现。您可以轻松地在调用 InitElements
(稍后更多地)中指定一个。目前,最好的选择是你的选项3,虽然我不会公开 IWebElement
成员;我会使它 private
,因为 PageFactory
可以像私有成员一样轻松地枚举私有成员。所以你的页面对象将如下所示:
[FindsBy(How = How.Id,Using =MonthDropdown)]
private IWebElement dropDown;
public SelectElement MonthDropdownElement
{
get {return new SelectElement(dropdown); }
}
如何获得实际的 IWebElement
当你需要它?由于
SelectElement
实现 IWrappedElement
,您可以简单地调用 WrappedElement
属性,如果您需要访问由 IWebElement
接口提供的元素的方法和属性。
最近版本的.NET绑定已将 PageFactory
重组为更可扩展。要添加您希望的内置等待,您可以执行以下操作:
//假设您有一个页面对象的类型MyPage。
//注意RetryingElementLocator的默认超时是
// 5秒,如果未指定。
//该代码的通用版本如下所示:
// MyPage page = PageFactory.InitElements&MyPage>(new RetryingElementLocator(driver),TimeSpan.FromSeconds(10));
MyPage page = new MyPage();
PageFactory.InitElements(page,new RetryingElementLocator(driver,TimeSpan.FromSeconds(10)));
此外,如果您真的需要自定义事情的工作原理,总是欢迎实现 IPageObjectMemberDecorator
,它允许您完全自定义如何枚举属性,并将值设置为用这些属性装饰的属性或字段。 PageFactory.InitElements
的(非泛型)重载之一采用实现 IPageObjectMemberDecorator
的对象的实例。 p>
我将抛开严格定义的页面对象模式的正确实现,不应将任何WebDriver对象暴露在每个页面对象之外。否则,所有您正在实施的是一个页面包装器,这是一个完美有效的方法,而不是称之为页面对象。
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 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."
这篇关于在Selenium C#中使用PageFactory / FindsBy时如何初始化SelectElements?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!