From e2adab1b0840af5f6189be733634fb8d35f726ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Thu, 18 Jun 2020 17:39:56 +0200 Subject: [PATCH] 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 --- .../Aquality.Selenium.Core.xml | 17 +++++++ .../Configurations/ILoggerConfiguration.cs | 5 ++ .../Configurations/LoggerConfiguration.cs | 3 ++ .../Elements/CachedElementStateProvider.cs | 21 +++++--- .../Elements/Element.cs | 28 +++++++--- .../Elements/ElementStateProvider.cs | 46 ++++++++++++++--- .../Elements/LogElementState.cs | 9 ++++ .../Localization/ILocalizedLogger.cs | 8 ++- .../Localization/LocalizationManager.cs | 19 +++++-- .../Localization/LocalizedLogger.cs | 8 ++- .../Resources/Localization/be.json | 13 ++++- .../Resources/Localization/en.json | 13 ++++- .../Resources/Localization/ru.json | 13 ++++- .../Resources/settings.json | 3 +- .../Browser/Elements/WebElement.cs | 2 + .../WindowsApp/Elements/WindowElement.cs | 2 + .../WindowsApp/RelativeElementFinderTests.cs | 3 +- .../Aquality.Selenium.Core.Tests.csproj | 2 + .../Configurations/EnvConfigurationTests.cs | 8 +++ .../Localization/LocalizationManagerTests.cs | 51 +++++++++++++++++-- .../Resources/Localization/be.json | 3 ++ .../Utilities/LoggerTests.cs | 41 ++++++++++++++- azure-pipelines.yml | 2 +- 23 files changed, 279 insertions(+), 41 deletions(-) create mode 100644 Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs create mode 100644 Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Resources/Localization/be.json diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml index 065b4bf..803e866 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml @@ -83,6 +83,11 @@ Supported language. + + + Perform page source logging in case of catastrophic failures or not. + + Describes retry configuration. @@ -577,6 +582,13 @@ Child elements state. List of child elements. + + + Logs element state. + + Key of localized message to log. + Key of localized state to log. + Implementation of for a relative supplier @@ -600,6 +612,11 @@ Log messages to different languages + + + Gets logger configuration. + + Logs localized message for action with INFO level which is applied for element, for example, click, send keys etc. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/ILoggerConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/ILoggerConfiguration.cs index 44190f8..f192897 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/ILoggerConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/ILoggerConfiguration.cs @@ -10,5 +10,10 @@ public interface ILoggerConfiguration /// /// Supported language. string Language { get; } + + /// + /// Perform page source logging in case of catastrophic failures or not. + /// + bool LogPageSource { get; } } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/LoggerConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/LoggerConfiguration.cs index 2f80894..afb7ebf 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/LoggerConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/LoggerConfiguration.cs @@ -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; } } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs index 212a4b2..df821f4 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs @@ -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; @@ -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; } @@ -50,12 +51,15 @@ protected virtual bool TryInvokeFunction(Func 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); } @@ -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 condition, string conditionName, TimeSpan? timeout) + protected virtual bool WaitForCondition(Func 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; diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs index 8830206..8f51521 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs @@ -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 { @@ -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"); @@ -85,18 +93,21 @@ public IList FindChildElements(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; @@ -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; } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs index c88647d..4172484 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs @@ -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; } @@ -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) @@ -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) @@ -81,7 +83,17 @@ private bool IsElementInDesiredState(Func 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) @@ -97,5 +109,23 @@ private bool IsElementInDesiredCondition(TimeSpan? timeout, DesiredState element { return ElementFinder.FindElements(elementLocator, elementStateCondition, timeout).Any(); } + + private bool DoAndLogWaitForState(Func 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; + } } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs new file mode 100644 index 0000000..aa1b6a6 --- /dev/null +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs @@ -0,0 +1,9 @@ +namespace Aquality.Selenium.Core.Elements +{ + /// + /// Logs element state. + /// + /// Key of localized message to log. + /// Key of localized state to log. + public delegate void LogElementState(string messageKey, string stateKey); +} diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/ILocalizedLogger.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/ILocalizedLogger.cs index 5f493cf..56406d8 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/ILocalizedLogger.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/ILocalizedLogger.cs @@ -1,4 +1,5 @@ -using System; +using Aquality.Selenium.Core.Configurations; +using System; namespace Aquality.Selenium.Core.Localization { @@ -7,6 +8,11 @@ namespace Aquality.Selenium.Core.Localization /// public interface ILocalizedLogger { + /// + /// Gets logger configuration. + /// + ILoggerConfiguration Configuration { get; } + /// /// Logs localized message for action with INFO level which is applied for element, for example, click, send keys etc. /// diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizationManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizationManager.cs index dd33ab0..4b9a441 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizationManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizationManager.cs @@ -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 @@ -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(jsonKey), args); + return string.Format(localizationFileToUse.GetValue(jsonKey), args); } logger.Debug($"Cannot find localized message by key '{jsonKey}'"); diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizedLogger.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizedLogger.cs index be54cfb..5a06ecb 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizedLogger.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Localization/LocalizedLogger.cs @@ -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 @@ -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)}"); diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/be.json b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/be.json index eb14fbc..0ca45ce 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/be.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/be.json @@ -1,11 +1,22 @@ { "loc.clicking": "Націскаем", "loc.el.getattr": "Атрымліваем атрыбут '{0}'", + "loc.el.attr.value": "Значэнне атрыбута '{0}': [{1}]", "loc.get.text": "Атрымліваем тэкст элемента", + "loc.text.value": "Тэкст элемента: [{0}]", "loc.text.sending.keys": "Націскаем клавішы '{0}'", "loc.no.elements.found.in.state": "Не знайшлі элементаў па лакатару '{0}' у {1} стане", "loc.no.elements.found.by.locator": "Не знайшлі элементаў па лакатару '{0}'", "loc.elements.were.found.but.not.in.state": "Знайшлі элементы па лакатару '{0}', але яны не ў жаданым стане {1}", "loc.elements.found.but.should.not": "Не павінна быць знойдзена элементаў па лакатару '{0}' у {1} стане", - "loc.search.of.elements.failed": "Пошук элемента па лакатару '{0}' прайшоў няўдала" + "loc.search.of.elements.failed": "Пошук элемента па лакатару '{0}' прайшоў няўдала", + "loc.wait.for.state": "Чакаем, пакуль элемент будзе {0}", + "loc.wait.for.state.failed": "Элемент не стаў {0} па заканчэнні часу чакання", + "loc.el.state.displayed": "бачны", + "loc.el.state.not.displayed": "нябачны або адсутны", + "loc.el.state.exist": "прысутны", + "loc.el.state.not.exist": "адсутны", + "loc.el.state.enabled": "даступны", + "loc.el.state.not.enabled": "недаступны", + "loc.el.state.clickable": "даступны для націску" } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/en.json b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/en.json index 624d1c5..35e6faa 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/en.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/en.json @@ -1,11 +1,22 @@ { "loc.clicking": "Clicking", "loc.el.getattr": "Getting attribute '{0}'", + "loc.el.attr.value": "Value of attribute '{0}': [{1}]", "loc.get.text": "Getting text from element", + "loc.text.value": "Element's text: [{0}]", "loc.text.sending.keys": "Sending keys '{0}'", "loc.no.elements.found.in.state": "No elements with locator '{0}' were found in {1} state", "loc.no.elements.found.by.locator": "No elements were found by locator '{0}'", "loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}' but not in desired state {1}", "loc.elements.found.but.should.not": "No elements should be found by locator '{0}' in {1} state", - "loc.search.of.elements.failed": "Search of element by locator '{0}' failed" + "loc.search.of.elements.failed": "Search of element by locator '{0}' failed", + "loc.wait.for.state": "Waiting for element to be {0}", + "loc.wait.for.state.failed": "Element has not become {0} after timeout", + "loc.el.state.displayed": "displayed", + "loc.el.state.not.displayed": "invisible or absent", + "loc.el.state.exist": "exist", + "loc.el.state.not.exist": "absent", + "loc.el.state.enabled": "enabled", + "loc.el.state.not.enabled": "disabled", + "loc.el.state.clickable": "clickable" } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/ru.json b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/ru.json index 7eaff65..f1a2472 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/ru.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/ru.json @@ -1,11 +1,22 @@ { "loc.clicking": "Клик", "loc.el.getattr": "Получение аттрибута '{0}'", + "loc.el.attr.value": "Значение аттрибута '{0}': [{1}]", "loc.get.text": "Получение текста элемента", + "loc.text.value": "Текст элемента: [{0}]", "loc.text.sending.keys": "Нажатие клавиши '{0}'", "loc.no.elements.found.in.state": "Не удалось найти элементов по локатору '{0}' в {1} состоянии", "loc.no.elements.found.by.locator": "Не удалось найти элементов по локатору '{0}'", "loc.elements.were.found.but.not.in.state": "Удалось найти элементы по локатору '{0}', но они не в желаемом состоянии {1}", "loc.elements.found.but.should.not": "Не должно быть найдено элементов по локатору '{0}' в {1} состоянии", - "loc.search.of.elements.failed": "Поиск элемента по локатору '{0}' прошел неудачно" + "loc.search.of.elements.failed": "Поиск элемента по локатору '{0}' прошел неудачно", + "loc.wait.for.state": "Ожидание, пока элемент станет {0}", + "loc.wait.for.state.failed": "Элемент не стал {0} по истечении времени ожидания", + "loc.el.state.displayed": "видимым", + "loc.el.state.not.displayed": "невидимым или остуствующим", + "loc.el.state.exist": "присутствующим", + "loc.el.state.not.exist": "отсутствующим", + "loc.el.state.enabled": "доступным", + "loc.el.state.not.enabled": "недоступным", + "loc.el.state.clickable": "кликабельным" } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json index 2af0782..1220624 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json @@ -10,7 +10,8 @@ "pollingInterval": 300 }, "logger": { - "language": "en" + "language": "en", + "logPageSource": true }, "elementCache": { "isEnabled": false diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElement.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElement.cs index c0e0660..64b9c25 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElement.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElement.cs @@ -29,5 +29,7 @@ protected WebElement(By locator, string name, ElementState state) : base(locator protected override IElementFinder Finder => AqualityServices.ServiceProvider.GetRequiredService(); protected override ILocalizedLogger LocalizedLogger => AqualityServices.ServiceProvider.GetRequiredService(); + + protected override ILocalizationManager LocalizationManager => AqualityServices.ServiceProvider.GetRequiredService(); } } diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/Elements/WindowElement.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/Elements/WindowElement.cs index 155f447..e24bc25 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/Elements/WindowElement.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/Elements/WindowElement.cs @@ -29,5 +29,7 @@ protected WindowElement(By locator, string name) : base(locator, name, ElementSt protected override IElementFinder Finder => AqualityServices.ServiceProvider.GetRequiredService(); protected override ILocalizedLogger LocalizedLogger => AqualityServices.ServiceProvider.GetRequiredService(); + + protected override ILocalizationManager LocalizationManager => AqualityServices.ServiceProvider.GetRequiredService(); } } diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/RelativeElementFinderTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/RelativeElementFinderTests.cs index a46e254..af99b59 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/RelativeElementFinderTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/WindowsApp/RelativeElementFinderTests.cs @@ -22,7 +22,8 @@ public class RelativeElementFinderTests : TestWithApplication private IElementStateProvider GetElementStateProvider(By locator) => new ElementStateProvider( locator, ConditionalWait, - ElementFinder); + ElementFinder, + (messageKey, stateKey) => AqualityServices.ServiceProvider.GetRequiredService().Debug(messageKey, args: stateKey)); [Test] public void Should_FindChildElements_ViaRelativeElementFinder() diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Aquality.Selenium.Core.Tests.csproj b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Aquality.Selenium.Core.Tests.csproj index 6351653..d134aa6 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Aquality.Selenium.Core.Tests.csproj +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Aquality.Selenium.Core.Tests.csproj @@ -8,12 +8,14 @@ + + Never diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Configurations/EnvConfigurationTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Configurations/EnvConfigurationTests.cs index 3ff4be5..0fdd8cb 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Configurations/EnvConfigurationTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Configurations/EnvConfigurationTests.cs @@ -21,6 +21,14 @@ public class EnvConfigurationTests : TestWithoutApplication public void CleanUp() { Environment.SetEnvironmentVariable(ProfileVariableName, null); + Environment.SetEnvironmentVariable("timeouts.timeoutImplicit", null); + Environment.SetEnvironmentVariable("timeouts.timeoutCondition", null); + Environment.SetEnvironmentVariable("timeouts.timeoutPollingInterval", null); + Environment.SetEnvironmentVariable("timeouts.timeoutCommand", null); + Environment.SetEnvironmentVariable("retry.number", null); + Environment.SetEnvironmentVariable("retry.pollingInterval", null); + Environment.SetEnvironmentVariable("logger.language", null); + Environment.SetEnvironmentVariable("elementCache.isEnabled", null); } [Test] diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Localization/LocalizationManagerTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Localization/LocalizationManagerTests.cs index ccb8f30..5e2072a 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Localization/LocalizationManagerTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Localization/LocalizationManagerTests.cs @@ -9,20 +9,34 @@ namespace Aquality.Selenium.Core.Tests.Localization { public sealed class LocalizationManagerTests : TestWithoutApplication { + private const string ClickingKey = "loc.clicking"; + private const string ClickingValueBe = "Націскаем"; + private const string ClickingValueEn = "Clicking"; private static readonly string[] SupportedLanguages = { "be", "en", "ru" }; private static readonly string[] KeysWithoutParams = { - "loc.clicking", - "loc.get.text", + ClickingKey, + "loc.get.text", + "loc.el.state.displayed", + "loc.el.state.not.displayed", + "loc.el.state.exist", + "loc.el.state.not.exist", + "loc.el.state.enabled", + "loc.el.state.not.enabled", + "loc.el.state.clickable" }; private static readonly string[] KeysWithParams = { "loc.el.getattr", + "loc.el.attr.value", + "loc.text.value", "loc.text.sending.keys", "loc.no.elements.found.in.state", "loc.no.elements.found.by.locator", "loc.elements.were.found.but.not.in.state", - "loc.elements.found.but.should.not" + "loc.elements.found.but.should.not", + "loc.wait.for.state", + "loc.wait.for.state.failed" }; [Test] @@ -31,13 +45,13 @@ public void Should_BePossibleTo_UseLocalizationManager_ForClicking_CustomConfig( Environment.SetEnvironmentVariable("profile", "custom"); SetUp(); Environment.SetEnvironmentVariable("profile", string.Empty); - Assert.AreEqual("Націскаем", ServiceProvider.GetService().GetLocalizedMessage("loc.clicking")); + Assert.AreEqual(ClickingValueBe, ServiceProvider.GetService().GetLocalizedMessage(ClickingKey)); } [Test] public void Should_BePossibleTo_UseLocalizationManager_ForClicking() { - Assert.AreEqual("Clicking", ServiceProvider.GetService().GetLocalizedMessage("loc.clicking")); + Assert.AreEqual(ClickingValueEn, ServiceProvider.GetService().GetLocalizedMessage(ClickingKey)); } [Test] @@ -59,6 +73,31 @@ public void Should_ReturnNonKeyValues_AndNotEmptyValues_ForKeysWithoutParams([Va Assert.IsNotEmpty(localizedValue, "Value should not be empty"); } + [Test] + public void Should_ReturnNonKeyValue_ForKeysPresentInCore_IfLanguageMissedInSiblingAssembly() + { + var configuration = new DynamicConfiguration + { + Language = "en" + }; + var localizedValue = new LocalizationManager(configuration, Logger.Instance, GetType().Assembly).GetLocalizedMessage(ClickingKey); + + Assert.AreEqual(ClickingValueEn, localizedValue, "Value should match to expected"); + } + + [Test] + public void Should_ReturnNonKeyValue_ForKeysPresentInCore_IfKeyMissedInSiblingAssembly() + { + + var configuration = new DynamicConfiguration + { + Language = "be" + }; + var localizedValue = new LocalizationManager(configuration, Logger.Instance, GetType().Assembly).GetLocalizedMessage(ClickingKey); + + Assert.AreEqual(ClickingValueBe, localizedValue, "Value should match to expected"); + } + [Test] public void Should_ReturnNonKeyValues_AndNotEmptyValues_ForKeysWithParams([ValueSource(nameof(SupportedLanguages))] string language, [ValueSource(nameof(KeysWithParams))] string key) { @@ -87,6 +126,8 @@ public void Should_ThrowsFormatException_WhenKeysRequireParams([ValueSource(name private class DynamicConfiguration : ILoggerConfiguration { public string Language { get; set; } + + public bool LogPageSource => throw new NotImplementedException(); } } } diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Resources/Localization/be.json b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Resources/Localization/be.json new file mode 100644 index 0000000..0ab3d26 --- /dev/null +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Resources/Localization/be.json @@ -0,0 +1,3 @@ +{ + "loc.somekey": "Нешта" +} \ No newline at end of file diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Utilities/LoggerTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Utilities/LoggerTests.cs index 515d0c7..5ea7b4e 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Utilities/LoggerTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Utilities/LoggerTests.cs @@ -1,17 +1,22 @@ using Aquality.Selenium.Core.Logging; +using Aquality.Selenium.Core.Tests.Applications.Browser; +using Aquality.Selenium.Core.Tests.Applications.Browser.Elements; +using Aquality.Selenium.Core.Tests.Applications.WindowsApp.Elements; using NLog.Targets; using NUnit.Framework; +using OpenQA.Selenium; using System; using System.IO; namespace Aquality.Selenium.Core.Tests.Utilities { [NonParallelizable] - public class LoggerTests + public class LoggerTests : TestWithBrowser { private const string AddTargetLogFile = "AddTargetTestLog.log"; private const string RemoveTargetLogFile = "RemoveTargetTestLog.log"; private const string TestMessage = "test message"; + private const string LogPageSourceEnvironmentVariable = "logger.logPageSource"; [SetUp] public void Setup() @@ -20,6 +25,40 @@ public void Setup() File.Delete(RemoveTargetLogFile); } + [TearDown] + public void TearDown() + { + Environment.SetEnvironmentVariable(LogPageSourceEnvironmentVariable, null); + } + + [Test] + public void Should_LogPageSource_WhenIsEnabledAndElementAbsent() + { + File.Delete(AddTargetLogFile); + Environment.SetEnvironmentVariable(LogPageSourceEnvironmentVariable, true.ToString()); + Logger.Instance.AddTarget(GetTarget(AddTargetLogFile)); + var element = new Label(By.Name("Absent element"), "Absent element", Elements.ElementState.ExistsInAnyState); + Assert.Throws(() => element.GetElement(TimeSpan.Zero), "Attempt to get absent element should throw an exception"); + Assert.True(File.Exists(AddTargetLogFile), + $"Target wasn't added. File '{AddTargetLogFile}' doesn't exist."); + var log = File.ReadAllText(AddTargetLogFile).Trim(); + StringAssert.Contains("Page source:", log, "Log file should contain logged page source"); + } + + [Test] + public void Should_NotLogPageSource_AndThrowException_WhenIsDisabledAndElementAbsent() + { + File.Delete(AddTargetLogFile); + Environment.SetEnvironmentVariable(LogPageSourceEnvironmentVariable, false.ToString()); + Logger.Instance.AddTarget(GetTarget(AddTargetLogFile)); + var element = new Label(By.Name("Absent element"), "Absent element", Elements.ElementState.ExistsInAnyState); + Assert.Throws(() => element.GetElement(TimeSpan.Zero), "Attempt to get absent element should throw an exception"); + Assert.True(File.Exists(AddTargetLogFile), + $"Target wasn't added. File '{AddTargetLogFile}' doesn't exist."); + var log = File.ReadAllText(AddTargetLogFile).Trim(); + StringAssert.DoesNotContain("Page source:", log, "Log file should not contain logged page source"); + } + [Test] public void Should_BePossibleTo_AddTarget() { diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aef3173..2e2af10 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -44,7 +44,7 @@ stages: displayName: Run tests variables: - webDriverVersion: '80.0.3987.106' + webDriverVersion: '83.0.4103.39' isRemote: true steps: