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();
}
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.
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.
Autofac is a Dependecy injection tool, it means it will only helps getting instances and all its dependency graph. It won't help intercepting method or properties.
What you need is an Aspect Orient Programming tool. Such tools will help intercepting method and so on. One of the most popular one for .net is castle dynamic proxy
DynamicProxy will automatically inject proxy for your type and inject IInterceptor
where needed.
Let's assume you have the following class
public class Test1
{
[Cacheable]
protected virtual String UserName => "test";
}
DynamicProxy will automatically create a class derived from Test1
and override the UserName
properties to inject these IInterceptor. That the reason why the properties should be virtual.
Then, instead of working with Test1
you will work with a type given by dynamic proxy.
There is many way to get the proxy type, one of the solution is the following one :
ProxyGenerator generator = new ProxyGenerator();
IInterceptor interceptor = new CacheableInterceptor();
Test1 test = new Test1();
Test1 proxy = generator.CreateClassProxyWithTarget<Test1>(test, interceptor);
In this case each time you do something with proxy
the CacheableInterceptor
will be invoked.
A really simple implementation of CacheableInterceptor
could be
public class CacheableInterceptor : IInterceptor
{
private readonly Dictionary<String, String> _cache = new Dictionary<String, String>();
public void Intercept(IInvocation invocation)
{
// This code is a sample and should not be used in production
// performance optimization are available and is not thread safe
var pi = invocation.Method
.DeclaringType
.GetProperties()
.Where(p => p.GetCustomAttributes<CacheableAttribute>().Any())
.FirstOrDefault(p => p.GetGetMethod() == invocation.Method);
if (pi != null)
{
if (this._cache.TryGetValue(pi.Name, out var value))
{
invocation.ReturnValue = value;
}
else
{
invocation.Proceed();
value = (String)invocation.ReturnValue;
this._cache.Add(pi.Name, value);
}
}
}
}
When you fully understand how dynamic proxy works. I would recommend to have a look at how Autofac works (without interception & so on) and then combine Autofac with DynamicProxy to make the magic happens : autofac can create the proxy, interceptor for you : See Autofac - Type interceptors