Preamble
I am using Selenium
to run automated tests, and Autofac
to manage the dependency injection at runtime.
Selenium uses a WebDriver
to control the browser, usually by the interface IWebDriver
. To find an element in a page, one can use the method FindElement
which will return an object with the interface IWebElement
. To find more than one element, we can use FindElements
which in turn will return a ReadOnlyCollection<IWebElement>
The most common pattern used in web automation is Page Object Model
, which establishes:
A page object is an object-oriented class that serves as an interface to a page of your AUT. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.
So an example of this could be:
public class SomePage
{
private IWebDriver driver;
private IWebElement UsernameElement => driver.FindElement(By.Id("username"));
private IWebElement PasswordElement => driver.FindElement(By.Id("password"));
private IWebElement LoginButton => driver.FindElement(By.Id("login"));
public SomePage(IWebDriver driver)
{
this.driver = driver;
}
public Input_Username(string username) => UsernameElement.SendKeys(username);
public Input_Password(string password) => PasswordElement.SendKeys(password);
public Click_Login() => LoginButton.Click();
}
The problem
Recently I have been struggling a problem: if an element of a webpage is immutable (meaning once it has been set in the DOM, it will never change), I would like to save the value of that element the first time it is accessed and return it each subsequent time we need that element.
A first approach following with the previous example could be:
public class SomePage
{
...
private IWebElement usernameBackingField;
private IWebElement UsernameElement
{
get
{
if (usernameBackingField == null)
usernameBackingField = driver.FindElement(By.Id("username"));
return usernameBackingField;
}
}
...
}
This way, the first time we use the property UsernameElement
, we save the value in usernameBackingField
and each time we need this element we will have it saved.
If the element is particularly difficult to create, we can use Lazy<IWebElement>
to save the value and later use Value
:
public class SomePage
{
...
private Lazy<IWebElement> lazyUsername;
private IWebElement UsernameElement
{
get
{
if (lazyUsername == null || lazyUsername.IsValueCreated == false)
lazyUsername = new Lazy<IWebElement>(() => driver.FindElement(By.Id("username")), LazyThreadSafetyMode.PublicationOnly);
return lazyUsername.Value;
}
}
...
}
This example works as intended but it's a pain to do this for each element.
The question
Knowing what I need, that is: intercepting the get method
of a Property, using it as a Func<IWebElement>
for the Lazy<IWebElement>
instantiation and later returning the Lazy.Value each time the property is accessed, is this something that Autofac could help me achieve so code like the following would work?
[Cacheable]
private IWebElement UsernameElement => driver.FindElement(By.Id("username"));
Thanks for your time.