From 74801915d4e0a7ced970b9eb8b95784263f3d5be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:40:23 +0000 Subject: [PATCH] [ci-fix] De-flake SafeAreaShouldWorkOnAllShellTabs (StaleElementReferenceException) The Android UI test SafeAreaShouldWorkOnAllShellTabs intermittently fails with a StaleElementReferenceException thrown from IUIElement.GetRect(). After App.TapTab switches tabs, the tab-switch animation recycles the EdgeLabel view between the WaitForElement query and the geometry read, invalidating the element reference. Add a stale-resilient WaitForElementAndGetRect helper that re-finds the element and retries GetRect() when the reference goes stale (mirroring the existing stale-element handling in AppiumMouseActions), and use it at the two post-navigation rect reads in the test. This makes the geometry read deterministic without weakening the assertions, which are left untouched. Refs: dotnet/maui#36259 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/Issues/Issue33034.cs | 4 +-- .../src/UITest.Appium/HelperExtensions.cs | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33034.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33034.cs index e2b9d4836436..d6b8ba4e9099 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33034.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33034.cs @@ -20,12 +20,12 @@ public void SafeAreaShouldWorkOnAllShellTabs() App.SetOrientationLandscape(); //Adding delay to allow for orientation change to complete Thread.Sleep(2000); - var initialRect = App.WaitForElement("EdgeLabel").GetRect(); + var initialRect = App.WaitForElementAndGetRect("EdgeLabel"); App.TapTab("Second Tab"); App.WaitForElement("EdgeLabel"); App.TapTab("First Tab"); - var afterSwitchRect = App.WaitForElement("EdgeLabel").GetRect(); + var afterSwitchRect = App.WaitForElementAndGetRect("EdgeLabel"); Assert.That(afterSwitchRect.X, Is.EqualTo(initialRect.X).Within(5)); Assert.That(afterSwitchRect.Width, Is.EqualTo(initialRect.Width).Within(5)); diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs index 9bd0aefa2bc6..1343d574af89 100644 --- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs +++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs @@ -179,6 +179,39 @@ public static Rectangle GetRect(this IUIElement element) })!; } + /// + /// Waits for an element and reads its on-screen rectangle, re-querying and retrying if the + /// element reference becomes stale. A navigation or tab-switch animation can recycle the + /// underlying view between the element query and the geometry read, which surfaces as a + /// from . Re-finding + /// the element and reading again once it has settled keeps the geometry read deterministic + /// without changing what the caller asserts on. This mirrors the existing stale-element handling + /// used elsewhere in the framework (see AppiumMouseActions). + /// + /// Represents the main gateway to interact with an app. + /// The id of the element to locate and measure. + /// Optional timeout for each wait operation. Default is null, which uses the default timeout. + public static Rectangle WaitForElementAndGetRect(this IApp app, string elementId, TimeSpan? timeout = null) + { + StaleElementReferenceException? lastStale = null; + + for (int attempt = 0; attempt < 3; attempt++) + { + try + { + return app.WaitForElement(elementId, timeout: timeout).GetRect(); + } + catch (StaleElementReferenceException ex) + { + // The view was recycled between the query and the geometry read (typically + // mid-animation); re-find the element and read again now that it should have settled. + lastStale = ex; + } + } + + throw lastStale!; + } + /// /// Determine if a form or form-like element (checkbox, select, etc...) is selected. ///