Using Page Object Model¶
The Page Object Model is a pattern, that is often used to abstract your Web UI with Selenium to easier automate it.
So to automate following HTML snippet
<input id="txtUrl" name="Url" type="text" value="">
you have following class to control it
public class PageObject
{
public IWebElement TxtUrl {get;}
}
When you are working with Selenium, you are always working with WebElements to access the different elements on your Website. You can find them with the FindElement
and FindElements
methods on the WebDriver
class.
If you are always using these methods directly in your automation code, you will get a lot of code duplication. This is the moment when you should start using the Page Object Model.
You hide the calls to the FindElement(s)
methods in a class.
This has following advantages:
the classes are easier reusable
if you need to change an
id
of your element, you need to change only one placeyour bindings are less dependent on your HTML structure
Simple Implementation¶
HTML:
<input id="txtUrl" name="Url" type="text" value="">
Code:
public class PageObject
{
private IWebDriver _webDriver;
public PageObject(IWebDriver webDriver)
{
_webDriver = webDriver;
}
public IWebElement txtUrl => _webDriver.FindElement(By.Id("txtUrl"));
}
You pass your WebDriver
instance via constructor, and always when you access the TxtUrl
property, the WebDriver
searches on the whole page for an element with the id txtUrl
. There is no caching involved.
Implementation with Caching¶
HTML:
<input id="txtUrl" name="Url" type="text" value="">
Code:
public class PageObject
{
private IWebDriver _webDriver;
private Lazy<IWebElement> _txtUrl;
public PageObject(IWebDriver webDriver)
{
_webDriver = webDriver;
_txtUrl = new Lazy<IWebElement>(() => _webDriver.FindElement(By.Id("txtUrl")));
}
public IWebElement txtUrl => _txtUrl.Value;
}
Again You pass your WebDriver
instance via constructor. In this case we are using Lazy as a easy way to cache the result of the FindElement
method.
Only the first call to the txtUrl
property, triggers a call to the FindElement
function. All subsequent calls, will return the same value as before.
This will save you some time in execution of your automation code, as the WebDriver needs to do search less often for the same element.
If you use a caching strategy like that, be careful with your lifetime of your page objects and your page. Don’t reuse an old instance of your page model, if the page changed in the meantime.
Implementation with Hierarchy¶
HTML:
<div class='A'>
<div class='B'/>
</div>
<div class='B'>
</div>
Code:
public class ParentPageObject
{
private IWebDriver _webDriver;
public ParentPageObject(IWebDriver webDriver)
{
_webDriver = webDriver;
}
public IWebElement WebElement => _webDriver.FindElement(By.ClassName("A"));
public ChildPageObject Child => new ChildPageObject(WebElement);
}
public class ChildPageObject
{
private IWebElement _webElement;
private Lazy<IWebElement> _txtUrl;
public ChildPageObject(IWebElement webElement)
{
_webElement = webElement;
}
public IWebElement WebElement => _webElement.FindElement(By.ClassName("B"));
}
In this example we have a slightly adjusted HTML document to work with. There are two div
- elements with the same class B
, but we only want the PageObject for the div
- element with the class A
and the child.
If we would use the same WebDriver.FindElement
method we would get the div
- element that is on the same level as the A
div.
But every WebElement has also the FindElement(s)
- methods. This enable you to query the elements only in a part of your whole HTML DOM.
To do that we are passing this time the parent
- WebElement to the ChildPageObject
class to only search for the element with the class B
within the A
- div.
This concept enables you to structure your PageObjects in a similar way you have your HTML DOM structure.