From 6c0b4d3e28e776ae9c9229750634e183befc3920 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:41:47 +0000 Subject: [PATCH] [ci-fix] De-flake iOS keyboard-scrolling UI test helper (NoSuchElementException race) EditorsScrollingPageTest and its sibling KeyboardScrolling helpers intermittently threw OpenQA.Selenium.NoSuchElementException on iOS because the shared helper looked up soft-keyboard elements with a bare driver.FindElement while the keyboard (and its Done/next buttons) were still animating in. Route the three lookups through a new non-throwing polled WaitForKeyboardElement helper (FindElements + short timeout) so the wait is deterministic. Assertions are unchanged: if an element genuinely never appears the poll times out and the existing ClassicAssert.NotNull(keyboardPositionNullable) still fails, so the test's signal is preserved. This is a de-flake, not a mute. Refs: dotnet/maui#36396 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/KeyboardScrolling.cs | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs index 6b886cb94cac..f1cc14f5b4ac 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/KeyboardScrolling.cs @@ -108,12 +108,37 @@ internal static void HideKeyboard(IApp app, AppiumDriver? driver, bool isEditor) app.DismissKeyboard(); } + // Polls for a keyboard-related element using the non-throwing FindElements API instead of a + // bare FindElement. On iOS the soft keyboard (and its buttons) can still be animating in when + // the lookup runs, so a bare FindElement throws NoSuchElementException intermittently. Returns + // null if the element never appears within the timeout so callers can react deterministically. + // See dotnet/maui#36396 and dotnet/maui#36393. + static AppiumElement? WaitForKeyboardElement(AppiumDriver? driver, OpenQA.Selenium.By locator, TimeSpan? timeout = null) + { + if (driver is null) + return null; + + timeout ??= TimeSpan.FromSeconds(5); + var start = DateTime.Now; + while (true) + { + var elements = driver.FindElements(locator); + if (elements.Count > 0) + return elements[0]; + + if (DateTime.Now - start >= timeout.Value) + return null; + + System.Threading.Thread.Sleep(200); + } + } + internal static System.Drawing.Point? FindiOSKeyboardLocation(AppiumDriver? driver) { if (driver?.IsKeyboardShown() == true) { - var keyboard = driver.FindElement(MobileBy.ClassName("UIAKeyboard")); - return keyboard.Location; + var keyboard = WaitForKeyboardElement(driver, MobileBy.ClassName("UIAKeyboard")); + return keyboard?.Location; } return null; } @@ -121,7 +146,7 @@ internal static void HideKeyboard(IApp app, AppiumDriver? driver, bool isEditor) internal static void CloseiOSEditorKeyboard(IApp app, AppiumDriver? driver) { var doneButtonName = app is AppiumIOSApp iosApp && HelperExtensions.IsIOS26OrHigher(iosApp) ? "selected" : "Done"; - var keyboardDoneButton = driver?.FindElement(MobileBy.Name(doneButtonName)); + var keyboardDoneButton = WaitForKeyboardElement(driver, MobileBy.Name(doneButtonName)); keyboardDoneButton?.Click(); } @@ -148,7 +173,7 @@ internal static void EntryNextEditorScrollingTest(IApp app, string galleryName) // Unintentionally types a 'V' but also presses the next keyboard key internal static void NextiOSKeyboardPress(AppiumDriver? driver) { - var keyboard = driver?.FindElement(MobileBy.ClassName("UIAKeyboard")); + var keyboard = WaitForKeyboardElement(driver, MobileBy.ClassName("UIAKeyboard")); keyboard?.SendKeys("\n"); }