How do you use selenium ExpectedConditions in a page object model design? - c#

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.

Related

Custom Attribute to cache read only Property value in C#

I am trying to create a Custom Attribute capable of caching the value of a property, the type of the property is IWebElement which is somewhat expensive to create and could throw a NoSuchElement exception.
I have had some success implementing it like this:
protected Lazy<IWebElement> lazyWebElement;
protected virtual IWebElement cachedWebElement
{
get
{
if (!lazyWebElement.IsValueCreated)
{
try
{
lazyWebElement = new Lazy<IWebElement>(() => driver.FindElement(By.Id("someElement")),
LazyThreadSafetyMode.PublicationOnly);
}
catch (NoSuchElementException)
{
throw new NoSuchElementException("someElement is not present in DOM");
}
}
return lazyModal.Value;
}
}
And what I would like to do is
[CachedWebElement]
protected virtual IWebElement cachedWebElement => driver.FindElement(By.Id("someElement"));
But Attribute does not allow complex types in it's constructor so I am not able to pass driver.FindElement(By.Id("someElement")) as a parameter.
Having it cached would be great as right now I am using a backing field to save the value and the whole mechanism is lost if I override the property in a derived class.
Thank you.
You probably won't be very successful in your approach. I mean, "just adding an attribute".
After compiling, and during runtime, when you retrieve your object (i.e. when you do something like cachedWebElement.Displayed), a get method is called.
When a project is build, all properties get get set accessor methods attached to them. You can find out more here microsoft get keyword. Basically, the { get; set; } get compiled into functions, that you can't really "mess" with. They are the default implementations of the compiler. (if I got that right).
If you really want to follow this approach, take a look at this answer.
Now, to your problem
If you want to have a "cache" for the elements properties, that's fine. You can create a class that will hold your elements for you, and before each FindElement() will check to see if it already exists.
If you also want to be able to "click" an element, that's impossible. If you "FindElement" on a button, and then the page changes, and you want to Click() it from cache, that can't happen. The page changed. There is a reason that exception is thrown.
I will assume that you want to "cache" only the properties/information about the element, and not actually interact with it.
Then, we will just have to create a "middle man" that will handle locating each element, but will also cache the elements we find. If we ask them again, the class will give them to us from memory instead of "re-locating" them. Let's do them as extension methods to be a little pretty:
public static class ElementLocator
{
private static readonly Dictionary<By, IWebElement> _CachedElements;
public static IWebElement FindElementFromCache(this IWebDriver driver, By by)
{
if (_CachedElements.ContainsKey(by))
return _CachedElements.Single(e => e.Key == by).Value;
return driver.FindElement(by);
}
public static ReadOnlyCollection<IWebElement> FindElementsFromCache(this IWebDriver driver, By by)
{
if (_CachedElements.ContainsKey(by))
{
List<IWebElement> foundCache = _CachedElements.Where(e => e.Key == by)
.Select(e => e.Value)
.ToList();
return new ReadOnlyCollection<IWebElement>(foundCache);
}
return driver.FindElements(by);
}
static ElementLocator()
{
_CachedElements = new Dictionary<By, IWebElement>();
}
}
Then, you can simply do driver.FindElementFromCache(By.Id("someElement"));
But this might not be the complete implementation. Because, when searching for multiple elements, maybe a new one was added. Instead of 4 elements you got a minute ago, now a new table row is added, and you have 5 elements. The ElementLocator will try to see if there is anything in cache, will find 4 elements and return them to you without searching for the 5th one.
IMO, creating a caching mechanism MIGHT not be the best solution to your problem. It introduces (excuse my language) a shit-ton of problems that you will face down the road.
If you could present us with what the problem is, we could figure out something that might not involve caching. I mean, don't say "how can I do this cache better" or "my caching has a problem", but let's see why you need a cache for elements to begin with.
Best of luck to you!

Best practice for using Page Object Pattern selectors in shadow DOM

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?

Loading Selenium Page Objects Based on Browser Window Size

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
}

How to constrain CodedUI to begin search from parent object?

I'm attempting to use CodedUI in a code-first approach (page object pattern) for a WPF UI. I'm able to navigate to a specific list item within a groupbox within a tab on the main window. Each list item contains a checkbox along with some other content; I'd like to automate clicking the checkbox, but I'm getting an exception with the message 'Search may have failed at " TabList as it may have virtualized children...'
The only thing is that I'm setting the containing WpfListItem as the parent for the WpfCheckBox per the following code:
public class ConfigItem
{
private readonly WpfListItem _instance;
public WecoConfigItem([NotNull] WpfListItem instance)
{
if (instance == null) throw new ArgumentNullException("instance");
_instance = instance;
}
public ConfigItem SelectConfiguration()
{
var checkBox = new WpfCheckBox(_instance);
_instance.DrawHighlight();
checkBox.SearchProperties.Add(WpfCheckBox.PropertyNames.AutomationId, "cbIsSelected");
Mouse.Click(checkBox);
return this;
}
}
The failure occurs in the SelectConfiguration method. During test execution, the corresponding ListItem is highlighted, but then in the html output the recorded image highlights the application. So, some questions:
Why is the search starting from the application window when I'm providing the WpfListItem as the parent in the constructor?
Am I doing something that is causing the discrepancy between the DrawHighlight() output and the HTML output?
How do I constrain the search to begin with the WpfListItem parent object, for a code-first page object pattern approach?
EDIT: The search is actually beginning from the top-level application, not the tab - I was looking at a stale HTML log. Problem statement is still essentially the same.
The moment you call DrawHighlight() you are initiating a search. The next statement then gives additional search criteria and then you access the control again (Mouse.click()), but then you are reading from cache. I assume that you either need to disable the cache by setting the SearchOptions to AlwaysSearch or add the criteria before you call DrawHighlight().

How to initialize SelectElements while using PageFactory / FindsBy in Selenium C#?

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."

Categories