Skip to content

Commit 413ff3e

Browse files
authored
Allow to cache the found element. (#48)
* Allow to cache the found element. * Implement ElementCacheConfiguration, ElementCacheHandler and CachedElementStateProvider
1 parent 4067f65 commit 413ff3e

File tree

18 files changed

+532
-5
lines changed

18 files changed

+532
-5
lines changed

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Applications/Startup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public virtual IServiceCollection ConfigureServices(IServiceCollection services,
3232

3333
services.AddSingleton(settingsFile);
3434
services.AddSingleton(Logger.Instance);
35+
services.AddSingleton<IElementCacheConfiguration, ElementCacheConfiguration>();
3536
services.AddSingleton<ILoggerConfiguration, LoggerConfiguration>();
3637
services.AddSingleton<ITimeoutConfiguration, TimeoutConfiguration>();
3738
services.AddSingleton<IRetryConfiguration, RetryConfiguration>();

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Aquality.Selenium.Core.Configurations
2+
{
3+
/// <summary>
4+
/// Provides element's cache configuration
5+
/// </summary>
6+
public class ElementCacheConfiguration : IElementCacheConfiguration
7+
{
8+
/// <summary>
9+
/// Instantiates class using <see cref="ISettingsFile"/> with general settings.
10+
/// </summary>
11+
/// <param name="settingsFile">Settings file.</param>
12+
public ElementCacheConfiguration(ISettingsFile settingsFile)
13+
{
14+
var jPath = ".elementCache.isEnabled";
15+
IsEnabled = settingsFile.IsValuePresent(jPath) && settingsFile.GetValue<bool>(jPath);
16+
}
17+
18+
public bool IsEnabled { get; }
19+
}
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Aquality.Selenium.Core.Configurations
2+
{
3+
/// <summary>
4+
/// Represents element's cache configuration.
5+
/// </summary>
6+
public interface IElementCacheConfiguration
7+
{
8+
/// <summary>
9+
/// Is element caching allowed or not.
10+
/// </summary>
11+
bool IsEnabled { get; }
12+
}
13+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using Aquality.Selenium.Core.Elements.Interfaces;
2+
using Aquality.Selenium.Core.Logging;
3+
using Aquality.Selenium.Core.Waitings;
4+
using OpenQA.Selenium;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
9+
namespace Aquality.Selenium.Core.Elements
10+
{
11+
public class CachedElementStateProvider : IElementStateProvider
12+
{
13+
private readonly IElementCacheHandler elementCacheHandler;
14+
private readonly ConditionalWait conditionalWait;
15+
private readonly By locator;
16+
17+
public CachedElementStateProvider(By locator, ConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler)
18+
{
19+
this.elementCacheHandler = elementCacheHandler;
20+
this.conditionalWait = conditionalWait;
21+
this.locator = locator;
22+
}
23+
24+
protected virtual IList<Type> ExceptionsHandledByDefault => new List<Type> { typeof(StaleElementReferenceException) };
25+
26+
protected virtual bool TryInvokeFunction(Func<IWebElement, bool> func, IList<Type> exceptionsToHandle = null)
27+
{
28+
var handledExceptions = exceptionsToHandle ?? ExceptionsHandledByDefault;
29+
try
30+
{
31+
return func(elementCacheHandler.GetElement(TimeSpan.Zero));
32+
}
33+
catch (Exception e)
34+
{
35+
if (handledExceptions.Any(type => type.IsAssignableFrom(e.GetType())))
36+
{
37+
return false;
38+
}
39+
throw;
40+
}
41+
}
42+
43+
public virtual bool IsDisplayed => !elementCacheHandler.IsStale
44+
&& TryInvokeFunction(element => element.Displayed, new List<Type> { typeof(StaleElementReferenceException), typeof(NoSuchElementException) });
45+
46+
public virtual bool IsExist => !elementCacheHandler.IsStale
47+
&& TryInvokeFunction(element => true, new List<Type> { typeof(NoSuchElementException) });
48+
49+
public virtual bool IsClickable => TryInvokeFunction(element => element.Displayed && element.Enabled);
50+
51+
public virtual bool IsEnabled => TryInvokeFunction(element => element.Enabled);
52+
53+
public virtual void WaitForClickable(TimeSpan? timeout = null)
54+
{
55+
var errorMessage = $"Element {locator} has not become clickable after timeout.";
56+
conditionalWait.WaitForTrue(() => IsClickable, timeout, message: errorMessage);
57+
}
58+
59+
public virtual bool WaitForDisplayed(TimeSpan? timeout = null)
60+
{
61+
return WaitForCondition(() => TryInvokeFunction(element => element.Displayed), "displayed", timeout);
62+
}
63+
64+
public virtual bool WaitForEnabled(TimeSpan? timeout = null)
65+
{
66+
return WaitForCondition(() => IsEnabled, "enabled", timeout);
67+
}
68+
69+
public virtual bool WaitForExist(TimeSpan? timeout = null)
70+
{
71+
return WaitForCondition(() => TryInvokeFunction(element => true), "exist", timeout);
72+
}
73+
74+
public virtual bool WaitForNotDisplayed(TimeSpan? timeout = null)
75+
{
76+
return WaitForCondition(() => !IsDisplayed, "invisible or absent", timeout);
77+
}
78+
79+
public virtual bool WaitForNotEnabled(TimeSpan? timeout = null)
80+
{
81+
return WaitForCondition(() => !IsEnabled, "disabled", timeout);
82+
}
83+
84+
public virtual bool WaitForNotExist(TimeSpan? timeout = null)
85+
{
86+
return WaitForCondition(() => !IsExist, "absent", timeout);
87+
}
88+
89+
protected virtual bool WaitForCondition(Func<bool> condition, string conditionName, TimeSpan? timeout)
90+
{
91+
var result = conditionalWait.WaitFor(condition, timeout);
92+
if (!result)
93+
{
94+
var timeoutString = timeout == null ? string.Empty : $"of {timeout.Value.TotalSeconds} seconds";
95+
Logger.Instance.Warn($"Element {locator} has not become {conditionName} after timeout {timeoutString}");
96+
}
97+
98+
return result;
99+
}
100+
}
101+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Aquality.Selenium.Core.Applications;
3+
using Aquality.Selenium.Core.Configurations;
34
using Aquality.Selenium.Core.Elements.Interfaces;
45
using Aquality.Selenium.Core.Localization;
56
using Aquality.Selenium.Core.Logging;
@@ -16,6 +17,7 @@ namespace Aquality.Selenium.Core.Elements
1617
public abstract class Element : IElement
1718
{
1819
private readonly ElementState elementState;
20+
private IElementCacheHandler elementCacheHandler;
1921

2022
protected Element(By locator, string name, ElementState state)
2123
{
@@ -28,12 +30,29 @@ protected Element(By locator, string name, ElementState state)
2830

2931
public string Name { get; }
3032

31-
public virtual IElementStateProvider State => new ElementStateProvider(Locator, ConditionalWait, Finder);
33+
public virtual IElementStateProvider State => CacheConfiguration.IsEnabled
34+
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache)
35+
: new ElementStateProvider(Locator, ConditionalWait, Finder);
36+
37+
protected virtual IElementCacheHandler Cache
38+
{
39+
get
40+
{
41+
if (elementCacheHandler == null)
42+
{
43+
elementCacheHandler = new ElementCacheHandler(Locator, elementState, Finder);
44+
}
45+
46+
return elementCacheHandler;
47+
}
48+
}
3249

3350
protected abstract ElementActionRetrier ActionRetrier { get; }
3451

3552
protected abstract IApplication Application { get; }
3653

54+
protected abstract IElementCacheConfiguration CacheConfiguration { get; }
55+
3756
protected abstract ConditionalWait ConditionalWait { get; }
3857

3958
protected abstract string ElementType { get; }
@@ -67,7 +86,9 @@ public virtual RemoteWebElement GetElement(TimeSpan? timeout = null)
6786
{
6887
try
6988
{
70-
return (RemoteWebElement)Finder.FindElement(Locator, elementState, timeout);
89+
return CacheConfiguration.IsEnabled
90+
? Cache.GetElement(timeout)
91+
: (RemoteWebElement) Finder.FindElement(Locator, elementState, timeout);
7192
}
7293
catch (NoSuchElementException ex)
7394
{
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Aquality.Selenium.Core.Elements.Interfaces;
2+
using OpenQA.Selenium;
3+
using OpenQA.Selenium.Remote;
4+
using System;
5+
6+
namespace Aquality.Selenium.Core.Elements
7+
{
8+
public class ElementCacheHandler : IElementCacheHandler
9+
{
10+
private readonly By locator;
11+
private readonly ElementState state;
12+
private readonly IElementFinder elementFinder;
13+
14+
private RemoteWebElement remoteElement;
15+
16+
public ElementCacheHandler(By locator, ElementState state, IElementFinder finder)
17+
{
18+
this.locator = locator;
19+
this.state = state;
20+
elementFinder = finder;
21+
}
22+
23+
public bool IsStale => remoteElement != null && IsRefreshNeeded;
24+
25+
public bool IsRefreshNeeded
26+
{
27+
get
28+
{
29+
if (remoteElement == null)
30+
{
31+
return true;
32+
}
33+
try
34+
{
35+
var isDisplayed = remoteElement.Displayed;
36+
// no refresh needed if the property is available
37+
return false;
38+
}
39+
catch
40+
{
41+
return true;
42+
}
43+
}
44+
}
45+
46+
public RemoteWebElement GetElement(TimeSpan? timeout = null)
47+
{
48+
49+
if (IsRefreshNeeded)
50+
{
51+
remoteElement = (RemoteWebElement)elementFinder.FindElement(locator, state, timeout);
52+
}
53+
54+
return remoteElement;
55+
}
56+
}
57+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using OpenQA.Selenium.Remote;
2+
using System;
3+
4+
namespace Aquality.Selenium.Core.Elements.Interfaces
5+
{
6+
/// <summary>
7+
/// Allows to use cached element.
8+
/// </summary>
9+
public interface IElementCacheHandler
10+
{
11+
/// <summary>
12+
/// Determines is the cached element refresh needed.
13+
/// </summary>
14+
bool IsRefreshNeeded { get; }
15+
16+
/// <summary>
17+
/// Determines is the element stale.
18+
/// </summary>
19+
bool IsStale { get; }
20+
21+
/// <summary>
22+
/// Allows to get cached element.
23+
/// </summary>
24+
/// <param name="timeout">Timeout used to retrive the element when <see cref="IsRefreshNeeded"/> is true.</param>
25+
/// <returns>Cached element.</returns>
26+
RemoteWebElement GetElement(TimeSpan? timeout = null);
27+
}
28+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
},
1212
"logger": {
1313
"language": "en"
14+
},
15+
"elementCache": {
16+
"isEnabled": false
1417
}
1518
}

Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Aquality.Selenium.Core.Applications;
22
using Aquality.Selenium.Core.Configurations;
3-
using Aquality.Selenium.Core.Utilities;
43
using Microsoft.Extensions.DependencyInjection;
54
using System;
65
using WebDriverManager;
@@ -22,7 +21,7 @@ private static ChromeApplication StartChrome(IServiceProvider services)
2221
{
2322
lock (downloadDriverLock)
2423
{
25-
var version = EnvironmentConfiguration.GetVariable("webdriverversion") ?? "Latest";
24+
var version = "Latest";
2625
new DriverManager().SetUpDriver(new ChromeConfig(), version: version);
2726
}
2827

0 commit comments

Comments
 (0)