From 6f5ba8ad0da7d55318501de3fb0e652adc1de58b Mon Sep 17 00:00:00 2001 From: amrali-eg <32075105+amrali-eg@users.noreply.github.com> Date: Thu, 10 Sep 2026 02:13:41 +0300 Subject: [PATCH] Wait for this action's report, and say what was true when it never came EC-28, first lead. WaitForMainReady accepted any of seven final conversion statuses, so it asked "has some run ended" rather than "has the one just started ended". A status outlives the action that wrote it - the window clears it only when the next action starts - so the wait could be satisfied by the previous action's report. Every phase drives one action per window today, which is the only reason that has not bitten. It is EC-26's shape in the one helper that fix did not reach. Each caller now names the headline its own action produces. Measured rather than assumed, by instrumenting the helper and reading a full suite run: declining a review reports "Conversion cancelled", carrying one through reports "Conversion complete", and the refused run in the warning path reports "Conversion did not run". The timeout now also records what could still be established: whether the process is alive, whether the main-window handle reads, whether the review is gone, and whether the status bar can be found and read. EC-28's one failure showed window chrome and nothing else, and left none of those knowable afterwards. Each is gathered separately so one unreadable answer does not cost the others. Control: expecting a headline EC never writes times out against a status reading "Conversion cancelled. No files were modified." - which the previous code accepted - and reports "Process alive: yes; main window handle: 26610754; review present: no; status bar found: yes; status bar readable: yes." This does not close EC-28. The failure was never reproduced, so this removes a known weakness in the failing path and makes the next occurrence legible; it does not show that this was the cause. EC is unchanged. Co-Authored-By: Claude Opus 5 --- .../EncodingChecker.GuiSmoke/EcGuiDriver.cs | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) 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.