From cdc23b0092a4157712338593a1caaa2ca34602d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Thu, 27 May 2021 20:19:51 +0300 Subject: [PATCH 01/12] - created form interface and implementation - implemented DumpManager to save the dump and compare with it - moved VisualStateProvider to Visualization namespace - moved LogElementState delegates to Logging namespace --- .../IVisualizationConfiguration.cs | 5 + .../VisualizationConfiguration.cs | 10 ++ .../Elements/CachedElementStateProvider.cs | 1 + .../Elements/Element.cs | 2 +- .../Elements/ElementStateProvider.cs | 1 + .../Elements/Interfaces/IElement.cs | 10 +- .../src/Aquality.Selenium.Core/Forms/Form.cs | 80 +++++++++++++++ .../src/Aquality.Selenium.Core/Forms/IForm.cs | 22 +++++ .../{Elements => Logging}/LogElementState.cs | 2 +- .../Resources/settings.json | 3 +- .../Visualization/DumpManager.cs | 98 +++++++++++++++++++ .../Visualization/IDumpManager.cs | 27 +++++ .../IVisualStateProvider.cs | 2 +- .../VisualStateProvider.cs | 5 +- 14 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs create mode 100644 Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs rename Aquality.Selenium.Core/src/Aquality.Selenium.Core/{Elements => Logging}/LogElementState.cs (93%) create mode 100644 Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs create mode 100644 Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs rename Aquality.Selenium.Core/src/Aquality.Selenium.Core/{Elements/Interfaces => Visualization}/IVisualStateProvider.cs (96%) rename Aquality.Selenium.Core/src/Aquality.Selenium.Core/{Elements => Visualization}/VisualStateProvider.cs (94%) diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/IVisualizationConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/IVisualizationConfiguration.cs index 8901d5e..3d85ce9 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/IVisualizationConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/IVisualizationConfiguration.cs @@ -19,5 +19,10 @@ public interface IVisualizationConfiguration /// Height of the image resized for comparison. /// int ComparisonHeight { get; } + + /// + /// Path used to save and load page dumps. + /// + string PathToDumps { get; } } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs index 61055ba..18eccf9 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs @@ -1,4 +1,5 @@ using Aquality.Selenium.Core.Utilities; +using System.IO; namespace Aquality.Selenium.Core.Configurations { @@ -24,5 +25,14 @@ public VisualizationConfiguration(ISettingsFile settingsFile) public int ComparisonWidth => settingsFile.GetValueOrDefault(".visualization.comparisonWidth", 16); public int ComparisonHeight => settingsFile.GetValueOrDefault(".visualization.comparisonHeight", 16); + + public string PathToDumps + { + get + { + var pathInConfiguration = settingsFile.GetValueOrDefault(".visualization.pathToDumps", "./Resources/Dumps/"); + return pathInConfiguration.Contains(".") ? Path.GetFullPath(pathInConfiguration) : pathInConfiguration; + } + } } } 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 5890c9a..59f7761 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/CachedElementStateProvider.cs @@ -1,4 +1,5 @@ using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Waitings; using OpenQA.Selenium; using System; 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 221a861..300613d 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs @@ -18,7 +18,7 @@ namespace Aquality.Selenium.Core.Elements /// public abstract class Element : IElement { - private readonly ElementState elementState; + internal readonly ElementState elementState; private IElementCacheHandler elementCacheHandler; protected Element(By locator, string name, ElementState state) 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 4172484..234be90 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs @@ -1,4 +1,5 @@ using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Waitings; using OpenQA.Selenium; using System; diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElement.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElement.cs index dde98c5..61613f3 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElement.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElement.cs @@ -1,4 +1,5 @@ -using OpenQA.Selenium; +using Aquality.Selenium.Core.Visualization; +using OpenQA.Selenium; using OpenQA.Selenium.Remote; using System; @@ -27,6 +28,11 @@ public interface IElement : IParent /// Instance of IElementStateProvider State { get; } + /// + /// Gets element visual state. + /// + IVisualStateProvider Visual { get; } + /// /// Finds current element by specified /// @@ -43,7 +49,7 @@ public interface IElement : IParent /// /// Gets element attribute value by its name. /// - /// Name of attrbiute + /// Name of attribute /// Value of element attribute. string GetAttribute(string attr); diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs new file mode 100644 index 0000000..893f4e3 --- /dev/null +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs @@ -0,0 +1,80 @@ +using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Visualization; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Aquality.Selenium.Core.Forms +{ + /// + /// Describes form that could be used for visualization purposes (see ). + /// + /// Base type(class or interface) of elements of this form. + public abstract class Form : IForm where T : IElement + { + /// + /// Name of the current form. + /// + public abstract string Name { get; } + + /// + /// Visualization configuration used by . + /// Could be get from AqualityServices. + /// + protected abstract IVisualizationConfiguration VisualizationConfiguration { get; } + + /// + /// Localizer logger used by . + /// Could be get from AqualityServices. + /// + protected abstract ILocalizedLogger LocalizedLogger { get; } + + /// + /// Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps. + /// Uses as basis for dump creation and comparison. + /// + public virtual IDumpManager Dump => new DumpManager(ElementsForVisualization, VisualizationConfiguration, LocalizedLogger); + + /// + /// List of pairs uniqueName-element to be used for dump saving and comparing. + /// By default, only currently displayed elements to be used (). + /// You can override this property with defined , or your own element set. + /// + protected virtual IDictionary ElementsForVisualization => DisplayedElements; + + /// + /// List of pairs uniqueName-element from all fields and properties of type . + /// + protected IDictionary AllElements + { + get + { + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var elementFields = GetType().GetFields(bindingFlags).Where(field => typeof(T).IsAssignableFrom(field.FieldType)) + .ToDictionary(field => field.Name, field => (T) field.GetValue(this)); + var elementProperties = GetType().GetProperties(bindingFlags).Where(property => typeof(T).IsAssignableFrom(property.PropertyType)) + .ToDictionary(property => property.Name, property => (T) property.GetValue(this)); + return elementFields.Concat(elementProperties) + .ToDictionary(el => el.Key, el => el.Value); + } + } + + /// + /// List of pairs uniqueName-element from all fields and properties of type , + /// which were initialized as . + /// + protected IDictionary ElementsInitializedAsDisplayed => AllElements + .Where(element => element.Value is Element && (element.Value as Element).elementState == ElementState.Displayed) + .ToDictionary(el => el.Key, el => el.Value); + + /// + /// List of pairs uniqueName-element from all fields and properties of type , + /// which are currently displayed (using ). + /// + protected IDictionary DisplayedElements => AllElements.Where(element => element.Value.State.IsDisplayed) + .ToDictionary(el => el.Key, el => el.Value); + } +} diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs new file mode 100644 index 0000000..5744403 --- /dev/null +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs @@ -0,0 +1,22 @@ +using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Visualization; + +namespace Aquality.Selenium.Core.Forms +{ + /// + /// Describes form that could be used for visualization purposes, such as saving and comparing dumps. + /// + public interface IForm + { + /// + /// Name of the current form. + /// + string Name { get; } + + /// + /// Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps. + /// + IDumpManager Dump { get; } + } +} diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Logging/LogElementState.cs similarity index 93% rename from Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs rename to Aquality.Selenium.Core/src/Aquality.Selenium.Core/Logging/LogElementState.cs index f7698ac..96acff6 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Logging/LogElementState.cs @@ -1,4 +1,4 @@ -namespace Aquality.Selenium.Core.Elements +namespace Aquality.Selenium.Core.Logging { /// /// Logs element state. 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 c47f6ad..5777a45 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json @@ -19,6 +19,7 @@ "visualization": { "defaultThreshold": 0.012, "comparisonWidth": 16, - "comparisonHeight": 16 + "comparisonHeight": 16, + "pathToDumps": "./Resources/Dumps/" } } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs new file mode 100644 index 0000000..72633fb --- /dev/null +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs @@ -0,0 +1,98 @@ +using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Localization; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; + +namespace Aquality.Selenium.Core.Visualization +{ + public class DumpManager : IDumpManager where T : IElement + { + private const string ImageFormat = ".png"; + public DumpManager(IDictionary elementsForVisualization, IVisualizationConfiguration visualizationConfiguration, ILocalizedLogger localizedLogger) + { + ElementsForVisualization = elementsForVisualization; + VisualizationConfiguration = visualizationConfiguration; + LocalizedLogger = localizedLogger; + } + + protected IDictionary ElementsForVisualization { get; } + + protected IVisualizationConfiguration VisualizationConfiguration { get; } + + protected ILocalizedLogger LocalizedLogger { get; } + + protected string DumpsDirectory => VisualizationConfiguration.PathToDumps; + + public virtual float CompareWithDump(string dumpName) + { + var directory = GetDumpDirectory(dumpName); + LocalizedLogger.Info("loc.visualization.dump.compare", dumpName); + if (!directory.Exists) + { + throw new InvalidOperationException($"Dump directory [{directory.FullName}] does not exist."); + } + var imageFiles = directory.GetFiles($"*{ImageFormat}"); + if (imageFiles.Length == 0) + { + throw new InvalidOperationException($"Dump directory [{directory.FullName}] does not contain any [*{ImageFormat}] files."); + } + var existingElements = ElementsForVisualization.Where(element => element.Value.State.IsExist) + .ToDictionary(el => el.Key, el => el.Value); + var countOfUnproceededElements = existingElements.Count(); + var countOfProceededElements = 0; + var comparisonResult = 0f; + foreach (var imageFile in imageFiles) + { + var key = imageFile.Name.Replace(ImageFormat, string.Empty); + if (!existingElements.ContainsKey(key)) + { + LocalizedLogger.Warn("loc.visualization.dump.keynotfound", key); + countOfUnproceededElements++; + } + else + { + comparisonResult += existingElements[key].Visual.GetDifference(Image.FromFile(imageFile.FullName)); + countOfUnproceededElements--; + countOfProceededElements++; + } + } + // adding of countOfUnproceededElements means 100% difference for each element absent in dump or on page + return (comparisonResult + countOfUnproceededElements) / (countOfProceededElements + countOfUnproceededElements); + } + + public virtual void SaveDump(string dumpName) + { + var directory = CleanUpAndGetDumpDirectory(dumpName); + ElementsForVisualization.Where(element => element.Value.State.IsExist).ToList() + .ForEach(element => element.Value.Visual.Image.Save($"{element.Key}.png")); + } + + protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName) + { + var dirInfo = GetDumpDirectory(dumpName); + if (dirInfo.Exists) + { + foreach (var file in dirInfo.EnumerateFiles()) + { + file.Delete(); + } + foreach (var dir in dirInfo.EnumerateDirectories()) + { + dir.Delete(true); + } + } + else + { + dirInfo.Create(); + } + + return dirInfo; + } + + private DirectoryInfo GetDumpDirectory(string dumpName) => new DirectoryInfo(Path.Combine(DumpsDirectory, dumpName)); + } +} diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs new file mode 100644 index 0000000..0b332eb --- /dev/null +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs @@ -0,0 +1,27 @@ +using Aquality.Selenium.Core.Configurations; + +namespace Aquality.Selenium.Core.Visualization +{ + /// + /// Describes dump manager for the form that could be used for visualization purposes, such as saving and comparing dumps. + /// + public interface IDumpManager + { + /// + /// Compares current form with the dump saved previously. + /// + /// Custom name of the sub-folder where the dump was saved. + /// Form name is used by default. + /// The difference of comparing the page to the dump as a percentage (no difference is 0%). + /// Calculated as sum of element differences divided by elements count. + float CompareWithDump(string dumpName); + + /// + /// Saves the dump of the current form + /// (a set of screenshots of selected form elements) + /// into dump folder under the path . + /// + /// Name of the sub-folder where to save the dump. Form name is used by default. + void SaveDump(string dumpName); + } +} \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IVisualStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs similarity index 96% rename from Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IVisualStateProvider.cs rename to Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs index 364f435..685455b 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IVisualStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs @@ -2,7 +2,7 @@ using Aquality.Selenium.Core.Visualization; using System.Drawing; -namespace Aquality.Selenium.Core.Elements.Interfaces +namespace Aquality.Selenium.Core.Visualization { /// /// Provides visual state of the element. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/VisualStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs similarity index 94% rename from Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/VisualStateProvider.cs rename to Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs index 37cf9cb..12398ef 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/VisualStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs @@ -1,12 +1,11 @@ -using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Utilities; -using Aquality.Selenium.Core.Visualization; using OpenQA.Selenium.Remote; using System; using System.Drawing; using System.Globalization; -namespace Aquality.Selenium.Core.Elements +namespace Aquality.Selenium.Core.Visualization { public class VisualStateProvider : IVisualStateProvider { From f975637a7c5a04f819d5fb5b621669aa104bdcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Fri, 28 May 2021 13:31:54 +0300 Subject: [PATCH 02/12] Fixed coding issues in DumpManager --- .../src/Aquality.Selenium.Core/Visualization/DumpManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs index 72633fb..1009e7e 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs @@ -42,7 +42,7 @@ public virtual float CompareWithDump(string dumpName) } var existingElements = ElementsForVisualization.Where(element => element.Value.State.IsExist) .ToDictionary(el => el.Key, el => el.Value); - var countOfUnproceededElements = existingElements.Count(); + var countOfUnproceededElements = existingElements.Count; var countOfProceededElements = 0; var comparisonResult = 0f; foreach (var imageFile in imageFiles) @@ -68,7 +68,7 @@ public virtual void SaveDump(string dumpName) { var directory = CleanUpAndGetDumpDirectory(dumpName); ElementsForVisualization.Where(element => element.Value.State.IsExist).ToList() - .ForEach(element => element.Value.Visual.Image.Save($"{element.Key}.png")); + .ForEach(element => element.Value.Visual.Image.Save(Path.Combine(directory.FullName, $"{element.Key}.png"))); } protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName) From 5860ba80b5278920d3890d103a9f9399c9c2317f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Fri, 28 May 2021 17:00:03 +0300 Subject: [PATCH 03/12] Add first FormDumpTest, corrected DumpManager, Form and VisualStateProvider --- .../Aquality.Selenium.Core.xml | 155 +++++++++++++++--- .../src/Aquality.Selenium.Core/Forms/Form.cs | 2 +- .../Visualization/DumpManager.cs | 27 ++- .../Visualization/IDumpManager.cs | 10 +- .../Visualization/VisualStateProvider.cs | 8 +- .../Browser/Elements/WebElement.cs | 2 +- .../Visualization/FormDumpTests.cs | 119 ++++++++++++++ 7 files changed, 281 insertions(+), 42 deletions(-) create mode 100644 Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs 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 80ece39..7a0f8dd 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 @@ -150,6 +150,11 @@ Height of the image resized for comparison. + + + Path used to save and load page dumps. + + Provides logger configuration @@ -344,6 +349,11 @@ Instance of + + + Gets element visual state. + + Finds current element by specified @@ -361,7 +371,7 @@ Gets element attribute value by its name. - Name of attrbiute + Name of attribute Value of element attribute. @@ -614,54 +624,77 @@ Child elements state. List of child elements. - + - Provides visual state of the element. + Implementation of for a relative supplier - + - Gets a size object containing the height and width of this element. + Describes form that could be used for visualization purposes (see ). + Base type(class or interface) of elements of this form. - + - Gets a point object containing the coordinates of the upper-left - corner of this element relative to the upper-left corner of the page. + Name of the current form. - + - Gets an image containing the screenshot of the element. + Visualization configuration used by . + Could be get from AqualityServices. - + - Gets the difference between the image of the element and the provided image - using + Localizer logger used by . + Could be get from AqualityServices. - The image to compare the element with. - How big a difference will be ignored as a percentage - value between 0 and 1. - If the value is null, the default value is got from . - The difference between the two images as a percentage - value between 0 and 1. - + - Logs element state. + Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps. + Uses as basis for dump creation and comparison. - Key of localized message to log. - Key of localized state to log. - + - Logs element visual state. + List of pairs uniqueName-element to be used for dump saving and comparing. + By default, only currently displayed elements to be used (). + You can override this property with defined , or your own element set. - Key of localized message to log. - Values to put into localized message (if any). - + - Implementation of for a relative supplier + List of pairs uniqueName-element from all fields and properties of type . + + + + + List of pairs uniqueName-element from all fields and properties of type , + which were initialized as . + + + + + List of pairs uniqueName-element from all fields and properties of type , + which are currently displayed (using ). + + + + + Describes form that could be used for visualization purposes, such as saving and comparing dumps. + + + + + Name of the current form. + + + + + Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps. @@ -733,6 +766,20 @@ Exception, gets null value by default. Arguments, which will be provided to template of localized message. + + + Logs element state. + + Key of localized message to log. + Key of localized state to log. + + + + Logs element visual state. + + Key of localized message to log. + Values to put into localized message (if any). + This class is using for a creating extended log. It implements a Singleton pattern. @@ -1067,6 +1114,29 @@ Default value. default(T) if not specified. Value or defaultValue or default(T) + + + Describes dump manager for the form that could be used for visualization purposes, such as saving and comparing dumps. + + + + + Compares current form with the dump saved previously. + + Custom name of the sub-folder where the dump was saved. + Form name is used by default. + The difference of comparing the page to the dump as a percentage (no difference is 0%). + Calculated as sum of element differences divided by elements count. + + + + Saves the dump of the current form + (a set of screenshots of selected form elements) + into dump folder under the path . + + Name of the sub-folder where to save the dump. + Form name is used by default. + Compares images with defined threshold. @@ -1150,6 +1220,37 @@ A gray scale version of the image See http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale for more details + + + Provides visual state of the element. + + + + + Gets a size object containing the height and width of this element. + + + + + Gets a point object containing the coordinates of the upper-left + corner of this element relative to the upper-left corner of the page. + + + + + Gets an image containing the screenshot of the element. + + + + + Gets the difference between the image of the element and the provided image + using + + The image to compare the element with. + How big a difference will be ignored as a percentage - value between 0 and 1. + If the value is null, the default value is got from . + The difference between the two images as a percentage - value between 0 and 1. + Selenium screenshot extensions. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs index 893f4e3..bb8b88c 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs @@ -36,7 +36,7 @@ public abstract class Form : IForm where T : IElement /// Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps. /// Uses as basis for dump creation and comparison. /// - public virtual IDumpManager Dump => new DumpManager(ElementsForVisualization, VisualizationConfiguration, LocalizedLogger); + public virtual IDumpManager Dump => new DumpManager(ElementsForVisualization, Name, VisualizationConfiguration, LocalizedLogger); /// /// List of pairs uniqueName-element to be used for dump saving and comparing. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs index 1009e7e..659e57a 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs @@ -12,25 +12,28 @@ namespace Aquality.Selenium.Core.Visualization public class DumpManager : IDumpManager where T : IElement { private const string ImageFormat = ".png"; - public DumpManager(IDictionary elementsForVisualization, IVisualizationConfiguration visualizationConfiguration, ILocalizedLogger localizedLogger) + public DumpManager(IDictionary elementsForVisualization, string formName, IVisualizationConfiguration visualizationConfiguration, ILocalizedLogger localizedLogger) { ElementsForVisualization = elementsForVisualization; + FormName = formName; VisualizationConfiguration = visualizationConfiguration; LocalizedLogger = localizedLogger; } protected IDictionary ElementsForVisualization { get; } + protected string FormName { get; } + protected IVisualizationConfiguration VisualizationConfiguration { get; } protected ILocalizedLogger LocalizedLogger { get; } protected string DumpsDirectory => VisualizationConfiguration.PathToDumps; - public virtual float CompareWithDump(string dumpName) + public virtual float CompareWithDump(string dumpName = null) { var directory = GetDumpDirectory(dumpName); - LocalizedLogger.Info("loc.visualization.dump.compare", dumpName); + LocalizedLogger.Info("loc.visualization.dump.compare", directory.Name); if (!directory.Exists) { throw new InvalidOperationException($"Dump directory [{directory.FullName}] does not exist."); @@ -64,14 +67,14 @@ public virtual float CompareWithDump(string dumpName) return (comparisonResult + countOfUnproceededElements) / (countOfProceededElements + countOfUnproceededElements); } - public virtual void SaveDump(string dumpName) + public virtual void SaveDump(string dumpName = null) { var directory = CleanUpAndGetDumpDirectory(dumpName); ElementsForVisualization.Where(element => element.Value.State.IsExist).ToList() .ForEach(element => element.Value.Visual.Image.Save(Path.Combine(directory.FullName, $"{element.Key}.png"))); } - protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName) + protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName = null) { var dirInfo = GetDumpDirectory(dumpName); if (dirInfo.Exists) @@ -93,6 +96,18 @@ protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName) return dirInfo; } - private DirectoryInfo GetDumpDirectory(string dumpName) => new DirectoryInfo(Path.Combine(DumpsDirectory, dumpName)); + protected virtual DirectoryInfo GetDumpDirectory(string dumpName = null) + { + const int maxNameLenght = 40; + var invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); + var name = dumpName ?? FormName; + foreach (var character in invalid) + { + name = name.Replace(character, ' '); + } + name = name.Length > maxNameLenght ? name.Substring(0, maxNameLenght) : name; + + return new DirectoryInfo(Path.Combine(DumpsDirectory, name)); + } } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs index 0b332eb..3699574 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs @@ -1,4 +1,5 @@ using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Forms; namespace Aquality.Selenium.Core.Visualization { @@ -11,17 +12,18 @@ public interface IDumpManager /// Compares current form with the dump saved previously. /// /// Custom name of the sub-folder where the dump was saved. - /// Form name is used by default. + /// Form name is used by default. /// The difference of comparing the page to the dump as a percentage (no difference is 0%). /// Calculated as sum of element differences divided by elements count. - float CompareWithDump(string dumpName); + float CompareWithDump(string dumpName = null); /// /// Saves the dump of the current form /// (a set of screenshots of selected form elements) /// into dump folder under the path . /// - /// Name of the sub-folder where to save the dump. Form name is used by default. - void SaveDump(string dumpName); + /// Name of the sub-folder where to save the dump. + /// Form name is used by default. + void SaveDump(string dumpName = null); } } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs index 12398ef..e9e6cdd 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs @@ -1,7 +1,9 @@ using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Utilities; +using OpenQA.Selenium; using OpenQA.Selenium.Remote; using System; +using System.Collections.Generic; using System.Drawing; using System.Globalization; @@ -27,12 +29,12 @@ public VisualStateProvider(IImageComparator imageComparator, IElementActionRetri public Point Location => GetLoggedValue(nameof(Location), element => element.Location); public Image Image - => GetLoggedValue(nameof(Image), element => element.GetScreenshot().AsImage(), image => image.Size.ToString()); + => GetLoggedValue(nameof(Image), element => element.GetScreenshot().AsImage(), image => image.Size.ToString(), new[] { typeof(WebDriverException) }); - private T GetLoggedValue(string name, Func getValue, Func toString = null) + private T GetLoggedValue(string name, Func getValue, Func toString = null, IEnumerable handledExceptions = null) { logVisualState($"loc.el.visual.get{name.ToLower()}"); - var value = actionRetrier.DoWithRetry(() => getValue(getElement())); + var value = actionRetrier.DoWithRetry(() => getValue(getElement()), handledExceptions); logVisualState($"loc.el.visual.{name.ToLower()}.value", toString == null ? value.ToString() : toString(value)); return value; } 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 eeb2c11..6d09e92 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 @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using OpenQA.Selenium; -namespace Aquality.Selenium.Core.Tests.Applications.Browser +namespace Aquality.Selenium.Core.Tests.Applications.Browser.Elements { public abstract class WebElement : Element { diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs new file mode 100644 index 0000000..9ad52bb --- /dev/null +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -0,0 +1,119 @@ +using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Forms; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Tests.Applications.Browser; +using Aquality.Selenium.Core.Tests.Applications.Browser.Elements; +using Aquality.Selenium.Core.Visualization; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using OpenQA.Selenium; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Aquality.Selenium.Core.Tests.Visualization +{ + public class FormDumpTests : TestWithBrowser + { + + private static readonly Uri HoversURL = new Uri($"{TestSite}/hovers"); + + private readonly WebForm form = new WebForm(); + + private string PathToDumps => AqualityServices.ServiceProvider.GetRequiredService().PathToDumps; + + [SetUp] + public new void SetUp() + { + AqualityServices.Application.Driver.Navigate().GoToUrl(HoversURL); + } + + [Test] + public void Should_BePossibleTo_SaveFormDump_WithCustomName() + { + var pathToDump = + new DirectoryInfo(Path.Combine(PathToDumps, form.Name.Replace("/", " "))); + pathToDump.Delete(true); + form.WaitUntilPresent(); + Assert.AreEqual(0, pathToDump.Exists ? pathToDump.GetFiles().Length : 0, "Dump directory should not contain any files before saving"); + Assert.DoesNotThrow(() => form.Dump.SaveDump()); + pathToDump.Refresh(); + DirectoryAssert.Exists(pathToDump); + Assert.Greater(pathToDump.GetFiles().Length, 0, "Dump should contain some files"); + } + + private class WebForm : Form + { + private static readonly By ContentLoc = By.XPath("//div[contains(@class,'example')]"); + private static readonly By HiddenElementsLoc = By.XPath("//h5"); + private static readonly By DisplayedElementsLoc = By.XPath("//img[@alt='User Avatar']"); + + private readonly Label displayedLabel = ElementFactory.GetCustomElement( + (loc, name, state) => new Label(loc, name, state), + DisplayedElementsLoc, "I'm displayed"); + private readonly Label displayedButInitializedAsExist + = new Label(DisplayedElementsLoc, "I'm displayed but initialized as existing", ElementState.ExistsInAnyState); + private Label DisplayedLabel + => new Label(DisplayedElementsLoc, "I'm displayed", ElementState.Displayed); + private Label HiddenLabel + => new Label(HiddenElementsLoc, "I'm hidden", ElementState.ExistsInAnyState); + private Label HiddenLabelInitializedAsDisplayed + => new Label(HiddenElementsLoc, "I'm hidden but mask as displayed", ElementState.Displayed); + + private Label ContentLabel => new Label(ContentLoc, "Content", ElementState.Displayed); + private Label ContentDuplicateLabel => new Label(ContentLoc, "Content", ElementState.Displayed); + + + private IDictionary elementsToCheck = null; + public override string Name => "Web page/form"; + private static IElementFactory ElementFactory => AqualityServices.ServiceProvider.GetRequiredService(); + + public WebForm() + { + } + + public void WaitUntilPresent() => DisplayedLabel.State.WaitForClickable(); + + public void SetElementsForDump(ElementsFilter filter) + { + switch (filter) + { + case ElementsFilter.ElementsInitializedAsDisplayed: + elementsToCheck = ElementsInitializedAsDisplayed; + break; + case ElementsFilter.AllElements: + elementsToCheck = AllElements; + break; + case ElementsFilter.DisplayedElements: + elementsToCheck = DisplayedElements; + break; + } + } + + protected override IDictionary ElementsForVisualization + { + get + { + if (elementsToCheck == null) + { + elementsToCheck = base.ElementsForVisualization; + } + return elementsToCheck; + } + } + + protected override IVisualizationConfiguration VisualizationConfiguration => AqualityServices.ServiceProvider.GetRequiredService(); + + protected override ILocalizedLogger LocalizedLogger => AqualityServices.ServiceProvider.GetRequiredService(); + + public enum ElementsFilter + { + ElementsInitializedAsDisplayed, + AllElements, + DisplayedElements + } + } + } +} From d554b229a4b6b2f5c7763caef2c872af9cf26e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Sat, 29 May 2021 02:45:55 +0300 Subject: [PATCH 04/12] Fix logging bug in ElementStateProvider --- .../Elements/ElementStateProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 234be90..2ae6939 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementStateProvider.cs @@ -34,22 +34,22 @@ public ElementStateProvider(By elementLocator, IConditionalWait conditionalWait, public bool WaitForDisplayed(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.Displayed), "displayed"); + return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.Displayed), "displayed", timeout); } public bool WaitForNotDisplayed(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsDisplayed, timeout), "not.displayed"); + return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsDisplayed, timeout), "not.displayed", timeout); } public bool WaitForExist(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.ExistsInAnyState), "exist"); + return DoAndLogWaitForState(() => IsAnyElementFound(timeout, ElementState.ExistsInAnyState), "exist", timeout); } public bool WaitForNotExist(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsExist, timeout), "not.exist"); + return DoAndLogWaitForState(() => ConditionalWait.WaitFor(() => !IsExist, timeout), "not.exist", timeout); } private bool IsAnyElementFound(TimeSpan? timeout, ElementState state) @@ -59,12 +59,12 @@ private bool IsAnyElementFound(TimeSpan? timeout, ElementState state) public bool WaitForEnabled(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout), "enabled"); + return DoAndLogWaitForState(() => IsElementInDesiredState(element => IsElementEnabled(element), "ENABLED", timeout), "enabled", timeout); } public bool WaitForNotEnabled(TimeSpan? timeout = null) { - return DoAndLogWaitForState(() => IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout), "not.enabled"); + return DoAndLogWaitForState(() => IsElementInDesiredState(element => !IsElementEnabled(element), "NOT ENABLED", timeout), "not.enabled", timeout); } protected virtual bool IsElementEnabled(IWebElement element) From 180446ecf2af3f6af7c62546adf6686fcb63f325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Sat, 29 May 2021 06:25:17 +0300 Subject: [PATCH 05/12] Add more tests, reworked elements selecting in form, add localization messages, reworked dump saving and comparing --- .gitignore | 1 + .../Aquality.Selenium.Core.xml | 4 +- .../VisualizationConfiguration.cs | 2 +- .../src/Aquality.Selenium.Core/Forms/Form.cs | 9 ++- .../src/Aquality.Selenium.Core/Forms/IForm.cs | 4 +- .../Resources/Localization/be.json | 14 ++-- .../Resources/Localization/en.json | 8 ++- .../Resources/Localization/ru.json | 8 ++- .../Resources/settings.json | 2 +- .../Visualization/DumpManager.cs | 34 ++++++++-- .../Visualization/IDumpManager.cs | 4 +- .../Visualization/IVisualStateProvider.cs | 1 - .../Visualization/VisualStateProvider.cs | 8 +-- .../Visualization/FormDumpTests.cs | 66 +++++++++++++++---- 14 files changed, 124 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 2ee2263..8b10aff 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ msbuild.wrn .vs/ Log/ +Dumps/ 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 7a0f8dd..bf4ab67 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 @@ -1119,7 +1119,7 @@ Describes dump manager for the form that could be used for visualization purposes, such as saving and comparing dumps. - + Compares current form with the dump saved previously. @@ -1128,7 +1128,7 @@ The difference of comparing the page to the dump as a percentage (no difference is 0%). Calculated as sum of element differences divided by elements count. - + Saves the dump of the current form (a set of screenshots of selected form elements) diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs index 18eccf9..bb41597 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs @@ -30,7 +30,7 @@ public string PathToDumps { get { - var pathInConfiguration = settingsFile.GetValueOrDefault(".visualization.pathToDumps", "./Resources/Dumps/"); + var pathInConfiguration = settingsFile.GetValueOrDefault(".visualization.pathToDumps", "../../../Resources/Dumps/"); return pathInConfiguration.Contains(".") ? Path.GetFullPath(pathInConfiguration) : pathInConfiguration; } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs index bb8b88c..4cc9296 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs @@ -3,6 +3,7 @@ using Aquality.Selenium.Core.Elements.Interfaces; using Aquality.Selenium.Core.Localization; using Aquality.Selenium.Core.Visualization; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -53,10 +54,12 @@ protected IDictionary AllElements get { const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - var elementFields = GetType().GetFields(bindingFlags).Where(field => typeof(T).IsAssignableFrom(field.FieldType)) - .ToDictionary(field => field.Name, field => (T) field.GetValue(this)); var elementProperties = GetType().GetProperties(bindingFlags).Where(property => typeof(T).IsAssignableFrom(property.PropertyType)) - .ToDictionary(property => property.Name, property => (T) property.GetValue(this)); + .ToDictionary(property => property.Name, property => (T)property.GetValue(this)); + var elementFields = GetType().GetFields(bindingFlags).Where(field => typeof(T).IsAssignableFrom(field.FieldType)) + .ToDictionary(field => elementProperties.Keys.Any( + key => key.Equals(field.Name, StringComparison.InvariantCultureIgnoreCase)) ? $"_{field.Name}" : field.Name, + field => (T)field.GetValue(this)); return elementFields.Concat(elementProperties) .ToDictionary(el => el.Key, el => el.Value); } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs index 5744403..b4ae37f 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/IForm.cs @@ -1,6 +1,4 @@ -using Aquality.Selenium.Core.Configurations; -using Aquality.Selenium.Core.Elements.Interfaces; -using Aquality.Selenium.Core.Visualization; +using Aquality.Selenium.Core.Visualization; namespace Aquality.Selenium.Core.Forms { 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 edd09f1..4a45a82 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 @@ -5,9 +5,9 @@ "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.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.wait.for.state": "Чакаем, пакуль элемент будзе {0}", @@ -27,5 +27,11 @@ "loc.el.visual.size.value": "Памер элемента: [{0}]", "loc.el.visual.getdifference": "Параўноўваем малюнак элемента з малюнкам памеру [{0}]", "loc.el.visual.getdifference.withthreshold": "Параўноўваем малюнак элемента з малюнкам памеру [{0}] з парогам адчувальнасці [{1}]", - "loc.el.visual.difference.value": "Адрозненне паміж цяперашнім і пададзеным малюнкамі складае [{0}]" + "loc.el.visual.difference.value": "Адрозненне паміж цяперашнім і пададзеным малюнкамі складае [{0}]", + "loc.form.dump.save": "Захоўваем дамп формы з іменем [{0}]", + "loc.form.dump.imagenotsaved": "Не ўдалося захаваць малюнак элемента [{0}]: {1}", + "loc.form.dump.compare": "Параўноўваем элементы формы з дампам [{0}]", + "loc.form.dump.elementnotfound": "Элемент [{0}], знойдзены ў дампе, не быў знойдзены на форме", + "loc.form.dump.unprocessedelements": "Колькасць неапрацаваных элементаў(не знойдзеных у дампе ці ў форме) складае [{0}]. Для іх адрозненне прынята за 100%", + "loc.form.dump.compare.result": "Адрозненне формы ад пададзенага дампа складае [{0}]" } \ 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 16b3165..861fab3 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 @@ -27,5 +27,11 @@ "loc.el.visual.size.value": "Element size: [{0}]", "loc.el.visual.getdifference": "Comparing element's image to image of size [{0}]", "loc.el.visual.getdifference.withthreshold": "Comparing element's image to image of size [{0}] with threshold [{1}]", - "loc.el.visual.difference.value": "The difference between the current and the given images is [{0}]" + "loc.el.visual.difference.value": "The difference between the current and the given images is [{0}]", + "loc.form.dump.save": "Saving dump of the form with name [{0}]", + "loc.form.dump.imagenotsaved": "Failed to save image of the element [{0}]: {1}", + "loc.form.dump.compare": "Comparing elements of the form to dump [{0}]", + "loc.form.dump.elementnotfound": "Element [{0}] found in the dump but was not found on the form", + "loc.form.dump.unprocessedelements": "Count of unprocessed (no match between form and dump) elements is [{0}]. For them difference count as 100%", + "loc.form.dump.compare.result": "The difference between the current form and the given dump is [{0}]" } \ 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 4a16e40..2d7c91c 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 @@ -27,5 +27,11 @@ "loc.el.visual.size.value": "Размер элемента: [{0}]", "loc.el.visual.getdifference": "Сравниваем изображение элемента с изображением размера [{0}]", "loc.el.visual.getdifference.withthreshold": "Сравниваем изображение элемента с изображением размера [{0}] с уровнем шума [{1}]", - "loc.el.visual.difference.value": "Отличие текущего изображения от предоставленного составляет [{0}]" + "loc.el.visual.difference.value": "Отличие текущего изображения от предоставленного составляет [{0}]", + "loc.form.dump.save": "Сохраняем дамп формы с именем [{0}]", + "loc.form.dump.imagenotsaved": "Не удалось сохранить изображение элемента [{0}]: {1}", + "loc.form.dump.compare": "Сравниваем элементы формы с дампом [{0}]", + "loc.form.dump.elementnotfound": "Элемент [{0}], найденный в дампе, не был найден на форме", + "loc.form.dump.unprocessedelements": "Количество необработанных элементов(не найдено совпадения между дампом и формой) составило [{0}]. Для них отличие принято за 100%", + "loc.form.dump.compare.result": "Отличие формы от предоставленного дампа составляет [{0}]" } \ 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 5777a45..a724766 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json @@ -20,6 +20,6 @@ "defaultThreshold": 0.012, "comparisonWidth": 16, "comparisonHeight": 16, - "pathToDumps": "./Resources/Dumps/" + "pathToDumps": "../../../Resources/Dumps/" } } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs index 659e57a..157417b 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Globalization; using System.IO; using System.Linq; @@ -30,10 +31,10 @@ public DumpManager(IDictionary elementsForVisualization, string formN protected string DumpsDirectory => VisualizationConfiguration.PathToDumps; - public virtual float CompareWithDump(string dumpName = null) + public virtual float Compare(string dumpName = null) { var directory = GetDumpDirectory(dumpName); - LocalizedLogger.Info("loc.visualization.dump.compare", directory.Name); + LocalizedLogger.Info("loc.form.dump.compare", directory.Name); if (!directory.Exists) { throw new InvalidOperationException($"Dump directory [{directory.FullName}] does not exist."); @@ -53,7 +54,7 @@ public virtual float CompareWithDump(string dumpName = null) var key = imageFile.Name.Replace(ImageFormat, string.Empty); if (!existingElements.ContainsKey(key)) { - LocalizedLogger.Warn("loc.visualization.dump.keynotfound", key); + LocalizedLogger.Warn("loc.form.dump.elementnotfound", key); countOfUnproceededElements++; } else @@ -63,15 +64,32 @@ public virtual float CompareWithDump(string dumpName = null) countOfProceededElements++; } } + if (countOfUnproceededElements > 0) + { + LocalizedLogger.Warn("loc.form.dump.unprocessedelements", countOfUnproceededElements); + } // adding of countOfUnproceededElements means 100% difference for each element absent in dump or on page - return (comparisonResult + countOfUnproceededElements) / (countOfProceededElements + countOfUnproceededElements); + var result = (comparisonResult + countOfUnproceededElements) / (countOfProceededElements + countOfUnproceededElements); + LocalizedLogger.Info("loc.form.dump.compare.result", result.ToString("P", CultureInfo.InvariantCulture)); + return result; } - public virtual void SaveDump(string dumpName = null) + public virtual void Save(string dumpName = null) { var directory = CleanUpAndGetDumpDirectory(dumpName); + LocalizedLogger.Info("loc.form.dump.save", directory.Name); ElementsForVisualization.Where(element => element.Value.State.IsExist).ToList() - .ForEach(element => element.Value.Visual.Image.Save(Path.Combine(directory.FullName, $"{element.Key}.png"))); + .ForEach(element => + { + try + { + element.Value.Visual.Image.Save(Path.Combine(directory.FullName, $"{element.Key}.png")); + } + catch (Exception e) + { + LocalizedLogger.Fatal("loc.form.dump.imagenotsaved", e, element.Key, e.Message); + } + }); } protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName = null) @@ -90,6 +108,10 @@ protected virtual DirectoryInfo CleanUpAndGetDumpDirectory(string dumpName = nul } else { + if (!Directory.Exists(DumpsDirectory)) + { + Directory.CreateDirectory(DumpsDirectory); + } dirInfo.Create(); } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs index 3699574..8539a01 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IDumpManager.cs @@ -15,7 +15,7 @@ public interface IDumpManager /// Form name is used by default. /// The difference of comparing the page to the dump as a percentage (no difference is 0%). /// Calculated as sum of element differences divided by elements count. - float CompareWithDump(string dumpName = null); + float Compare(string dumpName = null); /// /// Saves the dump of the current form @@ -24,6 +24,6 @@ public interface IDumpManager /// /// Name of the sub-folder where to save the dump. /// Form name is used by default. - void SaveDump(string dumpName = null); + void Save(string dumpName = null); } } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs index 685455b..d484dbe 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/IVisualStateProvider.cs @@ -1,5 +1,4 @@ using Aquality.Selenium.Core.Configurations; -using Aquality.Selenium.Core.Visualization; using System.Drawing; namespace Aquality.Selenium.Core.Visualization diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs index e9e6cdd..12398ef 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/VisualStateProvider.cs @@ -1,9 +1,7 @@ using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Utilities; -using OpenQA.Selenium; using OpenQA.Selenium.Remote; using System; -using System.Collections.Generic; using System.Drawing; using System.Globalization; @@ -29,12 +27,12 @@ public VisualStateProvider(IImageComparator imageComparator, IElementActionRetri public Point Location => GetLoggedValue(nameof(Location), element => element.Location); public Image Image - => GetLoggedValue(nameof(Image), element => element.GetScreenshot().AsImage(), image => image.Size.ToString(), new[] { typeof(WebDriverException) }); + => GetLoggedValue(nameof(Image), element => element.GetScreenshot().AsImage(), image => image.Size.ToString()); - private T GetLoggedValue(string name, Func getValue, Func toString = null, IEnumerable handledExceptions = null) + private T GetLoggedValue(string name, Func getValue, Func toString = null) { logVisualState($"loc.el.visual.get{name.ToLower()}"); - var value = actionRetrier.DoWithRetry(() => getValue(getElement()), handledExceptions); + var value = actionRetrier.DoWithRetry(() => getValue(getElement())); logVisualState($"loc.el.visual.{name.ToLower()}.value", toString == null ? value.ToString() : toString(value)); return value; } diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index 9ad52bb..5c26373 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -5,10 +5,10 @@ using Aquality.Selenium.Core.Localization; using Aquality.Selenium.Core.Tests.Applications.Browser; using Aquality.Selenium.Core.Tests.Applications.Browser.Elements; -using Aquality.Selenium.Core.Visualization; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using OpenQA.Selenium; +using OpenQA.Selenium.Interactions; using System; using System.Collections.Generic; using System.IO; @@ -17,7 +17,6 @@ namespace Aquality.Selenium.Core.Tests.Visualization { public class FormDumpTests : TestWithBrowser { - private static readonly Uri HoversURL = new Uri($"{TestSite}/hovers"); private readonly WebForm form = new WebForm(); @@ -28,35 +27,72 @@ public class FormDumpTests : TestWithBrowser public new void SetUp() { AqualityServices.Application.Driver.Navigate().GoToUrl(HoversURL); + form.WaitUntilPresent(); } [Test] - public void Should_BePossibleTo_SaveFormDump_WithCustomName() + public void Should_BePossibleTo_SaveFormDump_WithDefaultName() { var pathToDump = new DirectoryInfo(Path.Combine(PathToDumps, form.Name.Replace("/", " "))); - pathToDump.Delete(true); - form.WaitUntilPresent(); + if (pathToDump.Exists) + { + pathToDump.Delete(true); + pathToDump.Refresh(); + } Assert.AreEqual(0, pathToDump.Exists ? pathToDump.GetFiles().Length : 0, "Dump directory should not contain any files before saving"); - Assert.DoesNotThrow(() => form.Dump.SaveDump()); + Assert.DoesNotThrow(() => form.Dump.Save()); pathToDump.Refresh(); DirectoryAssert.Exists(pathToDump); Assert.Greater(pathToDump.GetFiles().Length, 0, "Dump should contain some files"); } + [Test] + public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZero() + { + form.Dump.Save("Zero diff"); + Assert.That(form.Dump.Compare("Zero diff"), Is.EqualTo(0), "Difference with current page should be around zero"); + } + + [Test] + public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() + { + form.Dump.Save("Non-zero diff"); + form.HoverAvatar(); + Assert.That(form.Dump.Compare("Non-zero diff"), Is.GreaterThan(0), "Difference with current page should be greater than zero"); + } + + [Test] + public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenElementSetDiffers() + { + var customForm = new WebForm(); + customForm.Dump.Save("Set differs"); + customForm.SetElementsForDump(WebForm.ElementsFilter.ElementsInitializedAsDisplayed); + Assert.That(customForm.Dump.Compare("Set differs"), Is.GreaterThan(0), "Difference with current page should be greater than zero if element set differs"); + } + + [Test] + public void Should_BePossibleTo_SaveAndCompareWithDump_WithCustomName_WhenAllElementsSelected() + { + var customForm = new WebForm(); + customForm.SetElementsForDump(WebForm.ElementsFilter.AllElements); + Assert.DoesNotThrow(() => customForm.Dump.Save("All elements")); + Assert.That(customForm.Dump.Compare("All elements"), Is.GreaterThan(0), "Some elements should be failed to take image, so count of elements would not match"); + } + private class WebForm : Form { private static readonly By ContentLoc = By.XPath("//div[contains(@class,'example')]"); private static readonly By HiddenElementsLoc = By.XPath("//h5"); - private static readonly By DisplayedElementsLoc = By.XPath("//img[@alt='User Avatar']"); + private static readonly By DisplayedElementsLoc = By.XPath("//div[contains(@class,'figure')]"); private readonly Label displayedLabel = ElementFactory.GetCustomElement( (loc, name, state) => new Label(loc, name, state), - DisplayedElementsLoc, "I'm displayed"); + DisplayedElementsLoc, "I'm displayed field"); private readonly Label displayedButInitializedAsExist = new Label(DisplayedElementsLoc, "I'm displayed but initialized as existing", ElementState.ExistsInAnyState); private Label DisplayedLabel - => new Label(DisplayedElementsLoc, "I'm displayed", ElementState.Displayed); + => new Label(DisplayedElementsLoc, "I'm displayed property", ElementState.Displayed); private Label HiddenLabel => new Label(HiddenElementsLoc, "I'm hidden", ElementState.ExistsInAnyState); private Label HiddenLabelInitializedAsDisplayed @@ -70,11 +106,19 @@ private Label HiddenLabelInitializedAsDisplayed public override string Name => "Web page/form"; private static IElementFactory ElementFactory => AqualityServices.ServiceProvider.GetRequiredService(); - public WebForm() + public void WaitUntilPresent() { + ContentLabel.Click(); + DisplayedLabel.State.WaitForClickable(); } - public void WaitUntilPresent() => DisplayedLabel.State.WaitForClickable(); + public void HoverAvatar() + { + new Actions(AqualityServices.Application.Driver) + .MoveToElement(DisplayedLabel.GetElement()) + .Build().Perform(); + HiddenLabel.State.WaitForDisplayed(); + } public void SetElementsForDump(ElementsFilter filter) { From 22e0e712f4abdb188e415ce7fc7749d4fa01d615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Sat, 29 May 2021 06:43:42 +0300 Subject: [PATCH 06/12] Stabilize hovering test --- .../Visualization/FormDumpTests.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index 5c26373..cf50a15 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -3,8 +3,10 @@ using Aquality.Selenium.Core.Elements.Interfaces; using Aquality.Selenium.Core.Forms; using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Tests.Applications.Browser; using Aquality.Selenium.Core.Tests.Applications.Browser.Elements; +using Aquality.Selenium.Core.Waitings; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using OpenQA.Selenium; @@ -58,8 +60,14 @@ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() { form.Dump.Save("Non-zero diff"); - form.HoverAvatar(); - Assert.That(form.Dump.Compare("Non-zero diff"), Is.GreaterThan(0), "Difference with current page should be greater than zero"); + var result = AqualityServices.ServiceProvider.GetRequiredService() + .WaitFor(() => + { + form.HoverAvatar(); + return form.Dump.Compare("Non-zero diff") > 0; + }); + + Assert.That(result, "Difference with current page should be greater than zero"); } [Test] @@ -114,6 +122,7 @@ public void WaitUntilPresent() public void HoverAvatar() { + AqualityServices.ServiceProvider.GetRequiredService().Info("Hovering avatar"); new Actions(AqualityServices.Application.Driver) .MoveToElement(DisplayedLabel.GetElement()) .Build().Perform(); From 4823f2d44884491c2158eed26e41e23e55b23bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Sat, 29 May 2021 07:08:12 +0300 Subject: [PATCH 07/12] Stabilizing hover test --- .../Visualization/FormDumpTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index cf50a15..0f04fb7 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -59,10 +59,11 @@ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZ [Test] public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() { - form.Dump.Save("Non-zero diff"); var result = AqualityServices.ServiceProvider.GetRequiredService() .WaitFor(() => { + AqualityServices.Application.Driver.Navigate().Refresh(); + form.Dump.Save("Non-zero diff"); form.HoverAvatar(); return form.Dump.Compare("Non-zero diff") > 0; }); From 79ecf7af56b9a7b05b9d8d1d60b7641676465b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Sat, 29 May 2021 07:22:44 +0300 Subject: [PATCH 08/12] Stabilizing hovering --- .../Visualization/FormDumpTests.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index 0f04fb7..6c8cc82 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -59,16 +59,10 @@ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZ [Test] public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() { - var result = AqualityServices.ServiceProvider.GetRequiredService() - .WaitFor(() => - { - AqualityServices.Application.Driver.Navigate().Refresh(); - form.Dump.Save("Non-zero diff"); - form.HoverAvatar(); - return form.Dump.Compare("Non-zero diff") > 0; - }); - - Assert.That(result, "Difference with current page should be greater than zero"); + form.Dump.Save("Non-zero diff"); + form.HoverAvatar(); + + Assert.That(form.Dump.Compare("Non-zero diff"), Is.GreaterThan(0), "Difference with current page should be greater than zero"); } [Test] @@ -126,6 +120,7 @@ public void HoverAvatar() AqualityServices.ServiceProvider.GetRequiredService().Info("Hovering avatar"); new Actions(AqualityServices.Application.Driver) .MoveToElement(DisplayedLabel.GetElement()) + .ClickAndHold() .Build().Perform(); HiddenLabel.State.WaitForDisplayed(); } From bbac3f488f7be26e997e519d2d4fd276e61bdb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Mon, 31 May 2021 13:24:29 +0300 Subject: [PATCH 09/12] try to stabilize non-zero diff test --- .../Visualization/FormDumpTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index 6c8cc82..58b0d90 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -6,7 +6,6 @@ using Aquality.Selenium.Core.Logging; using Aquality.Selenium.Core.Tests.Applications.Browser; using Aquality.Selenium.Core.Tests.Applications.Browser.Elements; -using Aquality.Selenium.Core.Waitings; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using OpenQA.Selenium; @@ -29,6 +28,7 @@ public class FormDumpTests : TestWithBrowser public new void SetUp() { AqualityServices.Application.Driver.Navigate().GoToUrl(HoversURL); + form.ClickOnContent(); form.WaitUntilPresent(); } @@ -59,9 +59,10 @@ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZ [Test] public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() { - form.Dump.Save("Non-zero diff"); form.HoverAvatar(); - + form.Dump.Save("Non-zero diff"); + AqualityServices.Application.Driver.Navigate().Refresh(); + form.WaitUntilPresent(); Assert.That(form.Dump.Compare("Non-zero diff"), Is.GreaterThan(0), "Difference with current page should be greater than zero"); } @@ -109,9 +110,13 @@ private Label HiddenLabelInitializedAsDisplayed public override string Name => "Web page/form"; private static IElementFactory ElementFactory => AqualityServices.ServiceProvider.GetRequiredService(); - public void WaitUntilPresent() + public void ClickOnContent() { ContentLabel.Click(); + } + + public void WaitUntilPresent() + { DisplayedLabel.State.WaitForClickable(); } From b5c0567dac206de4726110148909559869577a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Mon, 31 May 2021 13:48:26 +0300 Subject: [PATCH 10/12] remove dependency in between tests --- .../Visualization/FormDumpTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs index 58b0d90..99315c8 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Visualization/FormDumpTests.cs @@ -20,13 +20,12 @@ public class FormDumpTests : TestWithBrowser { private static readonly Uri HoversURL = new Uri($"{TestSite}/hovers"); - private readonly WebForm form = new WebForm(); - private string PathToDumps => AqualityServices.ServiceProvider.GetRequiredService().PathToDumps; [SetUp] public new void SetUp() { + var form = new WebForm(); AqualityServices.Application.Driver.Navigate().GoToUrl(HoversURL); form.ClickOnContent(); form.WaitUntilPresent(); @@ -35,6 +34,7 @@ public class FormDumpTests : TestWithBrowser [Test] public void Should_BePossibleTo_SaveFormDump_WithDefaultName() { + var form = new WebForm(); var pathToDump = new DirectoryInfo(Path.Combine(PathToDumps, form.Name.Replace("/", " "))); if (pathToDump.Exists) @@ -52,6 +52,7 @@ public void Should_BePossibleTo_SaveFormDump_WithDefaultName() [Test] public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZero() { + var form = new WebForm(); form.Dump.Save("Zero diff"); Assert.That(form.Dump.Compare("Zero diff"), Is.EqualTo(0), "Difference with current page should be around zero"); } @@ -59,6 +60,7 @@ public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsZ [Test] public void Should_BePossibleTo_CompareWithDump_WithCustomName_WhenDifferenceIsNotZero() { + var form = new WebForm(); form.HoverAvatar(); form.Dump.Save("Non-zero diff"); AqualityServices.Application.Driver.Navigate().Refresh(); From 2b9903b0d43774ea15e8399881727982831ec317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Mon, 31 May 2021 17:45:31 +0300 Subject: [PATCH 11/12] Enhanced logging of DumpManager --- .../Resources/Localization/be.json | 2 ++ .../Resources/Localization/en.json | 6 ++++-- .../Resources/Localization/ru.json | 2 ++ .../Visualization/DumpManager.cs | 11 +++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) 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 4a45a82..0c4f3eb 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 @@ -32,6 +32,8 @@ "loc.form.dump.imagenotsaved": "Не ўдалося захаваць малюнак элемента [{0}]: {1}", "loc.form.dump.compare": "Параўноўваем элементы формы з дампам [{0}]", "loc.form.dump.elementnotfound": "Элемент [{0}], знойдзены ў дампе, не быў знойдзены на форме", + "loc.form.dump.elementsmissedindump": "Элементы, прысутныя на форме, не былі знойдзеныя ў дампе: [{0}].", + "loc.form.dump.elementsmissedonform": "Элементы, прысутныя ў дампе, не былі знойдзеныя на форме: [{0}].", "loc.form.dump.unprocessedelements": "Колькасць неапрацаваных элементаў(не знойдзеных у дампе ці ў форме) складае [{0}]. Для іх адрозненне прынята за 100%", "loc.form.dump.compare.result": "Адрозненне формы ад пададзенага дампа складае [{0}]" } \ 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 861fab3..dbf9a02 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 @@ -31,7 +31,9 @@ "loc.form.dump.save": "Saving dump of the form with name [{0}]", "loc.form.dump.imagenotsaved": "Failed to save image of the element [{0}]: {1}", "loc.form.dump.compare": "Comparing elements of the form to dump [{0}]", - "loc.form.dump.elementnotfound": "Element [{0}] found in the dump but was not found on the form", - "loc.form.dump.unprocessedelements": "Count of unprocessed (no match between form and dump) elements is [{0}]. For them difference count as 100%", + "loc.form.dump.elementnotfound": "Element [{0}] found in the dump but was not found on form", + "loc.form.dump.elementsmissedindump": "Elements that were found on form but missed in the dump: [{0}].", + "loc.form.dump.elementsmissedonform": "Elements that were found in the dump but missed on form: [{0}].", + "loc.form.dump.unprocessedelements": "Count of unprocessed (no match between form and dump) elements is [{0}]. For them difference counts as 100%", "loc.form.dump.compare.result": "The difference between the current form and the given dump is [{0}]" } \ 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 2d7c91c..0c74849 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 @@ -32,6 +32,8 @@ "loc.form.dump.imagenotsaved": "Не удалось сохранить изображение элемента [{0}]: {1}", "loc.form.dump.compare": "Сравниваем элементы формы с дампом [{0}]", "loc.form.dump.elementnotfound": "Элемент [{0}], найденный в дампе, не был найден на форме", + "loc.form.dump.elementsmissedindump": "Элементы, найденные на форме, не были найдены в дампе: [{0}].", + "loc.form.dump.elementsmissedonform": "Элементы, найденные в дампе, не были найдены на форме: [{0}].", "loc.form.dump.unprocessedelements": "Количество необработанных элементов(не найдено совпадения между дампом и формой) составило [{0}]. Для них отличие принято за 100%", "loc.form.dump.compare.result": "Отличие формы от предоставленного дампа составляет [{0}]" } \ No newline at end of file diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs index 157417b..d80509c 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Visualization/DumpManager.cs @@ -49,6 +49,7 @@ public virtual float Compare(string dumpName = null) var countOfUnproceededElements = existingElements.Count; var countOfProceededElements = 0; var comparisonResult = 0f; + var absentOnFormElementNames = new List(); foreach (var imageFile in imageFiles) { var key = imageFile.Name.Replace(ImageFormat, string.Empty); @@ -56,16 +57,26 @@ public virtual float Compare(string dumpName = null) { LocalizedLogger.Warn("loc.form.dump.elementnotfound", key); countOfUnproceededElements++; + absentOnFormElementNames.Add(key); } else { comparisonResult += existingElements[key].Visual.GetDifference(Image.FromFile(imageFile.FullName)); countOfUnproceededElements--; countOfProceededElements++; + existingElements.Remove(key); } } if (countOfUnproceededElements > 0) { + if (existingElements.Any()) + { + LocalizedLogger.Warn("loc.form.dump.elementsmissedindump", string.Join(", ", existingElements.Keys)); + } + if (absentOnFormElementNames.Any()) + { + LocalizedLogger.Warn("loc.form.dump.elementsmissedonform", string.Join(", ", absentOnFormElementNames)); + } LocalizedLogger.Warn("loc.form.dump.unprocessedelements", countOfUnproceededElements); } // adding of countOfUnproceededElements means 100% difference for each element absent in dump or on page From cba328f27747b621b42359d334b4923f4e9950c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Tue, 1 Jun 2021 12:01:39 +0300 Subject: [PATCH 12/12] Fixed typos in documentation, updated path to VisualDumps --- .gitignore | 2 +- .../src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml | 6 +++--- .../Configurations/VisualizationConfiguration.cs | 2 +- .../src/Aquality.Selenium.Core/Forms/Form.cs | 6 +++--- .../src/Aquality.Selenium.Core/Resources/settings.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 8b10aff..453830d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ msbuild.wrn .vs/ Log/ -Dumps/ +VisualDumps/ 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 bf4ab67..35c2263 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 @@ -643,13 +643,13 @@ Visualization configuration used by . - Could be get from AqualityServices. + Could be obtained from AqualityServices. - Localizer logger used by . - Could be get from AqualityServices. + Localized logger used by . + Could be obtained from AqualityServices. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs index bb41597..65a5647 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Configurations/VisualizationConfiguration.cs @@ -30,7 +30,7 @@ public string PathToDumps { get { - var pathInConfiguration = settingsFile.GetValueOrDefault(".visualization.pathToDumps", "../../../Resources/Dumps/"); + var pathInConfiguration = settingsFile.GetValueOrDefault(".visualization.pathToDumps", "../../../Resources/VisualDumps/"); return pathInConfiguration.Contains(".") ? Path.GetFullPath(pathInConfiguration) : pathInConfiguration; } } diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs index 4cc9296..8a27187 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Forms/Form.cs @@ -23,13 +23,13 @@ public abstract class Form : IForm where T : IElement /// /// Visualization configuration used by . - /// Could be get from AqualityServices. + /// Could be obtained from AqualityServices. /// protected abstract IVisualizationConfiguration VisualizationConfiguration { get; } /// - /// Localizer logger used by . - /// Could be get from AqualityServices. + /// Localized logger used by . + /// Could be obtained from AqualityServices. /// protected abstract ILocalizedLogger LocalizedLogger { get; } 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 a724766..565acf7 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/settings.json @@ -20,6 +20,6 @@ "defaultThreshold": 0.012, "comparisonWidth": 16, "comparisonHeight": 16, - "pathToDumps": "../../../Resources/Dumps/" + "pathToDumps": "../../../Resources/VisualDumps/" } } \ No newline at end of file