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 and FindsByAttribute are sealed, so I can't force it to by just inheriting those.
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.
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.
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."
Related
I have IWebElements defined like this:
[FindsBy(How = How.Id, Using = "actionHistoryBtn")]
private IWebElement actionHistoryButtons;
But the only way I have been able to find elements with the same Id is by doing this:
private ReadOnlyCollection<IWebElement> actionHistoryButtons => driver.FindElements(By.Id("actionHistoryBtn"));
Is there a way I can create a collection in the same format? Or how I can modify this to get all elements using "WhateverId" Or is the ReadOnlyCollection the only way to get all the elements?
To simply answer your question, ReadOnlyCollection<IWebElement>, a collection of elements, is what driver.FindElements() returns so that's the data type you need to use. You can't use IWebElement because that represents a single element, not a collection of elements like you are looking for.
Having said that... some advice.
Simon Stewart, the Selenium Project lead and creator of PageFactory, has said not to use PageFactory. Here's a keynote from a seleniumconf a few years ago where he states this.
Keynote - Selenium: State of the Union - Simon Stewart – Selenium Project & WebDriver
25:18 Don't use PageFactory
Besides the fact that Simon said not to use it, it's best not to have state in your page objects, e.g. don't grab elements and store them in properties. Instead grab them as you need them... that will reduce StaleElementExceptions and other issues.
Here's a real quick sample page object showing what I'm describing.
class SamplePage
{
private readonly By _actionHistoryButtonsLocator = By.Id("actionHistoryBtn");
public ReadOnlyCollection<IWebElement> GetActionHistoryButtons()
{
return Driver.FindElements(_actionHistoryButtonsLocator);
}
}
What are best practices for accessing selectors, when you use page object pattern? Let's have a simple example:
[FindsBy(How = How.CssSelector, Using = "button-submit")]
public IWebElement button { get; set; }
in most cases you use just button.Click() etc., and that's simple, but what if sometimes you need to access the css selector, as you traverse in shadow DOM for example, so you need to write something like GetShadowElement(anotherButton).FindElement(By.CssSelector("button-submit"));.
Is there a way to access this Using= from page element while working with FindElement method or do i need to extract this into:
private const string locator = "button-submit"; for example, update my page object element into Using = locator and of course later on collect all such examples and put them in static class for constants, etc. Is there a better way for handling that? How do you traverse in shadow DOM with page object?
Hopefully I'm not the first person to encounter this issue.
I'm writing some selenium tests in C# and have a dilemma when trying to adobt a page object model design whilst also needing to do some explicit waits with the ExpectedConditions class.
Let's say I'm storing my elements in an element map class that is simply a property that calls the .FindElement method using an XPath stored in a resources file...
public class PageObject {
public IWebElement Element
{
get { return DriverContext.Driver.FindElement(By.XPath(Resources.Element)); }
}
}
Then I would go on to use that property in various selenium methods.
The issue I have is I also need to check whether this element is visible on the page, and it will error before I can perform the checked (e.g. with WebDriverWait, passing in ExpectedConditions.ElementIsVisible(by) to the .until method).
How do I cleanly seperate out the IWebElement and By locator and allow for this explicit wait/check where needed?
TLDR - How do I maintain a Page Object Model design whilst also having the flexibility to use explicit waits based on the By locator of my elements.
Many thanks,
I use page objects all the time but I have locators at the top of the class instead of elements. I then use the locators to click buttons, etc. as needed. The advantage of this is I only access the element on the page when needed which avoids stale element exceptions, etc. See a simple example below.
class SamplePage
{
public IWebDriver Driver;
private By waitForLocator = By.Id("sampleId");
// please put the variable declarations in alphabetical order
private By sampleElementLocator = By.Id("sampleId");
public SamplePage(IWebDriver webDriver)
{
this.Driver = webDriver;
// wait for page to finish loading
new WebDriverWait(Driver, TimeSpan.FromSeconds(10)).Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(waitForLocator));
// see if we're on the right page
if (!Driver.Url.Contains("samplePage.jsp"))
{
throw new InvalidOperationException("This is not the Sample page. Current URL: " + Driver.Url);
}
}
public void ClickSampleElement()
{
Driver.FindElement(sampleElementLocator).Click();
}
}
I would recommend against storing locators in a separate file because it breaks one of the mantras of page object model which is everything to do with the page goes in the page object. You shouldn't have to open anything but one file to do anything with Page X, the page object class.
When I declare my IWebElement like this:
[FindsBy(How = How.CssSelector, Using = "input#raffle_submit")]
private IWebElement _buyNowButton;
And initialize it in the class constructor using PageFactory:
PageFactory.InitElements(Driver, this);
Then when I make a break point in any method in order to look at the element's properties I see no properties in this element:
public CartObj ClickBuyNowButton()
{
_buyNowButton.Click(); //here is my break point
}
Only property I see is: - Non-Public members : http://prntscr.com/8k90r4
So where are all properties that must be in IWebElement like "Enabled", "Displayed" and so forth?
More to say: I don't have any problems with using this element, I can click on it, I can send keys to it but when I use the next JavaScript code it tells me that argument is wrong (but it worked well before):
((IJavaScriptExecutor)Driver).ExecuteScript("arguments[0].scrollIntoView();", webElement);
Error that I appears after executing JS code above:
Additional information: Argument is of an illegal
typeOpenQA.Selenium.Support.Events.EventFiringWebDriver+EventFiringWebElement
I'm sure it's connected with version of WebDriver.
So my current WebDriver version is 2.47.0
ChromDriver version is 2.19
To your first question: What you see in the debugger is actually just a proxy object. The PageFactory initializes your _buyNowButton with a proxy which will only be resolved once you use it somewhere in your script. So unless you actually use it, you won't see any properties like Displayed or Enabled in the debugger.
As to your second question: I can't recreate that now with Selenium 3 and the latest ChromeDriver, but it may have been a bug back in the day.
I am very new to Selenium, so my apologies if it's a silly question.
I have successfully wired up IntelliJ (Play! framework) with Selenium, and created some tests using firefoxDrivers.
I'm trying to check if the page had been validated properly.
long story short, I'm selecting an element like this:
WebElement smallDecel = firefoxDriver.findElement(By.cssSelector("#configTable tr:nth-child(2) td .playerDecelInput"));
I do some further operations (clear and change the value, submit the 'form'), and then I want to check if the TD the input sits in was given another class.
So, the question is - is there a simple technique I can use to find out if a WebElement / DOM has a class specified?
To expand on Sam Woods' answer, I use a simple extension method (this is for C#) to test whether or not an element has a specified class:
public static bool HasClass( this IWebElement el, string className ) {
return el.GetAttribute( "class" ).Split( ' ' ).Contains( className );
}
Once you find the element, you can just call myElement.GetAttribute("class"). Then you can parse the string that is returned and see if it contains or does not contain the class name you care about.
You can use FindElement(By.ClassName(//name of your class)); I would recommend that you either loop through and search the DOM for a set period of time or set a Thread.sleep(xxxx) and then look for the appended class.