From b4ed71ca06192ebc69ec4d8db7dd0ce0b01447fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Mon, 14 Jun 2021 10:35:27 +0300 Subject: [PATCH] [Visual Testing] Add ability of visualization checks for the Form (#56) +semver: breaking * [Visual Testing] Add ability of visualization checks for the Form +semver: breaking * update Aquality.Selenium.Core nuget package * implement IForm from Core.Forms by the Form * add "visualization" section to settings.json * add visual dump test * Add dumps folder to .gitignore *Add AppiumWebElement getter method to IElement which overrides the base one, update Element.cs * closes #54, closes #55 --- .gitignore | 1 + .../Aquality.WinAppDriver.csproj | 4 +- .../Aquality.WinAppDriver/Elements/Element.cs | 11 ++++ .../Elements/Interfaces/IElement.cs | 9 +++ .../Elements/WindowsElementFinder.cs | 9 ++- .../src/Aquality.WinAppDriver/Forms/Form.cs | 57 ++++++++++++++++++- .../src/Aquality.WinAppDriver/Forms/IForm.cs | 8 ++- .../Resources/settings.json | 6 ++ .../Aquality.WinAppDriver.Tests.csproj | 6 +- .../Forms/CalculatorForm.cs | 7 +++ .../Forms/FormTests.cs | 17 +++++- .../Resources/settings.json | 6 ++ 12 files changed, 129 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 2ee2263..453830d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ msbuild.wrn .vs/ Log/ +VisualDumps/ diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj index bfba78d..c4793ed 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Aquality.WinAppDriver.csproj @@ -45,8 +45,8 @@ - - + + diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs index 7145bd0..cc5047b 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Element.cs @@ -7,6 +7,7 @@ using Aquality.WinAppDriver.Elements.Actions; using Aquality.WinAppDriver.Elements.Interfaces; using OpenQA.Selenium; +using OpenQA.Selenium.Appium; using IKeyboardActions = Aquality.WinAppDriver.Actions.IKeyboardActions; using CoreElement = Aquality.Selenium.Core.Elements.Element; using CoreElementFactory = Aquality.Selenium.Core.Elements.Interfaces.IElementFactory; @@ -14,12 +15,14 @@ using OpenQA.Selenium.Appium.Windows; using System; using Aquality.Selenium.Core.Configurations; +using Aquality.Selenium.Core.Visualization; namespace Aquality.WinAppDriver.Elements { public abstract class Element : CoreElement, IElement { private readonly Func searchContextSupplier; + internal readonly ElementState elementState; protected Element( By locator, @@ -31,6 +34,7 @@ protected Element( { this.searchContextSupplier = searchContextSupplier; WindowsDriverSupplier = customSessionSupplier ?? (() => AqualityServices.Application.Driver); + this.elementState = elementState; } protected override IElementActionRetrier ActionRetrier => AqualityServices.Get(); @@ -56,5 +60,12 @@ protected Element( protected override ILocalizedLogger LocalizedLogger => AqualityServices.LocalizedLogger; protected override ILocalizationManager LocalizationManager => AqualityServices.Get(); + + protected override IImageComparator ImageComparator => AqualityServices.Get(); + + public new AppiumWebElement GetElement(TimeSpan? timeout = null) + { + return (AppiumWebElement) base.GetElement(timeout); + } } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs index 723fedf..6e6c7db 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/Interfaces/IElement.cs @@ -1,4 +1,5 @@ using Aquality.WinAppDriver.Elements.Actions; +using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; using System; using CoreElement = Aquality.Selenium.Core.Elements.Interfaces.IElement; @@ -25,5 +26,13 @@ public interface IElement : CoreElement /// Provides access to against the current element. /// IMouseActions MouseActions { get; } + + /// + /// Gets current mobile element by specified + /// + /// Timeout for waiting (would use default timeout from settings by default). + /// + /// Thrown if element was not found. + new AppiumWebElement GetElement(TimeSpan? timeout = null); } } diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/WindowsElementFinder.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/WindowsElementFinder.cs index 5e68102..cb49413 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/WindowsElementFinder.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Elements/WindowsElementFinder.cs @@ -22,7 +22,7 @@ public WindowsElementFinder(ILocalizedLogger logger, IConditionalWait conditiona private Func SearchContextSupplier { get; } - public override IWebElement FindElement(By locator, Func elementStateCondition, string stateName, TimeSpan? timeout = null) + public override IWebElement FindElement(By locator, Func elementStateCondition, string stateName, TimeSpan? timeout = null, string name = null) { IWebElement element = null; if (!ConditionalWait.WaitFor(() => @@ -44,9 +44,12 @@ public override IWebElement FindElement(By locator, Func elem } else { - Logger.Debug("loc.no.elements.found.by.locator", null, locator.ToString()); + Logger.Debug("loc.no.elements.with.name.found.by.locator", null, name, locator.ToString()); } - throw new NoSuchElementException($"No elements with locator '{locator.ToString()}' were found in {stateName} state"); + var message = string.IsNullOrEmpty(name) + ? $"No elements with locator '{locator}' were found in {stateName} state" + : $"Element [{name}] was not found by locator '{locator}' in {stateName} state"; + throw new NoSuchElementException(message); } return element; diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/Form.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/Form.cs index 02711ca..ee03854 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/Form.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/Form.cs @@ -6,10 +6,14 @@ using System; using System.Drawing; using Element = Aquality.WinAppDriver.Elements.Element; -using IElement = Aquality.Selenium.Core.Elements.Interfaces.IElement; using ElementFactory = Aquality.WinAppDriver.Elements.ElementFactory; using OpenQA.Selenium.Appium.Windows; using System.Diagnostics; +using Aquality.Selenium.Core.Visualization; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using Aquality.Selenium.Core.Configurations; namespace Aquality.WinAppDriver.Forms { @@ -66,7 +70,7 @@ private static Func ResolveSearchContextSupplier(IForm parentFor /// Locator of the element relative to current form. /// Name of the element. /// Delegate that defines constructor of element in case of custom element. - /// Element existance state + /// Element existence state /// Instance of element. protected virtual new T FindChildElement(By childLocator, string childName, ElementSupplier supplier = null, ElementState elementState = ElementState.Displayed) where T : IElement @@ -82,5 +86,54 @@ private static Func ResolveSearchContextSupplier(IForm parentFor public virtual Size Size => GetElement().Size; protected override string ElementType => LocalizationManager.GetLocalizedMessage("loc.form"); + + protected virtual IVisualizationConfiguration VisualizationConfiguration => AqualityServices.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, Name, 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 elementProperties = GetType().GetProperties(bindingFlags).Where(property => typeof(IElement).IsAssignableFrom(property.PropertyType)) + .ToDictionary(property => property.Name, property => (IElement)property.GetValue(this)); + var elementFields = GetType().GetFields(bindingFlags).Where(field => typeof(IElement).IsAssignableFrom(field.FieldType)) + .ToDictionary(field => elementProperties.Keys.Any( + key => key.Equals(field.Name, StringComparison.InvariantCultureIgnoreCase)) ? $"_{field.Name}" : field.Name, + field => (IElement)field.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.WinAppDriver/src/Aquality.WinAppDriver/Forms/IForm.cs b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/IForm.cs index 4535738..02a2262 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/IForm.cs +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/IForm.cs @@ -1,14 +1,20 @@ using System.Diagnostics; using System.Drawing; using Aquality.WinAppDriver.Elements.Interfaces; +using CoreForm = Aquality.Selenium.Core.Forms.IForm; namespace Aquality.WinAppDriver.Forms { /// /// Defines an interface for any form on any application's window. /// - public interface IForm : IElement + public interface IForm : IElement, CoreForm { + /// + /// Name of the current form. + /// + new string Name { get; } + /// /// Gets size of the form element defined by its locator. /// diff --git a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/settings.json b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/settings.json index 943daba..768a68f 100644 --- a/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/settings.json +++ b/Aquality.WinAppDriver/src/Aquality.WinAppDriver/Resources/settings.json @@ -26,5 +26,11 @@ }, "elementCache": { "isEnabled": true + }, + "visualization": { + "defaultThreshold": 0.012, + "comparisonWidth": 16, + "comparisonHeight": 16, + "pathToDumps": "../../../Resources/VisualDumps/" } } \ No newline at end of file diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Aquality.WinAppDriver.Tests.csproj b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Aquality.WinAppDriver.Tests.csproj index 9ccc187..0c14b7b 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Aquality.WinAppDriver.Tests.csproj +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Aquality.WinAppDriver.Tests.csproj @@ -7,12 +7,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/CalculatorForm.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/CalculatorForm.cs index 4654746..ca7ccee 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/CalculatorForm.cs +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/CalculatorForm.cs @@ -1,5 +1,6 @@ using Aquality.WinAppDriver.Elements.Interfaces; using Aquality.WinAppDriver.Forms; +using System.Collections.Generic; namespace Aquality.WinAppDriver.Tests.Forms { @@ -28,5 +29,11 @@ public class CalculatorForm : Form, ICalculatorForm public CalculatorForm() : base(CalculatorLocators.WindowLocator, "Calculator") { } + + protected override IDictionary ElementsForVisualization => new Dictionary + { + { NumberPad.Name, NumberPad }, + { MaximizeButton.Name, MaximizeButton } + }; } } \ No newline at end of file diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/FormTests.cs b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/FormTests.cs index 1fbe462..c6a0d4d 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/FormTests.cs +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Forms/FormTests.cs @@ -1,4 +1,6 @@ -namespace Aquality.WinAppDriver.Tests.Forms +using NUnit.Framework; + +namespace Aquality.WinAppDriver.Tests.Forms { public class FormTests : AbstractFormTests { @@ -13,5 +15,18 @@ public FormTests() CalculatorForm = new CalculatorForm(); TestForm = new TestForm(Locator, PageName); } + + [Test] + public void Should_SaveAndCompareDump() + { + CalculatorForm.OneButton.Click(); + CalculatorForm.PlusButton.Click(); + Assert.DoesNotThrow(() => CalculatorForm.Dump.Save(), "Dump should be saved without errors"); + Assert.That(() => CalculatorForm.Dump.Compare(), Is.EqualTo(0), "Dump should have no difference right after the saving"); + CalculatorForm.TwoButton.Click(); + CalculatorForm.EqualsButton.Click(); + StringAssert.Contains("3", CalculatorForm.ResultsLabel.Text); + Assert.That(() => CalculatorForm.Dump.Compare(), Is.GreaterThan(0), "Dump should have differences after some calculations"); + } } } \ No newline at end of file diff --git a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Resources/settings.json b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Resources/settings.json index 4b59c66..f6de145 100644 --- a/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Resources/settings.json +++ b/Aquality.WinAppDriver/tests/Aquality.WinAppDriver.Tests/Resources/settings.json @@ -26,5 +26,11 @@ }, "elementCache": { "isEnabled": true + }, + "visualization": { + "defaultThreshold": 0.03, + "comparisonWidth": 16, + "comparisonHeight": 16, + "pathToDumps": "../../../Resources/VisualDumps/" } } \ No newline at end of file