Skip to content
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,7 @@ public void HorizontalStackLayout_Spacing_WithLandscape()
App.WaitForElement("Apply");
App.Tap("Apply");

#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
#endif
}

[Test]
Expand All @@ -431,11 +427,7 @@ public void VerticalStackLayout_Spacing_WithLandscape()
App.WaitForElement("Apply");
App.Tap("Apply");

#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot(tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
#endif
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ public void ShouldFlyoutTextWrapsInLandscape()
App.WaitForElement("OpenFlyoutButton");
App.Tap("OpenFlyoutButton");
App.SetOrientationLandscape();
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,22 @@ public void ButtonsLayoutResolveWhenParentSizeChanges()
{
App.SetOrientationPortrait();
#endif
WaitForAllElements();
var changeBoundsButton = App.WaitForElement("ChangeBoundsButton");
// Use retryTimeout to allow layout to settle
VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "Original", tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
WaitForAllElements();
var changeBoundsButton = App.WaitForElement("ChangeBoundsButton");
// Use retryTimeout to allow layout to settle
VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "Original", tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));

changeBoundsButton.Click();
changeBoundsButton.Click();

WaitForAllElements();
VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "SizeButtonsDownPortrait", tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
WaitForAllElements();
VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "SizeButtonsDownPortrait", tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));

#if IOS || ANDROID
App.SetOrientationLandscape();

WaitForAllElements();
// Use retryTimeout to allow orientation change to settle
#if ANDROID
VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "SizeButtonsDownLandscape", cropLeft: 125, tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
#else

VerifyScreenshot(TestContext.CurrentContext.Test.MethodName + "SizeButtonsDownLandscape", tolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2));
#endif

changeBoundsButton.Click();
WaitForAllElements();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ public void BorderBackgroundSizeUpdatesWhenRotatingScreen()
App.WaitForElement("SetHeightTo200");
App.Tap("SetHeightTo200");
App.SetOrientationLandscape();
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ public void CarouselViewItemShouldScaleProperly()
App.WaitForElement("Baboon");
App.SetOrientationLandscape();
App.WaitForElement("Baboon");
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}

[TearDown]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ public void ToolbarExtendsAllTheWayLeftAndRight_FlyoutPage()
App.WaitForElement("ContentGrid");
App.SetOrientationLandscape();
App.WaitForElement("ContentGrid");
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ public void ToolbarExtendsAllTheWayLeftAndRight_NavigationPage()
App.WaitForElement("ContentGrid");
App.SetOrientationLandscape();
App.WaitForElement("ContentGrid");
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ public void ToolbarExtendsAllTheWayLeftAndRight_Shell()
App.WaitForElement("ContentGrid");
App.SetOrientationLandscape();
App.WaitForElement("ContentGrid");
#if ANDROID
VerifyScreenshot(cropLeft: 125);
#else
VerifyScreenshot();
#endif
}
}
#endif
72 changes: 37 additions & 35 deletions src/Controls/tests/TestCases.Shared.Tests/UITest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ namespace Microsoft.Maui.TestCases.Tests
#elif IOSUITEST
[TestFixture(TestDevice.iOS)]
#elif MACUITEST
[TestFixture(TestDevice.Mac)]
[TestFixture(TestDevice.Mac)]
#elif WINTEST
[TestFixture(TestDevice.Windows)]
[TestFixture(TestDevice.Windows)]
#endif
public abstract class UITest : UITestBase
{
Expand Down Expand Up @@ -96,11 +96,11 @@ public override IConfig GetTestConfig()
config.SetProperty("Udid", udid);
}
else
{
{
config.SetProperty("DeviceName", Environment.GetEnvironmentVariable("DEVICE_NAME") ?? "iPhone Xs");
config.SetProperty("PlatformVersion", Environment.GetEnvironmentVariable("PLATFORM_VERSION") ?? _defaultiOSVersion);
}

config.SetProperty("Headless", bool.Parse(Environment.GetEnvironmentVariable("HEADLESS") ?? "false"));
break;
case TestDevice.Windows:
Expand Down Expand Up @@ -157,7 +157,7 @@ public override void LaunchAppWithTest()
{
App.LaunchApp();
}

/// <summary>
/// Verifies the screenshots and returns an exception in case of failure.
/// </summary>
Expand All @@ -177,8 +177,6 @@ public void VerifyScreenshotOrSetException(
string? name = null,
TimeSpan? retryDelay = null,
TimeSpan? retryTimeout = null,
int cropLeft = 0,
int cropRight = 0,
int cropTop = 0,
int cropBottom = 0,
double tolerance = 0.0
Expand All @@ -189,7 +187,7 @@ public void VerifyScreenshotOrSetException(
{
try
{
VerifyScreenshot(name, retryDelay, retryTimeout, cropLeft, cropRight, cropTop, cropBottom, tolerance
VerifyScreenshot(name, retryDelay, retryTimeout, cropTop, cropBottom, tolerance
#if MACUITEST || WINTEST
, includeTitleBar
#endif
Expand All @@ -208,13 +206,11 @@ public void VerifyScreenshotOrSetException(
/// <param name="retryDelay">Optional delay between retry attempts when verification fails. Default is 500ms.</param>
/// <param name="retryTimeout">Optional total time to keep retrying before giving up. If not specified, only one retry is attempted.
/// Use this for animations with variable completion times (e.g., retryTimeout: TimeSpan.FromSeconds(2)).</param>
/// <param name="cropLeft">Number of pixels to crop from the left of the screenshot.</param>
/// <param name="cropRight">Number of pixels to crop from the right of the screenshot.</param>
/// <param name="cropTop">Number of pixels to crop from the top of the screenshot.</param>
/// <param name="cropBottom">Number of pixels to crop from the bottom of the screenshot.</param>
/// <param name="tolerance">Tolerance level for image comparison as a percentage from 0 to 100.</param>
#if MACUITEST || WINTEST
/// <param name="includeTitleBar">Whether to include the title bar in the screenshot comparison.</param>
/// <param name="includeTitleBar">Whether to include the title bar in the screenshot comparison.</param>
#endif
/// <remarks>
/// This method immediately throws an exception if the screenshot verification fails.
Expand Down Expand Up @@ -242,8 +238,6 @@ public void VerifyScreenshot(
string? name = null,
TimeSpan? retryDelay = null,
TimeSpan? retryTimeout = null,
int cropLeft = 0,
int cropRight = 0,
int cropTop = 0,
int cropBottom = 0,
double tolerance = 0.0 // Add tolerance parameter (0.05 = 5%)
Expand All @@ -253,14 +247,14 @@ public void VerifyScreenshot(
)
{
retryDelay ??= TimeSpan.FromMilliseconds(500);

// If retryTimeout is specified, keep retrying until timeout expires
// Otherwise, just retry once (backward compatible behavior)
if (retryTimeout.HasValue)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
Exception? lastException = null;

while (stopwatch.Elapsed < retryTimeout.Value)
{
try
Expand All @@ -277,7 +271,7 @@ public void VerifyScreenshot(
}
}
}

// Final attempt after timeout
try
{
Expand Down Expand Up @@ -407,13 +401,19 @@ but both can happen.

var actualImage = new ImageSnapshot(screenshotPngBytes, ImageSnapshotFormat.PNG);

// Get the current orientation, default to Portrait (e.g. Windows, Mac)
var orientation = OpenQA.Selenium.ScreenOrientation.Portrait;
#if ANDROID || IOSUITEST
orientation = App.GetOrientation();
#endif

// For Android and iOS, crop off the OS status bar at the top since it's not part of the
// app itself and contains the time, which always changes. For WinUI, crop off the title
// bar at the top as it varies slightly based on OS theme and is also not part of the app.
int cropFromTop = _testDevice switch
{
TestDevice.Android => environmentName == "android-notch-36" ? 112 : 60,
TestDevice.iOS => environmentName == "ios-iphonex" ? 90 : 110,
TestDevice.iOS => orientation == OpenQA.Selenium.ScreenOrientation.Portrait ? (environmentName == "ios-iphonex" ? 90 : 110) : 0,
TestDevice.Windows => 32,
TestDevice.Mac => 29,
_ => 0,
Expand All @@ -425,34 +425,36 @@ but both can happen.
cropFromTop = 0;
}
#endif

// For Android also crop the 3 button nav from the bottom, since it's not part of the
// For Android crop the 3 button nav from the bottom and left based on orientation, since it's not part of the
// app itself and the button color can vary (the buttons change clear briefly when tapped).
// For iOS, crop the home indicator at the bottom.
int cropFromBottom = _testDevice switch
{
TestDevice.Android => environmentName == "android-notch-36" ? 52 : 125,
TestDevice.iOS => 40,
_ => 0,
};

// Cropping from the left or right can be applied for any platform using the user-specified crop values.
// The default values are set based on the platform, but the final cropping is determined by the parameters passed in.
// This allows cropping of UI elements (such as navigation bars or home indicators) for any platform as needed.
int cropFromLeft = 0;
int cropFromRight = 0;
int cropFromBottom = 0;
if (_testDevice == TestDevice.Android)
{
if (orientation == OpenQA.Selenium.ScreenOrientation.Portrait)
{
cropFromBottom = environmentName == "android-notch-36" ? 52 : 125;
}
else
{
cropFromLeft = 125;
}
}
else if (_testDevice == TestDevice.iOS)
{
cropFromBottom = 40;
}

cropFromLeft = cropLeft > 0 ? cropLeft : cropFromLeft;
cropFromRight = cropRight > 0 ? cropRight : cropFromRight;
cropFromTop = cropTop > 0 ? cropTop : cropFromTop;
cropFromBottom = cropBottom > 0 ? cropBottom : cropFromBottom;

if (cropFromLeft > 0 || cropFromRight > 0 || cropFromTop > 0 || cropFromBottom > 0)
if (cropFromTop > 0 || cropFromBottom > 0 || cropFromLeft > 0)
{
IImageEditor imageEditor = _imageEditorFactory.CreateImageEditor(actualImage);
(int width, int height) = imageEditor.GetSize();

imageEditor.Crop(cropFromLeft, cropFromTop, width - cropFromLeft - cropFromRight, height - cropFromTop - cropFromBottom);
imageEditor.Crop(cropFromLeft, cropFromTop, width - cropFromLeft, height - cropFromTop - cropFromBottom);

actualImage = imageEditor.GetUpdatedImage();
}
Expand Down Expand Up @@ -552,7 +554,7 @@ protected virtual void TryToResetTestState()
{
Reset();
}

protected override void FixtureSetup()
{
int retries = 0;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading