From 9b0f534dca1c1423eb1c656da8e2476f0a681c6a Mon Sep 17 00:00:00 2001 From: Ben Olden-Cooligan Date: Sat, 8 Jul 2023 18:26:36 -0700 Subject: [PATCH] WIP: Improve Appium test flakiness --- NAPS2.App.Tests/Appium/AppiumTests.cs | 53 +++++++++----------- NAPS2.App.Tests/Appium/ImportAndSaveTests.cs | 5 +- NAPS2.App.Tests/Appium/ScanAndSaveTests.cs | 16 +++--- 3 files changed, 33 insertions(+), 41 deletions(-) diff --git a/NAPS2.App.Tests/Appium/AppiumTests.cs b/NAPS2.App.Tests/Appium/AppiumTests.cs index 52ca5219b..a73f5ea1d 100644 --- a/NAPS2.App.Tests/Appium/AppiumTests.cs +++ b/NAPS2.App.Tests/Appium/AppiumTests.cs @@ -1,3 +1,4 @@ +using System.Linq.Expressions; using System.Threading; using NAPS2.App.Tests.Targets; using NAPS2.Sdk.Tests; @@ -22,6 +23,7 @@ public class AppiumTests : ContextualTests public void Init(IAppTestTarget target) { _session = StartSession(target.Gui, FolderPath); + ResetMainWindow(); } public override void Dispose() @@ -37,32 +39,35 @@ public class AppiumTests : ContextualTests base.Dispose(); } - protected void WaitUntilGone(string name, int timeoutInMs) + protected void ResetMainWindow() { + _session.SwitchTo().Window(WaitFor(() => _session.WindowHandles.Single())); + } + + protected T WaitFor(Expression> expr, int timeoutInMs = 2000) + { + var func = expr.Compile(); var stopwatch = Stopwatch.StartNew(); - try + while (true) { - while (true) + try { - if (_session.FindElementsByName(name).Count == 0) + var value = func(); + if (value is null or false) { - break; + throw new Exception(); } + return value; + } + catch (Exception) + { if (stopwatch.ElapsedMilliseconds > timeoutInMs) { - throw new WebDriverException("Timeout waiting for element to be gone"); + throw new Exception($"Timed out waiting for \"{expr.Body}\""); } Thread.Sleep(100); } } - catch (InvalidOperationException) - { - } - } - - protected void ResetMainWindow() - { - _session.SwitchTo().Window(_session.WindowHandles[0]); } protected void ClickAt(WindowsElement element) @@ -75,7 +80,7 @@ public class AppiumTests : ContextualTests protected void ClickAtName(string name) { - ClickAt(WaitAndFindElementByName(name)); + ClickAt(WaitFor(() => _session.FindElementByName(name))); } protected void DoubleClickAt(WindowsElement element) @@ -89,23 +94,11 @@ public class AppiumTests : ContextualTests protected void DoubleClickAtName(string name) { - DoubleClickAt(WaitAndFindElementByName(name)); + DoubleClickAt(WaitFor(() => _session.FindElementByName(name))); } - protected WindowsElement WaitAndFindElementByName(string name) + protected bool HasElementWithName(string name) { - int i = 0; - while(true) - { - try - { - return _session.FindElementByName(name); - } - catch (WebDriverException) - { - if (++i > 10) throw; - Thread.Sleep(100); - } - } + return _session.FindElementsByName(name).Count > 0; } } \ No newline at end of file diff --git a/NAPS2.App.Tests/Appium/ImportAndSaveTests.cs b/NAPS2.App.Tests/Appium/ImportAndSaveTests.cs index 36e3d1ead..3b7d914d2 100644 --- a/NAPS2.App.Tests/Appium/ImportAndSaveTests.cs +++ b/NAPS2.App.Tests/Appium/ImportAndSaveTests.cs @@ -34,14 +34,13 @@ public class ImportAndSaveTests : AppiumTests ClickAtName("Save PDF"); ResetMainWindow(); - var fileNameElements = _session.FindElementsByName("File name:"); - var fileTextBox = fileNameElements.Last(); + var fileTextBox = WaitFor(() => _session.FindElementsByName("File name:").Last()); ClickAt(fileTextBox); fileTextBox.SendKeys("test.pdf"); ClickAtName("Save"); // Wait for the save to finish Thread.Sleep(100); - WaitUntilGone("Cancel", 10_000); + WaitFor(() => !HasElementWithName("Cancel"), 10_000); var path = Path.Combine(FolderPath, "test.pdf"); PdfAsserts.AssertImages(path, diff --git a/NAPS2.App.Tests/Appium/ScanAndSaveTests.cs b/NAPS2.App.Tests/Appium/ScanAndSaveTests.cs index f37895ff2..757320f2c 100644 --- a/NAPS2.App.Tests/Appium/ScanAndSaveTests.cs +++ b/NAPS2.App.Tests/Appium/ScanAndSaveTests.cs @@ -26,17 +26,17 @@ public class ScanAndSaveTests : AppiumTests // Click OK in the wia device dialog (selecting the first available device by default) // TODO: More consistent way to pick the right OK button ClickAt(_session.FindElementsByName("OK")[0]); - WaitUntilGone("Properties", 1_000); + WaitFor(() => !HasElementWithName("Properties")); // Click OK in the profile settings window ClickAtName("OK"); + WaitFor(() => HasElementWithName("Cancel")); // Wait for scanning to finish - WaitUntilGone("Cancel", 30_000); + WaitFor(() => !HasElementWithName("Cancel"), 30_000); ResetMainWindow(); // Save "test.pdf" in the default location (which will be the test data path as NAPS2 knows we're in a test)^ ClickAtName("Save PDF"); ResetMainWindow(); - var fileNameElements = _session.FindElementsByName("File name:"); - var fileTextBox = fileNameElements.Last(); + var fileTextBox = WaitFor(() => _session.FindElementsByName("File name:").Last()); ClickAt(fileTextBox); fileTextBox.SendKeys("test.pdf"); ClickAtName("Save"); @@ -62,17 +62,17 @@ public class ScanAndSaveTests : AppiumTests if (!string.IsNullOrEmpty(deviceName)) ClickAtName(deviceName); // Click Select in the twain device dialog (selecting the first available device by default) ClickAtName("Select"); - WaitUntilGone("Select", 1_000); + WaitFor(() => !HasElementWithName("Select")); // Click OK in the profile settings window ClickAtName("OK"); + WaitFor(() => HasElementWithName("Cancel")); // Wait for scanning to finish - WaitUntilGone("Cancel", 30_000); + WaitFor(() => !HasElementWithName("Cancel"), 30_000); ResetMainWindow(); // Save "test.pdf" in the default location (which will be the test data path as NAPS2 knows we're in a test)^ ClickAtName("Save Images"); ResetMainWindow(); - var fileNameElements = _session.FindElementsByName("File name:"); - var fileTextBox = fileNameElements.Last(); + var fileTextBox = WaitFor(() => _session.FindElementsByName("File name:").Last()); ClickAt(fileTextBox); fileTextBox.SendKeys("test.jpg"); ClickAtName("Save");