Skip to content

Commit

Permalink
[Visual Testing] Add ability of visualization checks for the Form (#56
Browse files Browse the repository at this point in the history
…) +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
  • Loading branch information
mialeska authored Jun 14, 2021
1 parent 7dc9a7e commit b4ed71c
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ msbuild.wrn
.vs/

Log/
VisualDumps/
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Appium.WebDriver" Version="4.1.1" />
<PackageReference Include="Aquality.Selenium.Core" Version="1.2.2" />
<PackageReference Include="Appium.WebDriver" Version="4.3.1" />
<PackageReference Include="Aquality.Selenium.Core" Version="1.6.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@
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;
using CoreElementFinder = Aquality.Selenium.Core.Elements.Interfaces.IElementFinder;
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<ISearchContext> searchContextSupplier;
internal readonly ElementState elementState;

protected Element(
By locator,
Expand All @@ -31,6 +34,7 @@ protected Element(
{
this.searchContextSupplier = searchContextSupplier;
WindowsDriverSupplier = customSessionSupplier ?? (() => AqualityServices.Application.Driver);
this.elementState = elementState;
}

protected override IElementActionRetrier ActionRetrier => AqualityServices.Get<IElementActionRetrier>();
Expand All @@ -56,5 +60,12 @@ protected Element(
protected override ILocalizedLogger LocalizedLogger => AqualityServices.LocalizedLogger;

protected override ILocalizationManager LocalizationManager => AqualityServices.Get<ILocalizationManager>();

protected override IImageComparator ImageComparator => AqualityServices.Get<IImageComparator>();

public new AppiumWebElement GetElement(TimeSpan? timeout = null)
{
return (AppiumWebElement) base.GetElement(timeout);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -25,5 +26,13 @@ public interface IElement : CoreElement
/// Provides access to <see cref="IMouseActions"/> against the current element.
/// </summary>
IMouseActions MouseActions { get; }

/// <summary>
/// Gets current mobile element by specified <see cref="CoreElement.Locator"/>
/// </summary>
/// <param name="timeout">Timeout for waiting (would use default timeout from settings by default).</param>
/// <returns></returns>
/// <exception cref="OpenQA.Selenium.NoSuchElementException">Thrown if element was not found.</exception>
new AppiumWebElement GetElement(TimeSpan? timeout = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public WindowsElementFinder(ILocalizedLogger logger, IConditionalWait conditiona

private Func<ISearchContext> SearchContextSupplier { get; }

public override IWebElement FindElement(By locator, Func<IWebElement, bool> elementStateCondition, string stateName, TimeSpan? timeout = null)
public override IWebElement FindElement(By locator, Func<IWebElement, bool> elementStateCondition, string stateName, TimeSpan? timeout = null, string name = null)
{
IWebElement element = null;
if (!ConditionalWait.WaitFor(() =>
Expand All @@ -44,9 +44,12 @@ public override IWebElement FindElement(By locator, Func<IWebElement, bool> 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;
Expand Down
57 changes: 55 additions & 2 deletions Aquality.WinAppDriver/src/Aquality.WinAppDriver/Forms/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -66,7 +70,7 @@ private static Func<ISearchContext> ResolveSearchContextSupplier(IForm parentFor
/// <param name="childLocator">Locator of the element relative to current form.</param>
/// <param name="childName">Name of the element.</param>
/// <param name="supplier">Delegate that defines constructor of element in case of custom element.</param>
/// <param name="elementState">Element existance state</param>
/// <param name="elementState">Element existence state</param>
/// <returns>Instance of element.</returns>
protected virtual new T FindChildElement<T>(By childLocator, string childName, ElementSupplier<T> supplier = null, ElementState elementState = ElementState.Displayed)
where T : IElement
Expand All @@ -82,5 +86,54 @@ private static Func<ISearchContext> ResolveSearchContextSupplier(IForm parentFor
public virtual Size Size => GetElement().Size;

protected override string ElementType => LocalizationManager.GetLocalizedMessage("loc.form");

protected virtual IVisualizationConfiguration VisualizationConfiguration => AqualityServices.Get<IVisualizationConfiguration>();

/// <summary>
/// Gets dump manager for the current form that could be used for visualization purposes, such as saving and comparing dumps.
/// Uses <see cref="ElementsForVisualization"/> as basis for dump creation and comparison.
/// </summary>
public virtual IDumpManager Dump => new DumpManager<IElement>(ElementsForVisualization, Name, VisualizationConfiguration, LocalizedLogger);

/// <summary>
/// List of pairs uniqueName-element to be used for dump saving and comparing.
/// By default, only currently displayed elements to be used (<see cref="ElementsInitializedAsDisplayed"/>).
/// You can override this property with defined <see cref="AllElements"/>, <see cref="DisplayedElements"/> or your own element set.
/// </summary>
protected virtual IDictionary<string, IElement> ElementsForVisualization => DisplayedElements;

/// <summary>
/// List of pairs uniqueName-element from all fields and properties of type <see cref="IElement"/>.
/// </summary>
protected IDictionary<string, IElement> 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);
}
}

/// <summary>
/// List of pairs uniqueName-element from all fields and properties of type <see cref="IElement"/>,
/// which were initialized as <see cref="ElementState.Displayed"/>.
/// </summary>
protected IDictionary<string, IElement> ElementsInitializedAsDisplayed => AllElements
.Where(element => element.Value is Element && (element.Value as Element).elementState == ElementState.Displayed)
.ToDictionary(el => el.Key, el => el.Value);

/// <summary>
/// List of pairs uniqueName-element from all fields and properties of type <see cref="IElement"/>,
/// which are currently displayed (using <see cref="Selenium.Core.Elements.Interfaces.IElementStateProvider.IsDisplayed"/>).
/// </summary>
protected IDictionary<string, IElement> DisplayedElements => AllElements.Where(element => element.Value.State.IsDisplayed)
.ToDictionary(el => el.Key, el => el.Value);
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines an interface for any form on any application's window.
/// </summary>
public interface IForm : IElement
public interface IForm : IElement, CoreForm
{
/// <summary>
/// Name of the current form.
/// </summary>
new string Name { get; }

/// <summary>
/// Gets size of the form element defined by its locator.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@
},
"elementCache": {
"isEnabled": true
},
"visualization": {
"defaultThreshold": 0.012,
"comparisonWidth": 16,
"comparisonHeight": 16,
"pathToDumps": "../../../Resources/VisualDumps/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Aquality.WinAppDriver.Elements.Interfaces;
using Aquality.WinAppDriver.Forms;
using System.Collections.Generic;

namespace Aquality.WinAppDriver.Tests.Forms
{
Expand Down Expand Up @@ -28,5 +29,11 @@ public class CalculatorForm : Form, ICalculatorForm
public CalculatorForm() : base(CalculatorLocators.WindowLocator, "Calculator")
{
}

protected override IDictionary<string, IElement> ElementsForVisualization => new Dictionary<string, IElement>
{
{ NumberPad.Name, NumberPad },
{ MaximizeButton.Name, MaximizeButton }
};
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Aquality.WinAppDriver.Tests.Forms
using NUnit.Framework;

namespace Aquality.WinAppDriver.Tests.Forms
{
public class FormTests : AbstractFormTests
{
Expand All @@ -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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@
},
"elementCache": {
"isEnabled": true
},
"visualization": {
"defaultThreshold": 0.03,
"comparisonWidth": 16,
"comparisonHeight": 16,
"pathToDumps": "../../../Resources/VisualDumps/"
}
}

0 comments on commit b4ed71c

Please sign in to comment.