diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/DragAndDropUITests.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/DragAndDropUITests.cs index 60a2ea803955..1b76ffe5dbbc 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/DragAndDropUITests.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/DragAndDropUITests.cs @@ -33,50 +33,24 @@ public void DragEvents() App.WaitForElement("LabelDragElement"); App.DragAndDrop("LabelDragElement", "DragTarget"); - App.WaitForElement("DragStartEventsLabel"); - var textAfterDragStart = App.FindElement("DragStartEventsLabel").GetText(); - - if (string.IsNullOrEmpty(textAfterDragStart)) - { - Assert.Fail("Text was expected: Drag start event"); - } - else - { - Assert.That(textAfterDragStart, Is.EqualTo("DragStarting")); - } - - App.WaitForElement("DragOverEventsLabel"); - var textAfterDragOver = App.FindElement("DragOverEventsLabel").GetText(); - if (string.IsNullOrEmpty(textAfterDragOver)) - { - Assert.Fail("Text was expected: Drag over event"); - } - else - { - Assert.That(textAfterDragOver, Is.EqualTo("DragOver")); - } - - App.WaitForElement("DragCompletedEventsLabel"); - var textAfterDragComplete = App.FindElement("DragCompletedEventsLabel").GetText(); - if (string.IsNullOrEmpty(textAfterDragComplete)) - { - Assert.Fail("Text was expected: Drag complete event"); - } - else - { - Assert.That(textAfterDragComplete, Is.EqualTo("DropCompleted")); - } + AssertEventText("DragStartEventsLabel", "DragStarting"); + AssertEventText("DragOverEventsLabel", "DragOver"); + AssertEventText("DragCompletedEventsLabel", "DropCompleted"); + AssertEventText("DropEventsLabel", "Drop"); + } - App.WaitForElement("DropEventsLabel"); - var textAfterDrop = App.FindElement("DropEventsLabel").GetText(); - if (string.IsNullOrEmpty(textAfterDrop)) - { - Assert.Fail("Text was expected: Drop event"); - } - else - { - Assert.That(textAfterDrop, Is.EqualTo("Drop")); - } + void AssertEventText(string automationId, string expectedText) + { + // Wait for the label's text to become EXACTLY the expected value. A substring wait is + // unreliable here because each label's placeholder (e.g. "DragOverEvents: ") already + // contains the expected event name (e.g. "DragOver"), so a Contains-based wait would pass + // immediately on the placeholder. WaitForTextEqualToElement polls until the text matches + // exactly, so the Assert below fails only on a genuine timeout; no separate GetText re-read + // is needed (it would only re-open a window for transient Appium flakiness). + Assert.That( + App.WaitForTextEqualToElement(automationId, expectedText), + Is.True, + $"Timed out waiting for {automationId} to become '{expectedText}'."); } [Test] @@ -94,7 +68,7 @@ public void DragAndDropBetweenLayouts() App.WaitForElement("Green"); App.DragAndDrop("Red", "Green"); - App.WaitForElement("DragStartEventsLabel"); + App.WaitForTextEqualToElement("DragStartEventsLabel", "DragStarting"); var textAfterDragStart = App.FindElement("DragStartEventsLabel").GetText(); if (string.IsNullOrEmpty(textAfterDragStart)) @@ -106,7 +80,7 @@ public void DragAndDropBetweenLayouts() Assert.That(textAfterDragStart, Is.EqualTo("DragStarting")); } - App.WaitForElement("DragOverEventsLabel"); + App.WaitForTextEqualToElement("DragOverEventsLabel", "DragOver"); var textAfterDragOver = App.FindElement("DragOverEventsLabel").GetText(); if (string.IsNullOrEmpty(textAfterDragOver)) { @@ -117,7 +91,7 @@ public void DragAndDropBetweenLayouts() Assert.That(textAfterDragOver, Is.EqualTo("DragOver")); } - App.WaitForElement("DragCompletedEventsLabel"); + App.WaitForTextEqualToElement("DragCompletedEventsLabel", "DropCompleted"); var textAfterDragComplete = App.FindElement("DragCompletedEventsLabel").GetText(); if (string.IsNullOrEmpty(textAfterDragComplete)) { @@ -139,7 +113,7 @@ public void DragAndDropBetweenLayouts() Assert.That(rainbowColorText, Is.EqualTo("RainbowColorsAdd:Red")); } - App.WaitForElement("DropEventsLabel"); + App.WaitForTextEqualToElement("DropEventsLabel", "Drop"); var textAfterDrop = App.FindElement("DropEventsLabel").GetText(); if (string.IsNullOrEmpty(textAfterDrop)) { diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs index 6bbd5cfae4ec..19abd1b3a7e4 100644 --- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs +++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs @@ -1054,30 +1054,54 @@ public static void WaitForNoElement( } public static bool WaitForTextToBePresentInElement(this IApp app, string automationId, string text, TimeSpan? timeout = null) + => app.WaitForText(automationId, text, s => s.Contains(text, StringComparison.OrdinalIgnoreCase), timeout); + + /// + /// Waits until the element's text is exactly equal to (ordinal), rather + /// than merely containing it. Use this when the element's placeholder/initial text already + /// contains the expected value as a substring, which would make a Contains-based wait pass + /// prematurely on the placeholder. + /// + public static bool WaitForTextEqualToElement(this IApp app, string automationId, string text, TimeSpan? timeout = null) + => app.WaitForText(automationId, text, s => string.Equals(s, text, StringComparison.Ordinal), timeout); + + /// + /// Shared polling loop for the text-wait helpers. Repeatedly reads the element's text and + /// returns as soon as is satisfied. On + /// timeout it logs the last observed text (and the expected value) so a stalled or + /// placeholder-stuck label is distinguishable from a text-read failure, then returns + /// . + /// + static bool WaitForText(this IApp app, string automationId, string expected, Func matches, TimeSpan? timeout) { timeout ??= DefaultTimeout; TimeSpan retryFrequency = TimeSpan.FromMilliseconds(500); DateTime start = DateTime.Now; + string? lastObservedText = null; while (true) { var element = app.FindElements(automationId).FirstOrDefault(); - if (element is not null && element.TryGetText(out var s) && s.Contains(text, StringComparison.OrdinalIgnoreCase)) + if (element is not null && element.TryGetText(out var s)) { - return true; + lastObservedText = s; + if (matches(s)) + { + return true; + } } long elapsed = DateTime.Now.Subtract(start).Ticks; if (elapsed >= timeout.Value.Ticks) { - Debug.WriteLine($">>>>> {elapsed} ticks elapsed, timeout value is {timeout.Value.Ticks}"); + Debug.WriteLine($">>>>> {elapsed} ticks elapsed, timeout value is {timeout.Value.Ticks}; last observed text for '{automationId}' was '{lastObservedText ?? ""}', expected '{expected}'"); return false; } - Task.Delay(retryFrequency.Milliseconds).Wait(); + Task.Delay(retryFrequency).Wait(); } }