diff --git a/sources/EncodingChecker.GuiSmoke/EcGuiDriver.cs b/sources/EncodingChecker.GuiSmoke/EcGuiDriver.cs index d550a52..85a6b23 100644 --- a/sources/EncodingChecker.GuiSmoke/EcGuiDriver.cs +++ b/sources/EncodingChecker.GuiSmoke/EcGuiDriver.cs @@ -160,7 +160,7 @@ internal void CancelReview(AutomationElement review) int handle = review.Current.NativeWindowHandle; Invoke(review, "btnCancelConversionReview"); WaitUntil(() => !WindowExists(handle), "The conversion review did not close."); - WaitForMainReady(); + WaitForMainReady("Conversion cancelled"); } internal void Proceed(AutomationElement review) @@ -168,7 +168,7 @@ internal void Proceed(AutomationElement review) int handle = review.Current.NativeWindowHandle; Invoke(review, "btnProceedConversion"); WaitUntil(() => !WindowExists(handle), "The conversion review did not close."); - WaitForMainReady(); + WaitForMainReady("Conversion complete"); } internal void ProceedExpectingWarning(AutomationElement review) @@ -188,7 +188,7 @@ internal void ProceedExpectingWarning(AutomationElement review) Invoke(ok); WaitUntil(() => !WindowExists(warningHandle), "The warning did not close."); - WaitForMainReady(); + WaitForMainReady("Conversion did not run"); } internal bool ReviewContainsControl(AutomationElement review, string automationId) => @@ -387,10 +387,72 @@ internal void WaitForStatus(string fragment) lastError); } - private void WaitForMainReady() => - WaitForOperationOutcome( - () => FindReviewWindow() is null && ConversionHasFinished(), - "EncodingChecker did not return to its idle state."); + /// + /// Waits for the window to finish the action just performed and go idle. + /// + /// + /// The caller names the headline its own action produces, rather than this accepting + /// any final conversion status. A status outlives the action that wrote it - the + /// window clears it only when the next action starts - so accepting any of them lets + /// a wait be satisfied by the previous action's report and return before the current + /// one has finished. Every phase drives one action per window today, which is the + /// only reason that has not bitten; it is the shape EC-26 was about, in the one + /// helper that fix did not reach. + /// + private void WaitForMainReady(string expectedHeadline) + { + if (WaitFor( + () => FindReviewWindow() is null + && StatusLine() is string status + && status.Contains(expectedHeadline, StringComparison.Ordinal) + ? new object() + : null, + Timeout, + out Exception? lastError) is not null) + { + return; + } + + throw Expired( + $"EncodingChecker did not go idle: no '{expectedHeadline}' was reported." + + Safely(() => " The status showed: " + StatusText(), + " The status could not be read") + + DescribeIdleState(), + lastError); + } + + /// + /// What could still be established about the window when a wait for idle gave up. + /// + /// + /// A timeout otherwise says only that something expected did not arrive, which is the + /// position EC-28 left: a phase A failure whose diagnostic showed window chrome and + /// nothing else, with no way to tell afterwards whether the process had died, the + /// review was still open, the held main-window element had gone stale while the + /// window was healthy, or the status bar simply could not be found. These four + /// separate those, and each is gathered on its own so one unreadable answer does not + /// cost the others. + /// + private string DescribeIdleState() => + " Process alive: " + Ask(() => _process.HasExited ? "no" : "yes") + + "; main window handle: " + Ask(() => MainWindow.Current.NativeWindowHandle.ToString()) + + "; review present: " + Ask(() => FindReviewWindow() is not null ? "yes" : "no") + + "; status bar found: " + + Ask(() => FindById(MainWindow, "statusBar") is not null ? "yes" : "no") + + "; status bar readable: " + Ask(() => StatusLine() is null ? "no" : "yes") + "."; + + /// One fact for a diagnostic, or why it could not be had. + private static string Ask(Func fact) + { + try + { + return fact(); + } + catch (Exception ex) + { + return "unknown (" + ex.GetType().Name + ")"; + } + } /// /// Waits for something the operation itself produced, rather than for a button.