Skip to content

Commit b1447fd

Browse files
authored
[Visual Testing] Implement element's VisualStateProvider (#89) +semver: feature
* Implement element's VisualStateProvider - add Visual element's property - add localized logging - add tests
1 parent 909edd7 commit b1447fd

File tree

17 files changed

+301
-15
lines changed

17 files changed

+301
-15
lines changed

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Aquality.Selenium.Core.Localization;
77
using Aquality.Selenium.Core.Logging;
88
using Aquality.Selenium.Core.Utilities;
9+
using Aquality.Selenium.Core.Visualization;
910
using Aquality.Selenium.Core.Waitings;
1011
using OpenQA.Selenium;
1112
using OpenQA.Selenium.Remote;
@@ -35,6 +36,9 @@ protected Element(By locator, string name, ElementState state)
3536
? (IElementStateProvider) new CachedElementStateProvider(Locator, ConditionalWait, Cache, LogElementState)
3637
: new ElementStateProvider(Locator, ConditionalWait, Finder, LogElementState);
3738

39+
public virtual IVisualStateProvider Visual
40+
=> new VisualStateProvider(ImageComparator, ActionRetrier, () => GetElement(), LogVisualState);
41+
3842
protected virtual IElementCacheHandler Cache
3943
{
4044
get
@@ -62,6 +66,8 @@ protected virtual IElementCacheHandler Cache
6266

6367
protected abstract IElementFinder Finder { get; }
6468

69+
protected abstract IImageComparator ImageComparator { get; }
70+
6571
protected abstract ILocalizedLogger LocalizedLogger { get; }
6672

6773
protected abstract ILocalizationManager LocalizationManager { get; }
@@ -74,6 +80,19 @@ protected virtual IElementCacheHandler Cache
7480
(string messageKey, string stateKey)
7581
=> LocalizedLogger.InfoElementAction(ElementType, Name, messageKey, LocalizationManager.GetLocalizedMessage(stateKey));
7682

83+
protected virtual LogVisualState LogVisualState =>
84+
(string messageKey, object[] values) =>
85+
{
86+
if (values == null || values.Length == 0)
87+
{
88+
LogElementAction(messageKey);
89+
}
90+
else
91+
{
92+
LogElementAction(messageKey, values);
93+
}
94+
};
95+
7796
public void Click()
7897
{
7998
LogElementAction("loc.clicking");
@@ -123,7 +142,7 @@ private void LogPageSource(WebDriverException exception)
123142
catch (WebDriverException e)
124143
{
125144
Logger.Error(exception.Message);
126-
Logger.Fatal("An exception occured while tried to save the page source", e);
145+
Logger.Fatal("An exception occurred while tried to save the page source", e);
127146
}
128147
}
129148

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Aquality.Selenium.Core.Configurations;
2+
using Aquality.Selenium.Core.Visualization;
3+
using System.Drawing;
4+
5+
namespace Aquality.Selenium.Core.Elements.Interfaces
6+
{
7+
/// <summary>
8+
/// Provides visual state of the element.
9+
/// </summary>
10+
public interface IVisualStateProvider
11+
{
12+
/// <summary>
13+
/// Gets a size object containing the height and width of this element.
14+
/// </summary>
15+
Size Size { get; }
16+
17+
/// <summary>
18+
/// Gets a point object containing the coordinates of the upper-left
19+
/// corner of this element relative to the upper-left corner of the page.
20+
/// </summary>
21+
Point Location { get; }
22+
23+
/// <summary>
24+
/// Gets an image containing the screenshot of the element.
25+
/// </summary>
26+
Image Image { get; }
27+
28+
/// <summary>
29+
/// Gets the difference between the image of the element and the provided image
30+
/// using <see cref="IImageComparator"/>
31+
/// </summary>
32+
/// <param name="theOtherOne">The image to compare the element with.</param>
33+
/// <param name="threshold">How big a difference will be ignored as a percentage - value between 0 and 1.
34+
/// If the value is null, the default value is got from <see cref="IVisualizationConfiguration"/>.</param>
35+
/// <returns>The difference between the two images as a percentage - value between 0 and 1.</returns>
36+
float GetDifference(Image theOtherOne, float? threshold = null);
37+
}
38+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/LogElementState.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,11 @@
66
/// <param name="messageKey">Key of localized message to log.</param>
77
/// <param name="stateKey">Key of localized state to log.</param>
88
public delegate void LogElementState(string messageKey, string stateKey);
9+
10+
/// <summary>
11+
/// Logs element visual state.
12+
/// </summary>
13+
/// <param name="messageKey">Key of localized message to log.</param>
14+
/// <param name="values">Values to put into localized message (if any).</param>
15+
public delegate void LogVisualState(string messageKey, params object[] values);
916
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Aquality.Selenium.Core.Elements.Interfaces;
2+
using Aquality.Selenium.Core.Utilities;
3+
using Aquality.Selenium.Core.Visualization;
4+
using OpenQA.Selenium.Remote;
5+
using System;
6+
using System.Drawing;
7+
using System.Globalization;
8+
9+
namespace Aquality.Selenium.Core.Elements
10+
{
11+
public class VisualStateProvider : IVisualStateProvider
12+
{
13+
private readonly IImageComparator imageComparator;
14+
private readonly IElementActionRetrier actionRetrier;
15+
private readonly Func<RemoteWebElement> getElement;
16+
private readonly LogVisualState logVisualState;
17+
18+
public VisualStateProvider(IImageComparator imageComparator, IElementActionRetrier actionRetrier, Func<RemoteWebElement> getElement, LogVisualState logVisualState)
19+
{
20+
this.imageComparator = imageComparator;
21+
this.actionRetrier = actionRetrier;
22+
this.getElement = getElement;
23+
this.logVisualState = logVisualState;
24+
}
25+
26+
public Size Size => GetLoggedValue(nameof(Size), element => element.Size);
27+
28+
public Point Location => GetLoggedValue(nameof(Location), element => element.Location);
29+
30+
public Image Image
31+
=> GetLoggedValue(nameof(Image), element => element.GetScreenshot().AsImage(), image => image.Size.ToString());
32+
33+
private T GetLoggedValue<T>(string name, Func<RemoteWebElement, T> getValue, Func<T, string> toString = null)
34+
{
35+
logVisualState($"loc.el.visual.get{name.ToLower()}");
36+
var value = actionRetrier.DoWithRetry(() => getValue(getElement()));
37+
logVisualState($"loc.el.visual.{name.ToLower()}.value", toString == null ? value.ToString() : toString(value));
38+
return value;
39+
}
40+
41+
public float GetDifference(Image theOtherOne, float? threshold = null)
42+
{
43+
var currentImage = Image;
44+
if (threshold == null)
45+
{
46+
logVisualState("loc.el.visual.getdifference", theOtherOne.Size.ToString());
47+
}
48+
else
49+
{
50+
logVisualState("loc.el.visual.getdifference.withthreshold", theOtherOne.Size.ToString(), threshold?.ToString("P", CultureInfo.InvariantCulture));
51+
}
52+
var value = imageComparator.PercentageDifference(currentImage, theOtherOne, threshold);
53+
logVisualState("loc.el.visual.difference.value", value.ToString("P", CultureInfo.InvariantCulture));
54+
return value;
55+
}
56+
}
57+
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/be.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,14 @@
1818
"loc.el.state.not.exist": "адсутны",
1919
"loc.el.state.enabled": "даступны",
2020
"loc.el.state.not.enabled": "недаступны",
21-
"loc.el.state.clickable": "даступны для націску"
21+
"loc.el.state.clickable": "даступны для націску",
22+
"loc.el.visual.getimage": "Атрымліваем малюнак элемента",
23+
"loc.el.visual.image.value": "Памер малюнка элемента: [{0}]",
24+
"loc.el.visual.getlocation": "Атрымліваем месцазнаходжанне элемента на старонцы",
25+
"loc.el.visual.location.value": "Месцазнаходжанне элемента на старонцы: [{0}]",
26+
"loc.el.visual.getsize": "Атрымліваем памер элемента",
27+
"loc.el.visual.size.value": "Памер элемента: [{0}]",
28+
"loc.el.visual.getdifference": "Параўноўваем малюнак элемента з малюнкам памеру [{0}]",
29+
"loc.el.visual.getdifference.withthreshold": "Параўноўваем малюнак элемента з малюнкам памеру [{0}] з парогам адчувальнасці [{1}]",
30+
"loc.el.visual.difference.value": "Адрозненне паміж цяперашнім і пададзеным малюнкамі складае [{0}]"
2231
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/en.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,14 @@
1818
"loc.el.state.not.exist": "absent",
1919
"loc.el.state.enabled": "enabled",
2020
"loc.el.state.not.enabled": "disabled",
21-
"loc.el.state.clickable": "clickable"
21+
"loc.el.state.clickable": "clickable",
22+
"loc.el.visual.getimage": "Getting image of element",
23+
"loc.el.visual.image.value": "Element's image size: [{0}]",
24+
"loc.el.visual.getlocation": "Getting element location on the page",
25+
"loc.el.visual.location.value": "Element location on the page: [{0}]",
26+
"loc.el.visual.getsize": "Getting element size",
27+
"loc.el.visual.size.value": "Element size: [{0}]",
28+
"loc.el.visual.getdifference": "Comparing element's image to image of size [{0}]",
29+
"loc.el.visual.getdifference.withthreshold": "Comparing element's image to image of size [{0}] with threshold [{1}]",
30+
"loc.el.visual.difference.value": "The difference between the current and the given images is [{0}]"
2231
}

Aquality.Selenium.Core/src/Aquality.Selenium.Core/Resources/Localization/ru.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,14 @@
1818
"loc.el.state.not.exist": "отсутствующим",
1919
"loc.el.state.enabled": "доступным",
2020
"loc.el.state.not.enabled": "недоступным",
21-
"loc.el.state.clickable": "кликабельным"
21+
"loc.el.state.clickable": "кликабельным",
22+
"loc.el.visual.getimage": "Получаем изображение элемента",
23+
"loc.el.visual.image.value": "Размеры изображения элемента : [{0}]",
24+
"loc.el.visual.getlocation": "Получаем положение элемента на странице",
25+
"loc.el.visual.location.value": "Положение элемента на странице: [{0}]",
26+
"loc.el.visual.getsize": "Получаем размер элемента",
27+
"loc.el.visual.size.value": "Размер элемента: [{0}]",
28+
"loc.el.visual.getdifference": "Сравниваем изображение элемента с изображением размера [{0}]",
29+
"loc.el.visual.getdifference.withthreshold": "Сравниваем изображение элемента с изображением размера [{0}] с уровнем шума [{1}]",
30+
"loc.el.visual.difference.value": "Отличие текущего изображения от предоставленного составляет [{0}]"
2231
}

Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using Aquality.Selenium.Core.Applications;
22
using Aquality.Selenium.Core.Configurations;
3-
using Aquality.Selenium.Core.Utilities;
43
using Microsoft.Extensions.DependencyInjection;
5-
using Microsoft.Win32;
64
using System;
7-
using System.Diagnostics;
85
using WebDriverManager;
96
using WebDriverManager.DriverConfigs.Impl;
107
using WebDriverManager.Helpers;
@@ -29,8 +26,7 @@ public static IServiceProvider ServiceProvider
2926
{
3027
SetServiceProvider(value);
3128
}
32-
}
33-
29+
}
3430

3531
private static ChromeApplication StartChrome(IServiceProvider services)
3632
{

Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/CachedElementTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Aquality.Selenium.Core.Elements;
22
using Aquality.Selenium.Core.Elements.Interfaces;
3-
using Aquality.Selenium.Core.Tests.Applications.WindowsApp.Elements;
3+
using Aquality.Selenium.Core.Tests.Applications.Browser.Elements;
44
using Aquality.Selenium.Core.Utilities;
55
using Aquality.Selenium.Core.Waitings;
66
using Microsoft.Extensions.DependencyInjection;

0 commit comments

Comments
 (0)