Skip to content

Commit 8e5a173

Browse files
jsuarezruizCopilot
andauthored
[Testing] Include a tolerance parameter in the Verifyscreenshot method (#29776)
* Added tolerance parameter to the VerifyScreenshot method * More changes * Update src/Controls/tests/TestCases.Shared.Tests/UITest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Controls/tests/TestCases.Shared.Tests/UITest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f8e2404 commit 8e5a173

File tree

1 file changed

+99
-4
lines changed
  • src/Controls/tests/TestCases.Shared.Tests

1 file changed

+99
-4
lines changed

src/Controls/tests/TestCases.Shared.Tests/UITest.cs

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Reflection;
2+
using System.Text.RegularExpressions;
23
using ImageMagick;
34
using NUnit.Framework;
5+
using NUnit.Framework.Constraints;
46
using UITest.Appium;
57
using UITest.Appium.NUnit;
68
using UITest.Core;
@@ -123,15 +125,16 @@ public void VerifyScreenshotOrSetException(
123125
string? name = null,
124126
TimeSpan? retryDelay = null,
125127
int cropTop = 0,
126-
int cropBottom = 0
128+
int cropBottom = 0,
129+
double tolerance = 0.0
127130
#if MACUITEST || WINTEST
128131
, bool includeTitleBar = false
129132
#endif
130133
)
131134
{
132135
try
133136
{
134-
VerifyScreenshot(name, retryDelay, cropTop, cropBottom
137+
VerifyScreenshot(name, retryDelay, cropTop, cropBottom, tolerance
135138
#if MACUITEST || WINTEST
136139
, includeTitleBar
137140
#endif
@@ -143,11 +146,42 @@ public void VerifyScreenshotOrSetException(
143146
}
144147
}
145148

149+
/// <summary>
150+
/// Verifies a screenshot by comparing it against a baseline image and throws an exception if verification fails.
151+
/// </summary>
152+
/// <param name="name">Optional name for the screenshot. If not provided, a default name will be used.</param>
153+
/// <param name="retryDelay">Optional delay between retry attempts when verification fails.</param>
154+
/// <param name="cropTop">Number of pixels to crop from the top of the screenshot.</param>
155+
/// <param name="cropBottom">Number of pixels to crop from the bottom of the screenshot.</param>
156+
/// <param name="tolerance">Tolerance level for image comparison as a percentage from 0 to 100.</param>
157+
#if MACUITEST || WINTEST
158+
/// <param name="includeTitleBar">Whether to include the title bar in the screenshot comparison.</param>
159+
#endif
160+
/// <remarks>
161+
/// This method immediately throws an exception if the screenshot verification fails.
162+
/// For batch verification of multiple screenshots, consider using <see cref="VerifyScreenshotOrSetException"/> instead.
163+
/// </remarks>
164+
/// <example>
165+
/// <code>
166+
/// // Exact match (no tolerance)
167+
/// VerifyScreenshot("LoginScreen");
168+
///
169+
/// // Allow 2% difference for dynamic content
170+
/// VerifyScreenshot("DashboardWithTimestamp", tolerance: 2.0);
171+
///
172+
/// // Allow 5% difference for animations or slight rendering variations
173+
/// VerifyScreenshot("ButtonHoverState", tolerance: 5.0);
174+
///
175+
/// // Combined with cropping and tolerance
176+
/// VerifyScreenshot("HeaderSection", cropTop: 50, cropBottom: 100, tolerance: 3.0);
177+
/// </code>
178+
/// </example>
146179
public void VerifyScreenshot(
147180
string? name = null,
148181
TimeSpan? retryDelay = null,
149182
int cropTop = 0,
150-
int cropBottom = 0
183+
int cropBottom = 0,
184+
double tolerance = 0.0 // Add tolerance parameter (0.05 = 5%)
151185
#if MACUITEST || WINTEST
152186
, bool includeTitleBar = false
153187
#endif
@@ -291,8 +325,69 @@ but both can happen.
291325
actualImage = imageEditor.GetUpdatedImage();
292326
}
293327

294-
_visualRegressionTester.VerifyMatchesSnapshot(name!, actualImage, environmentName: environmentName, testContext: _visualTestContext);
328+
// Apply tolerance if specified
329+
if (tolerance > 0)
330+
{
331+
VerifyWithTolerance(name!, actualImage, environmentName, tolerance);
332+
}
333+
else
334+
{
335+
_visualRegressionTester.VerifyMatchesSnapshot(name!, actualImage, environmentName: environmentName, testContext: _visualTestContext);
336+
}
337+
}
338+
}
339+
340+
void VerifyWithTolerance(string name, ImageSnapshot actualImage, string environmentName, double tolerance)
341+
{
342+
if (tolerance > 15)
343+
{
344+
throw new ArgumentException($"Tolerance {tolerance}% exceeds the acceptable limit. Please review whether this requires a different test or if it is a bug.");
295345
}
346+
347+
try
348+
{
349+
_visualRegressionTester.VerifyMatchesSnapshot(name, actualImage, environmentName: environmentName, testContext: _visualTestContext);
350+
}
351+
catch (Exception ex) when (IsVisualDifferenceException(ex))
352+
{
353+
var difference = ExtractDifferencePercentage(ex);
354+
if (difference <= tolerance)
355+
{
356+
// Log warning but pass test
357+
TestContext.WriteLine($"Visual difference {difference}% within tolerance {tolerance}% for '{name}' on {environmentName}");
358+
return;
359+
}
360+
throw; // Re-throw if exceeds tolerance
361+
}
362+
}
363+
364+
bool IsVisualDifferenceException(Exception ex)
365+
{
366+
// Check if this is a visual regression failure
367+
return ex.GetType().Name.Contains("Assert", StringComparison.Ordinal) ||
368+
ex.Message.Contains("Snapshot different", StringComparison.Ordinal) ||
369+
ex.Message.Contains("baseline", StringComparison.Ordinal) ||
370+
ex.Message.Contains("different", StringComparison.Ordinal);
371+
}
372+
373+
double ExtractDifferencePercentage(Exception ex)
374+
{
375+
var message = ex.Message;
376+
377+
// Extract percentage from pattern: "X,XX% difference"
378+
var match = Regex.Match(message, @"(\d+,\d+)%\s*difference", RegexOptions.IgnoreCase);
379+
if (match.Success)
380+
{
381+
var percentageString = match.Groups[1].Value.Replace(',', '.');
382+
if (double.TryParse(percentageString, System.Globalization.NumberStyles.Float,
383+
System.Globalization.CultureInfo.InvariantCulture, out var percentage))
384+
{
385+
return percentage;
386+
}
387+
}
388+
389+
// If can't extract specific percentage, throw an exception to indicate failure
390+
throw new InvalidOperationException("Unable to extract difference percentage from exception message.");
296391
}
297392

298393
protected void VerifyInternetConnectivity()

0 commit comments

Comments
 (0)