What is the standard approach when applying the PageObjects pattern to components of a page?
For the sake of an example lets say I am writing tests for the Features on an Amazon product page.
That page contains a large number of separate features, Product Information, Customers who Viewed this, Other Customers Suggested etc etc.
Current examples I have seen for PageObjects really only cover how to deal with a single page that has limited functionality. What I am looking for is something along the line of a PageObject that would represent the Product page and then be composed of ComponentObjects that represent each component.
eg:
public class ProductPage
{
[FindsBy(How = How.Id, Using = "productInformation")]
public ProductInformationControl ProductionInformation{get;set}
[FindsBy(How = How.Id, Using = "customersViewed")]
public CustomersAlsoViewedControl CustomersAlsoViewed{get;set}
[FindsBy(How = How.Id, Using = "customersSuggested")]
public CustomersSuggestedControl CustomersSuggested{get;set}
}
public class ProductInformationControl
{
//Ideally the FindsBy here would find the element based on the Controls context
// So only resolving to an element that was within the context of this control.
[FindsBy(How = How.ClassName, Using = "title")]
private IWebElement _title;
public string Title
{
get
{
return _title.Text;
}
}
}
Then within a test I would access the control like so:
var productPage = ScenarioContext.Current.Get<ProductPage>();
Assert.That(productPage.ProductInformation.GetTitle(), Is.EqualTo("My Title"));
I've used this approach previously but with a custom framework built on top of Selenium that allowed for resolution of the child objects and their components. What I'm looking for is the approach to take with Selenium 2 and C# out of the box.
There is no out of the box way to do this.
Your approach is very good,
you can implement it by calling PageFactory.InitElements() with custom implementation for IElementLocator and IPageObjectMemberDecorator
See this answer:
How to initialize SelectElements while using PageFactory / FindsBy in Selenium C#?
Related
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?
I'm struggling with finding by Xpath. My problem is that the software that I'm testing has one specific function where I need to scan two components (is it called two-step scan) and there are two textboxes without the name and with the same Automationid. So I need to find the second one I tried this but it does not work.
[FindsBy(How = How.Xpath, Using = "//*[#AutomationId='ScanTextBox'][1]")]
public IWebElement ScanTextBox1;
[FindsBy(How = How.Xpath, Using = "//*[#AutomationId='ScanTextBox'][2]")]
public IWebElement ScanTextBox2;
I'm using winium and I'm testing WPF application.
Update locators as described here:
(//*[#AutomationId='ScanTextBox'])[1]
(//*[#AutomationId='ScanTextBox'])[2]
The difference is that in my case (locator)[n]you select n-th element out of all elements found by a locator. And bylocator[n] you search for element that has n-th positions inside parent nodes
You really need to use a FindsBy? We are unable to create a correct XPath to a FindsBy to these buttons without the html. This way, I only can provide you another solution, and update my awnser when the html code be provided.
You can use FindElement, the plural one, that is able to return both. After, you select the desired button by index. A example method able to do it:
public IWebElement GetScanTextBox(int index)
{
return Driver
.FindElements(By.XPath("//*[#AutomationId='ScanTextBox']"))
.ElementAt(index);
}
public void UsageExample()
{
var buttonOne = GetScanTextBox(0);
var buttonTwo = GetScanTextBox(1);
}
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.
We are building a web application that has expandable workspaces on the sides of the page that expand and contract based on page size. when the workplace is contracted the page objects of the expanded list are not visible to be initialized when the class is called as the site is being developed in Angular 2.0
We are also building an automated test framework with Selenium with C# bindings and using the Page Object Model to run our automation. Is it possible load objects based on the window into a single page class like this
public class PageObjectClass
{
public PageObjectClass(IwebDriver driver)
{
PageFactory.InitElements(driver, this)
}
private IWebElement anObjectVisibleWhenContracted
//load an object that is not visible based on window size
}
Or do I need to get the size of the window when I call the class and have separate classes based on window width in the test scripts like this?
if (driver.Manage().Window.Size.Width < 1280)
{
someVar = new PageObjectClass(driver):
}
else
{
someOtherVar = new exp[andedPageObjects
}
//do stuff here
If you don't want to face issues when an element is displayed but Selenium doesn't interact with it a good practice is to maximize your browser's window on setUp.
Still if you don't want to do this, Selenium scrolls to an element when you interact with it.
So, answering your question - you don't need to change the window size. When you use PageObject in C# like you shown then each element will be initialized when you address to it (click, sendKeys, etc.). Each time you address to an element it will be initialized again and Selenium must scroll to this element.
But there are some bugs that appears in some cases when an element is displayed at the edge of the page and Selenium can not scroll to it correctly. Why this happens I don't know but luckily it happens very rarely.
I figured this out on my own. In the classes where I needed to deal with expandable workspaces, I declared all my variables in the class and didn't assign them any values before running PageFactory.InitElements
In the class constructor I passed in a Size variable along with the WebDriver that has the current size of the window. Then the objects that were appropriate for the window size were all loaded based on that
Just took a little restructuring and now it's working like a charm. Class structure looks like this now
public class ClassName: InheritedClass
{
#region Page Objects
private IWebElement object1;
private IWebElement object2;
#endregion
public ClassName(IWebDriver driver, Size winSize)
{
PageFactory.InitElements(driver, this);
if (winSize.Width > 1440)
{
object1= driver.FindElement(expanded By phrase locator);
object1 = driver.FindElement(expanded By phrase locator);
}
else
{
object1= driver.FindElement(contracted By phrase locator);
object1 = driver.FindElement(contracted By phrase locator);
}
}
#region Page Methods that use these objects
#endregion
}
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."