From d965bf6361a4b1ab5493f910ea918882776c95db Mon Sep 17 00:00:00 2001 From: Alexander Rubtsov <32265866+Alexxfromgit@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:13:54 +0300 Subject: [PATCH] Type the path in file dialogs and check where the export went The file name box of the Windows Server file dialog ignores text set through UI Automation, so the export ended up in the default location on GitHub's runners. FileDialog.Choose now types the path (new UiElement.TypeKeys), and the demo app reports where it saved the cart, which the example test checks. Co-Authored-By: Claude Opus 5.5 --- demo/ShopDesk.Core/AppText.cs | 2 ++ demo/ShopDesk.WinForms/MainForm.cs | 2 +- demo/ShopDesk.Wpf/MainWindow.xaml.cs | 2 +- docs/windows-and-locators.md | 1 + examples/Taf.Desktop.Examples/Tests/MenuAndDialogTests.cs | 2 +- examples/Taf.Desktop.Examples/Windows/MainWindow.cs | 8 ++++---- src/Taf.Desktop.Core/Ui/Dialogs.cs | 3 ++- src/Taf.Desktop.Core/Ui/UiElement.cs | 6 ++++++ 8 files changed, 18 insertions(+), 8 deletions(-) diff --git a/demo/ShopDesk.Core/AppText.cs b/demo/ShopDesk.Core/AppText.cs index bce00f7..151b454 100644 --- a/demo/ShopDesk.Core/AppText.cs +++ b/demo/ShopDesk.Core/AppText.cs @@ -26,5 +26,7 @@ public static class AppText public static string Total(decimal amount) => "Total: " + Money.Format(amount); + public static string Exported(string path) => $"Cart exported to {path}"; + public static string OrderPlaced(OrderResult order) => $"Order {order.OrderId} placed. Total: {Money.Format(order.Total)}"; } diff --git a/demo/ShopDesk.WinForms/MainForm.cs b/demo/ShopDesk.WinForms/MainForm.cs index 03a98d3..dc98239 100644 --- a/demo/ShopDesk.WinForms/MainForm.cs +++ b/demo/ShopDesk.WinForms/MainForm.cs @@ -156,7 +156,7 @@ private void Export() if (dialog.ShowDialog(this) == DialogResult.OK) { File.WriteAllText(dialog.FileName, Services.Cart.ToCsv()); - status.Text = "Cart exported"; + status.Text = AppText.Exported(dialog.FileName); } } diff --git a/demo/ShopDesk.Wpf/MainWindow.xaml.cs b/demo/ShopDesk.Wpf/MainWindow.xaml.cs index adaa060..d675f05 100644 --- a/demo/ShopDesk.Wpf/MainWindow.xaml.cs +++ b/demo/ShopDesk.Wpf/MainWindow.xaml.cs @@ -94,7 +94,7 @@ private void OnExport(object sender, RoutedEventArgs e) if (dialog.ShowDialog(this) == true) { File.WriteAllText(dialog.FileName, Services.Cart.ToCsv()); - StatusText.Text = "Cart exported"; + StatusText.Text = AppText.Exported(dialog.FileName); } } diff --git a/docs/windows-and-locators.md b/docs/windows-and-locators.md index 1fe7cee..136991e 100644 --- a/docs/windows-and-locators.md +++ b/docs/windows-and-locators.md @@ -95,6 +95,7 @@ there are no stale references. Every action is a report step. |---|---| | `Click()`, `DoubleClick()`, `RightClick()` | `Ui:ClickMode`: `Auto` (Invoke for WPF/XAML, mouse for WinForms/Win32), `Invoke`, `Mouse` | | `Type(text)`, `Clear()` | through UI Automation when the field accepts it, otherwise typed. Password fields are masked in the report. | +| `TypeKeys(text)` | always typed on the keyboard, for controls that ignore values set through UI Automation (file dialogs) | | `Check()`, `Uncheck()`, `SetChecked(bool)` | toggle pattern; only toggles when needed | | `Select(item)` / `Select()` | an item of a combo box or list (expands and collapses as needed) / this element itself | | `OpenMenu("File", "Export cart...")` | menu bars, including WinForms drop-downs that open as separate popup windows | diff --git a/examples/Taf.Desktop.Examples/Tests/MenuAndDialogTests.cs b/examples/Taf.Desktop.Examples/Tests/MenuAndDialogTests.cs index 57924bd..052adf7 100644 --- a/examples/Taf.Desktop.Examples/Tests/MenuAndDialogTests.cs +++ b/examples/Taf.Desktop.Examples/Tests/MenuAndDialogTests.cs @@ -46,7 +46,7 @@ public void ExportWritesTheCartAsCsv() var main = On(); main.StartExport(); On().Choose(file); - main.WaitStatus("Cart exported"); + Verify.Equal(main.WaitStatus("Cart exported"), $"Cart exported to {file}", "Status bar"); File.ReadAllLines(file).Should().Equal("Product,Quantity,Amount", "Canvas Backpack,2,59.98"); } diff --git a/examples/Taf.Desktop.Examples/Windows/MainWindow.cs b/examples/Taf.Desktop.Examples/Windows/MainWindow.cs index f33578a..aa6e244 100644 --- a/examples/Taf.Desktop.Examples/Windows/MainWindow.cs +++ b/examples/Taf.Desktop.Examples/Windows/MainWindow.cs @@ -65,11 +65,11 @@ public MainWindow StartSearch(string text) return this; } - /// Waits until the status bar shows (the app finished an action). - public MainWindow WaitStatus(string text) + /// Waits until the status bar starts with (the app finished an action). + public string WaitStatus(string prefix) { - status.WaitText(text); - return this; + status.WaitUntil(s => s.Text.StartsWith(prefix, StringComparison.Ordinal), $"starts with '{prefix}'"); + return Status; } public MainWindow WaitUntilLoaded() diff --git a/src/Taf.Desktop.Core/Ui/Dialogs.cs b/src/Taf.Desktop.Core/Ui/Dialogs.cs index 3eb05ae..451ce4c 100644 --- a/src/Taf.Desktop.Core/Ui/Dialogs.cs +++ b/src/Taf.Desktop.Core/Ui/Dialogs.cs @@ -48,7 +48,8 @@ public void Choose(string path) => Step.Run($"Choose '{path}' in {Name} ('{Title { fileName = Element("fileName", Locator.ById("1148", ControlType.Edit)); // open dialog } - fileName.Type(path); + // Typed, not set: the dialog keeps its own file name and may ignore text set through UI Automation. + fileName.TypeKeys(path); fileName.Press(VirtualKeyShort.ENTER); WaitClosed(); }); diff --git a/src/Taf.Desktop.Core/Ui/UiElement.cs b/src/Taf.Desktop.Core/Ui/UiElement.cs index ee7dcaf..5744867 100644 --- a/src/Taf.Desktop.Core/Ui/UiElement.cs +++ b/src/Taf.Desktop.Core/Ui/UiElement.cs @@ -133,6 +133,12 @@ private static bool TrySetValue(IUiNode node, string text) } } + /// + /// Replaces the content by typing on the keyboard, like a user. For controls that ignore values set through UI + /// Automation (the file name box of Windows file dialogs). + /// + public void TypeKeys(string text) => Step.Run($"Type '{text}' into {Name} (keyboard)", () => WaitEnabled().TypeText(text)); + public void Clear() => Step.Run($"Clear {Name}", () => { var node = WaitEnabled();