Skip to content

Commit

Permalink
Feature: Log element property getters (#76) +semver: minor
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mialeska authored Jun 18, 2020
1 parent ca158e8 commit e2adab1
Show file tree
Hide file tree
Showing 23 changed files with 279 additions and 41 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ public interface ILoggerConfiguration
/// </summary>
/// <value>Supported language.</value>
string Language { get; }

/// <summary>
/// Perform page source logging in case of catastrophic failures or not.
/// </summary>
bool LogPageSource { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ public class LoggerConfiguration : ILoggerConfiguration
public LoggerConfiguration(ISettingsFile settingsFile)
{
Language = settingsFile.GetValueOrDefault(".logger.language", DefaultLanguage);
LogPageSource = settingsFile.GetValueOrDefault(".logger.logPageSource", true);
}

public string Language { get; }

public bool LogPageSource { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Aquality.Selenium.Core.Elements.Interfaces;
using Aquality.Selenium.Core.Logging;
using Aquality.Selenium.Core.Waitings;
using OpenQA.Selenium;
using System;
Expand All @@ -11,12 +10,14 @@ namespace Aquality.Selenium.Core.Elements
public class CachedElementStateProvider : IElementStateProvider
{
private readonly IElementCacheHandler elementCacheHandler;
private readonly LogElementState logElementState;
private readonly IConditionalWait conditionalWait;
private readonly By locator;

public CachedElementStateProvider(By locator, IConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler)
public CachedElementStateProvider(By locator, IConditionalWait conditionalWait, IElementCacheHandler elementCacheHandler, LogElementState logElementState)
{
this.elementCacheHandler = elementCacheHandler;
this.logElementState = logElementState;
this.conditionalWait = conditionalWait;
this.locator = locator;
}
Expand Down Expand Up @@ -50,12 +51,15 @@ protected virtual bool TryInvokeFunction(Func<IWebElement, bool> func)
public virtual void WaitForClickable(TimeSpan? timeout = null)
{
var errorMessage = $"Element {locator} has not become clickable after timeout.";
var conditionKey = "loc.el.state.clickable";
try
{
logElementState("loc.wait.for.state", conditionKey);
conditionalWait.WaitForTrue(() => IsClickable, timeout, message: errorMessage);
}
catch (TimeoutException e)
{
logElementState("loc.wait.for.state.failed", conditionKey);
throw new WebDriverTimeoutException(e.Message, e);
}

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

public virtual bool WaitForNotDisplayed(TimeSpan? timeout = null)
{
return WaitForCondition(() => !IsDisplayed, "invisible or absent", timeout);
return WaitForCondition(() => !IsDisplayed, "not.displayed", timeout);
}

public virtual bool WaitForNotEnabled(TimeSpan? timeout = null)
{
return WaitForCondition(() => !IsEnabled, "disabled", timeout);
return WaitForCondition(() => !IsEnabled, "not.enabled", timeout);
}

public virtual bool WaitForNotExist(TimeSpan? timeout = null)
{
return WaitForCondition(() => !IsExist, "absent", timeout);
return WaitForCondition(() => !IsExist, "not.exist", timeout);
}

protected virtual bool WaitForCondition(Func<bool> condition, string conditionName, TimeSpan? timeout)
protected virtual bool WaitForCondition(Func<bool> condition, string conditionKeyPart, TimeSpan? timeout)
{
var conditionKey = $"loc.el.state.{conditionKeyPart}";
logElementState("loc.wait.for.state", conditionKey);
var result = conditionalWait.WaitFor(condition, timeout);
if (!result)
{
var timeoutString = timeout == null ? string.Empty : $"of {timeout.Value.TotalSeconds} seconds";
Logger.Instance.Warn($"Element {locator} has not become {conditionName} after timeout {timeoutString}");
logElementState("loc.wait.for.state.failed", conditionKey);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ protected Element(By locator, string name, ElementState state)

public string Name { get; }

public virtual IElementStateProvider State => CacheConfiguration.IsEnabled
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache)
: new ElementStateProvider(Locator, ConditionalWait, Finder);
public virtual IElementStateProvider State => CacheConfiguration.IsEnabled
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache, LogElementState)
: new ElementStateProvider(Locator, ConditionalWait, Finder, LogElementState);

protected virtual IElementCacheHandler Cache
{
Expand Down Expand Up @@ -64,8 +64,16 @@ protected virtual IElementCacheHandler Cache

protected abstract ILocalizedLogger LocalizedLogger { get; }

protected abstract ILocalizationManager LocalizationManager { get; }

protected virtual ILoggerConfiguration LoggerConfiguration => LocalizedLogger.Configuration;

protected virtual Logger Logger => Logger.Instance;

protected virtual LogElementState LogElementState =>
(string messageKey, string stateKey)
=> LocalizedLogger.InfoElementAction(ElementType, Name, messageKey, LocalizationManager.GetLocalizedMessage(stateKey));

public void Click()
{
LogElementAction("loc.clicking");
Expand All @@ -85,18 +93,21 @@ public IList<T> FindChildElements<T>(By childLocator, string name = null, Elemen
public string GetAttribute(string attr)
{
LogElementAction("loc.el.getattr", attr);
return DoWithRetry(() => GetElement().GetAttribute(attr));
var value = DoWithRetry(() => GetElement().GetAttribute(attr));
LogElementAction("loc.el.attr.value", attr, value);

return value;
}

public virtual RemoteWebElement GetElement(TimeSpan? timeout = null)
{
try
{
return CacheConfiguration.IsEnabled
return CacheConfiguration.IsEnabled
? Cache.GetElement(timeout)
: (RemoteWebElement) Finder.FindElement(Locator, elementState, timeout);
}
catch (NoSuchElementException ex)
catch (NoSuchElementException ex) when (LoggerConfiguration.LogPageSource)
{
LogPageSource(ex);
throw;
Expand All @@ -121,7 +132,10 @@ public string Text
get
{
LogElementAction("loc.get.text");
return DoWithRetry(() => GetElement().Text);
var value = DoWithRetry(() => GetElement().Text);
LogElementAction("loc.text.value", value);

return value;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ namespace Aquality.Selenium.Core.Elements
public class ElementStateProvider : IElementStateProvider
{
private readonly By elementLocator;
private readonly LogElementState logElementState;

public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait, IElementFinder elementFinder)
public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait, IElementFinder elementFinder, LogElementState logElementState)
{
this.elementLocator = elementLocator;
ConditionalWait = conditionalWait;
ElementFinder = elementFinder;
this.logElementState = logElementState;
}

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

public bool WaitForDisplayed(TimeSpan? timeout = null)
{
return IsAnyElementFound(timeout, ElementState.Displayed);
return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.Displayed), "displayed");
}

public bool WaitForNotDisplayed(TimeSpan? timeout = null)
{
return ConditionalWait.WaitFor(() => !IsDisplayed, timeout);
return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsDisplayed, timeout), "not.displayed");
}

public bool WaitForExist(TimeSpan? timeout = null)
{
return IsAnyElementFound(timeout, ElementState.ExistsInAnyState);
return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.ExistsInAnyState), "exist");
}

public bool WaitForNotExist(TimeSpan? timeout = null)
{
return ConditionalWait.WaitFor(() => !IsExist, timeout);
return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsExist, timeout), "not.exist");
}

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

public bool WaitForEnabled(TimeSpan? timeout = null)
{
return IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout);
return DoAndLogWaitForState(() => IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout), "enabled");
}

public bool WaitForNotEnabled(TimeSpan? timeout = null)
{
return IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout);
return DoAndLogWaitForState(() => IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout), "not.enabled");
}

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

public void WaitForClickable(TimeSpan? timeout = null)
{
IsElementClickable(timeout, false);
var conditionKey = "loc.el.state.clickable";
try
{
logElementState("loc.wait.for.state", conditionKey);
IsElementClickable(timeout, false);
}
catch
{
logElementState("loc.wait.for.state.failed", conditionKey);
throw;
}
}

private bool IsElementClickable(TimeSpan? timeout, bool catchTimeoutException)
Expand All @@ -97,5 +109,23 @@ private bool IsElementInDesiredCondition(TimeSpan? timeout, DesiredState element
{
return ElementFinder.FindElements(elementLocator, elementStateCondition, timeout).Any();
}

private bool DoAndLogWaitForState(Func<bool> waitingAction, string conditionKeyPart, TimeSpan? timeout = null)
{
if (TimeSpan.Zero == timeout)
{
return waitingAction();
}

var conditionKey = $"loc.el.state.{conditionKeyPart}";
logElementState("loc.wait.for.state", conditionKey);
var result = waitingAction();
if (!result)
{
logElementState("loc.wait.for.state.failed", conditionKey);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Aquality.Selenium.Core.Elements
{
/// <summary>
/// Logs element state.
/// </summary>
/// <param name="messageKey">Key of localized message to log.</param>
/// <param name="stateKey">Key of localized state to log.</param>
public delegate void LogElementState(string messageKey, string stateKey);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Aquality.Selenium.Core.Configurations;
using System;

namespace Aquality.Selenium.Core.Localization
{
Expand All @@ -7,6 +8,11 @@ namespace Aquality.Selenium.Core.Localization
/// </summary>
public interface ILocalizedLogger
{
/// <summary>
/// Gets logger configuration.
/// </summary>
ILoggerConfiguration Configuration { get; }

/// <summary>
/// Logs localized message for action with INFO level which is applied for element, for example, click, send keys etc.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Logging;
using Aquality.Selenium.Core.Utilities;
using System.Linq;
using System.Reflection;

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

public LocalizationManager(ILoggerConfiguration loggerConfiguration, Logger logger, Assembly assembly = null)
{
var language = loggerConfiguration.Language;
localizationFile = new JsonSettingsFile(string.Format(LangResource, language.ToLower()), assembly ?? Assembly.GetExecutingAssembly());
localizationFile = GetLocalizationFile(language, assembly ?? Assembly.GetExecutingAssembly());
coreLocalizationFile = GetLocalizationFile(language, Assembly.GetExecutingAssembly());
this.logger = logger;
}

private static ISettingsFile GetLocalizationFile(string language, Assembly assembly)
{
var embeddedResourceName = string.Format(LangResource, language.ToLower());
var assemblyToUse = assembly.GetManifestResourceNames().Any(name => name.Contains(embeddedResourceName))
? assembly
: Assembly.GetExecutingAssembly();
return new JsonSettingsFile(embeddedResourceName, assemblyToUse);
}

public string GetLocalizedMessage(string messageKey, params object[] args)
{
var jsonKey = $"$['{messageKey}']";
if (localizationFile.IsValuePresent(jsonKey))
var localizationFileToUse = localizationFile.IsValuePresent(jsonKey) ? localizationFile : coreLocalizationFile;
if (localizationFileToUse.IsValuePresent(jsonKey))
{
return string.Format(localizationFile.GetValue<string>(jsonKey), args);
return string.Format(localizationFileToUse.GetValue<string>(jsonKey), args);
}

logger.Debug($"Cannot find localized message by key '{jsonKey}'");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Aquality.Selenium.Core.Logging;
using Aquality.Selenium.Core.Configurations;
using Aquality.Selenium.Core.Logging;
using System;

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

public LocalizedLogger(ILocalizationManager localizationManager, Logger logger)
public LocalizedLogger(ILocalizationManager localizationManager, Logger logger, ILoggerConfiguration configuration)
{
this.localizationManager = localizationManager;
this.logger = logger;
Configuration = configuration;
}

public ILoggerConfiguration Configuration { get; }

public void InfoElementAction(string elementType, string elementName, string messageKey, params object[] args)
{
logger.Info($"{elementType} '{elementName}' :: {localizationManager.GetLocalizedMessage(messageKey, args)}");
Expand Down
Loading

0 comments on commit e2adab1

Please sign in to comment.