Skip to content

Commit e2adab1

Browse files
authored
Feature: Log element property getters (#76) +semver: minor
* Enhance LocalizationManager to get value from core if the key is missed in passed assembly * Added logging of values for GetAttribute and Text of Element * Added configuration to be able to disable LogPageSource functionality * add Configuration property to ILocalizedLogger * Added logging to ElementStateProvider's waiting functions * Update webDriverVersion to 83 in pipeline yml file
1 parent ca158e8 commit e2adab1

File tree

23 files changed

+279
-41
lines changed

23 files changed

+279
-41
lines changed

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

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/ILoggerConfiguration.cs

+5
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@ public interface ILoggerConfiguration
1010
/// </summary>
1111
/// <value>Supported language.</value>
1212
string Language { get; }
13+
14+
/// <summary>
15+
/// Perform page source logging in case of catastrophic failures or not.
16+
/// </summary>
17+
bool LogPageSource { get; }
1318
}
1419
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/LoggerConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ public class LoggerConfiguration : ILoggerConfiguration
1616
public LoggerConfiguration(ISettingsFile settingsFile)
1717
{
1818
Language = settingsFile.GetValueOrDefault(".logger.language", DefaultLanguage);
19+
LogPageSource = settingsFile.GetValueOrDefault(".logger.logPageSource", true);
1920
}
2021

2122
public string Language { get; }
23+
24+
public bool LogPageSource { get; }
2225
}
2326
}

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

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Aquality.Selenium.Core.Elements.Interfaces;
2-
using Aquality.Selenium.Core.Logging;
32
using Aquality.Selenium.Core.Waitings;
43
using OpenQA.Selenium;
54
using System;
@@ -11,12 +10,14 @@ namespace Aquality.Selenium.Core.Elements
1110
public class CachedElementStateProvider : IElementStateProvider
1211
{
1312
private readonly IElementCacheHandler elementCacheHandler;
13+
private readonly LogElementState logElementState;
1414
private readonly IConditionalWait conditionalWait;
1515
private readonly By locator;
1616

17-
public CachedElementStateProvider(By locator, IConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler)
17+
public CachedElementStateProvider(By locator, IConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler, LogElementState logElementState)
1818
{
1919
this.elementCacheHandler = elementCacheHandler;
20+
this.logElementState = logElementState;
2021
this.conditionalWait = conditionalWait;
2122
this.locator = locator;
2223
}
@@ -50,12 +51,15 @@ protected virtual bool TryInvokeFunction(Func<IWebElement, bool> func)
5051
public virtual void WaitForClickable(TimeSpan? timeout = null)
5152
{
5253
var errorMessage = $"Element {locator} has not become clickable after timeout.";
54+
var conditionKey = "loc.el.state.clickable";
5355
try
5456
{
57+
logElementState("loc.wait.for.state", conditionKey);
5558
conditionalWait.WaitForTrue(() => IsClickable, timeout, message: errorMessage);
5659
}
5760
catch (TimeoutException e)
5861
{
62+
logElementState("loc.wait.for.state.failed", conditionKey);
5963
throw new WebDriverTimeoutException(e.Message, e);
6064
}
6165

@@ -78,26 +82,27 @@ public virtual bool WaitForExist(TimeSpan? timeout = null)
7882

7983
public virtual bool WaitForNotDisplayed(TimeSpan? timeout = null)
8084
{
81-
return WaitForCondition(() => !IsDisplayed, "invisible or absent", timeout);
85+
return WaitForCondition(() => !IsDisplayed, "not.displayed", timeout);
8286
}
8387

8488
public virtual bool WaitForNotEnabled(TimeSpan? timeout = null)
8589
{
86-
return WaitForCondition(() => !IsEnabled, "disabled", timeout);
90+
return WaitForCondition(() => !IsEnabled, "not.enabled", timeout);
8791
}
8892

8993
public virtual bool WaitForNotExist(TimeSpan? timeout = null)
9094
{
91-
return WaitForCondition(() => !IsExist, "absent", timeout);
95+
return WaitForCondition(() => !IsExist, "not.exist", timeout);
9296
}
9397

94-
protected virtual bool WaitForCondition(Func<bool> condition, string conditionName, TimeSpan? timeout)
98+
protected virtual bool WaitForCondition(Func<bool> condition, string conditionKeyPart, TimeSpan? timeout)
9599
{
100+
var conditionKey = $"loc.el.state.{conditionKeyPart}";
101+
logElementState("loc.wait.for.state", conditionKey);
96102
var result = conditionalWait.WaitFor(condition, timeout);
97103
if (!result)
98104
{
99-
var timeoutString = timeout == null ? string.Empty : $"of {timeout.Value.TotalSeconds} seconds";
100-
Logger.Instance.Warn($"Element {locator} has not become {conditionName} after timeout {timeoutString}");
105+
logElementState("loc.wait.for.state.failed", conditionKey);
101106
}
102107

103108
return result;

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

+21-7
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ protected Element(By locator, string name, ElementState state)
3131

3232
public string Name { get; }
3333

34-
public virtual IElementStateProvider State => CacheConfiguration.IsEnabled
35-
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache)
36-
: new ElementStateProvider(Locator, ConditionalWait, Finder);
34+
public virtual IElementStateProvider State => CacheConfiguration.IsEnabled
35+
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache, LogElementState)
36+
: new ElementStateProvider(Locator, ConditionalWait, Finder, LogElementState);
3737

3838
protected virtual IElementCacheHandler Cache
3939
{
@@ -64,8 +64,16 @@ protected virtual IElementCacheHandler Cache
6464

6565
protected abstract ILocalizedLogger LocalizedLogger { get; }
6666

67+
protected abstract ILocalizationManager LocalizationManager { get; }
68+
69+
protected virtual ILoggerConfiguration LoggerConfiguration => LocalizedLogger.Configuration;
70+
6771
protected virtual Logger Logger => Logger.Instance;
6872

73+
protected virtual LogElementState LogElementState =>
74+
(string messageKey, string stateKey)
75+
=> LocalizedLogger.InfoElementAction(ElementType, Name, messageKey, LocalizationManager.GetLocalizedMessage(stateKey));
76+
6977
public void Click()
7078
{
7179
LogElementAction("loc.clicking");
@@ -85,18 +93,21 @@ public IList<T> FindChildElements<T>(By childLocator, string name = null, Elemen
8593
public string GetAttribute(string attr)
8694
{
8795
LogElementAction("loc.el.getattr", attr);
88-
return DoWithRetry(() => GetElement().GetAttribute(attr));
96+
var value = DoWithRetry(() => GetElement().GetAttribute(attr));
97+
LogElementAction("loc.el.attr.value", attr, value);
98+
99+
return value;
89100
}
90101

91102
public virtual RemoteWebElement GetElement(TimeSpan? timeout = null)
92103
{
93104
try
94105
{
95-
return CacheConfiguration.IsEnabled
106+
return CacheConfiguration.IsEnabled
96107
? Cache.GetElement(timeout)
97108
: (RemoteWebElement) Finder.FindElement(Locator, elementState, timeout);
98109
}
99-
catch (NoSuchElementException ex)
110+
catch (NoSuchElementException ex) when (LoggerConfiguration.LogPageSource)
100111
{
101112
LogPageSource(ex);
102113
throw;
@@ -121,7 +132,10 @@ public string Text
121132
get
122133
{
123134
LogElementAction("loc.get.text");
124-
return DoWithRetry(() => GetElement().Text);
135+
var value = DoWithRetry(() => GetElement().Text);
136+
LogElementAction("loc.text.value", value);
137+
138+
return value;
125139
}
126140
}
127141

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

+38-8
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ namespace Aquality.Selenium.Core.Elements
99
public class ElementStateProvider : IElementStateProvider
1010
{
1111
private readonly By elementLocator;
12+
private readonly LogElementState logElementState;
1213

13-
public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait, IElementFinder elementFinder)
14+
public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait, IElementFinder elementFinder, LogElementState logElementState)
1415
{
1516
this.elementLocator = elementLocator;
1617
ConditionalWait = conditionalWait;
1718
ElementFinder = elementFinder;
19+
this.logElementState = logElementState;
1820
}
1921

2022
private IConditionalWait ConditionalWait { get; }
@@ -31,22 +33,22 @@ public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait,
3133

3234
public bool WaitForDisplayed(TimeSpan? timeout = null)
3335
{
34-
return IsAnyElementFound(timeout, ElementState.Displayed);
36+
return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.Displayed), "displayed");
3537
}
3638

3739
public bool WaitForNotDisplayed(TimeSpan? timeout = null)
3840
{
39-
return ConditionalWait.WaitFor(() => !IsDisplayed, timeout);
41+
return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsDisplayed, timeout), "not.displayed");
4042
}
4143

4244
public bool WaitForExist(TimeSpan? timeout = null)
4345
{
44-
return IsAnyElementFound(timeout, ElementState.ExistsInAnyState);
46+
return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.ExistsInAnyState), "exist");
4547
}
4648

4749
public bool WaitForNotExist(TimeSpan? timeout = null)
4850
{
49-
return ConditionalWait.WaitFor(() => !IsExist, timeout);
51+
return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsExist, timeout), "not.exist");
5052
}
5153

5254
private bool IsAnyElementFound(TimeSpan? timeout, ElementState state)
@@ -56,12 +58,12 @@ private bool IsAnyElementFound(TimeSpan? timeout, ElementState state)
5658

5759
public bool WaitForEnabled(TimeSpan? timeout = null)
5860
{
59-
return IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout);
61+
return DoAndLogWaitForState(() => IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout), "enabled");
6062
}
6163

6264
public bool WaitForNotEnabled(TimeSpan? timeout = null)
6365
{
64-
return IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout);
66+
return DoAndLogWaitForState(() => IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout), "not.enabled");
6567
}
6668

6769
protected virtual bool IsElementEnabled(IWebElement element)
@@ -81,7 +83,17 @@ private bool IsElementInDesiredState(Func<IWebElement, bool> elementStateConditi
8183

8284
public void WaitForClickable(TimeSpan? timeout = null)
8385
{
84-
IsElementClickable(timeout, false);
86+
var conditionKey = "loc.el.state.clickable";
87+
try
88+
{
89+
logElementState("loc.wait.for.state", conditionKey);
90+
IsElementClickable(timeout, false);
91+
}
92+
catch
93+
{
94+
logElementState("loc.wait.for.state.failed", conditionKey);
95+
throw;
96+
}
8597
}
8698

8799
private bool IsElementClickable(TimeSpan? timeout, bool catchTimeoutException)
@@ -97,5 +109,23 @@ private bool IsElementInDesiredCondition(TimeSpan? timeout, DesiredState element
97109
{
98110
return ElementFinder.FindElements(elementLocator, elementStateCondition, timeout).Any();
99111
}
112+
113+
private bool DoAndLogWaitForState(Func<bool> waitingAction, string conditionKeyPart, TimeSpan? timeout = null)
114+
{
115+
if (TimeSpan.Zero == timeout)
116+
{
117+
return waitingAction();
118+
}
119+
120+
var conditionKey = $"loc.el.state.{conditionKeyPart}";
121+
logElementState("loc.wait.for.state", conditionKey);
122+
var result = waitingAction();
123+
if (!result)
124+
{
125+
logElementState("loc.wait.for.state.failed", conditionKey);
126+
}
127+
128+
return result;
129+
}
100130
}
101131
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Aquality.Selenium.Core.Elements
2+
{
3+
/// <summary>
4+
/// Logs element state.
5+
/// </summary>
6+
/// <param name="messageKey">Key of localized message to log.</param>
7+
/// <param name="stateKey">Key of localized state to log.</param>
8+
public delegate void LogElementState(string messageKey, string stateKey);
9+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/ILocalizedLogger.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using Aquality.Selenium.Core.Configurations;
2+
using System;
23

34
namespace Aquality.Selenium.Core.Localization
45
{
@@ -7,6 +8,11 @@ namespace Aquality.Selenium.Core.Localization
78
/// </summary>
89
public interface ILocalizedLogger
910
{
11+
/// <summary>
12+
/// Gets logger configuration.
13+
/// </summary>
14+
ILoggerConfiguration Configuration { get; }
15+
1016
/// <summary>
1117
/// Logs localized message for action with INFO level which is applied for element, for example, click, send keys etc.
1218
/// </summary>

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizationManager.cs

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Aquality.Selenium.Core.Configurations;
22
using Aquality.Selenium.Core.Logging;
33
using Aquality.Selenium.Core.Utilities;
4+
using System.Linq;
45
using System.Reflection;
56

67
namespace Aquality.Selenium.Core.Localization
@@ -9,21 +10,33 @@ public class LocalizationManager : ILocalizationManager
910
{
1011
private const string LangResource = "Resources.Localization.{0}.json";
1112
private readonly ISettingsFile localizationFile;
13+
private readonly ISettingsFile coreLocalizationFile;
1214
private readonly Logger logger;
1315

1416
public LocalizationManager(ILoggerConfiguration loggerConfiguration, Logger logger, Assembly assembly = null)
1517
{
1618
var language = loggerConfiguration.Language;
17-
localizationFile = new JsonSettingsFile(string.Format(LangResource, language.ToLower()), assembly ?? Assembly.GetExecutingAssembly());
19+
localizationFile = GetLocalizationFile(language, assembly ?? Assembly.GetExecutingAssembly());
20+
coreLocalizationFile = GetLocalizationFile(language, Assembly.GetExecutingAssembly());
1821
this.logger = logger;
1922
}
2023

24+
private static ISettingsFile GetLocalizationFile(string language, Assembly assembly)
25+
{
26+
var embeddedResourceName = string.Format(LangResource, language.ToLower());
27+
var assemblyToUse = assembly.GetManifestResourceNames().Any(name => name.Contains(embeddedResourceName))
28+
? assembly
29+
: Assembly.GetExecutingAssembly();
30+
return new JsonSettingsFile(embeddedResourceName, assemblyToUse);
31+
}
32+
2133
public string GetLocalizedMessage(string messageKey, params object[] args)
2234
{
2335
var jsonKey = $"$['{messageKey}']";
24-
if (localizationFile.IsValuePresent(jsonKey))
36+
var localizationFileToUse = localizationFile.IsValuePresent(jsonKey) ? localizationFile : coreLocalizationFile;
37+
if (localizationFileToUse.IsValuePresent(jsonKey))
2538
{
26-
return string.Format(localizationFile.GetValue<string>(jsonKey), args);
39+
return string.Format(localizationFileToUse.GetValue<string>(jsonKey), args);
2740
}
2841

2942
logger.Debug($"Cannot find localized message by key '{jsonKey}'");

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizedLogger.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Aquality.Selenium.Core.Logging;
1+
using Aquality.Selenium.Core.Configurations;
2+
using Aquality.Selenium.Core.Logging;
23
using System;
34

45
namespace Aquality.Selenium.Core.Localization
@@ -8,12 +9,15 @@ public class LocalizedLogger : ILocalizedLogger
89
private readonly ILocalizationManager localizationManager;
910
private readonly Logger logger;
1011

11-
public LocalizedLogger(ILocalizationManager localizationManager, Logger logger)
12+
public LocalizedLogger(ILocalizationManager localizationManager, Logger logger, ILoggerConfiguration configuration)
1213
{
1314
this.localizationManager = localizationManager;
1415
this.logger = logger;
16+
Configuration = configuration;
1517
}
1618

19+
public ILoggerConfiguration Configuration { get; }
20+
1721
public void InfoElementAction(string elementType, string elementName, string messageKey, params object[] args)
1822
{
1923
logger.Info($"{elementType} '{elementName}' :: {localizationManager.GetLocalizedMessage(messageKey, args)}");

0 commit comments

Comments
 (0)