{"id":21129476,"url":"https://github.com/guorg/gu.wpf.uiautomation","last_synced_at":"2025-04-05T23:12:26.972Z","repository":{"id":24289775,"uuid":"101097388","full_name":"GuOrg/Gu.Wpf.UiAutomation","owner":"GuOrg","description":null,"archived":false,"fork":false,"pushed_at":"2024-03-26T10:08:22.000Z","size":2321,"stargazers_count":97,"open_issues_count":18,"forks_count":17,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-29T22:09:23.363Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GuOrg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-22T18:57:55.000Z","updated_at":"2024-12-28T02:09:33.000Z","dependencies_parsed_at":"2023-11-06T13:44:03.673Z","dependency_job_id":"acab2eb2-bbce-4fb5-8dcb-100a03298e56","html_url":"https://github.com/GuOrg/Gu.Wpf.UiAutomation","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuOrg%2FGu.Wpf.UiAutomation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuOrg%2FGu.Wpf.UiAutomation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuOrg%2FGu.Wpf.UiAutomation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuOrg%2FGu.Wpf.UiAutomation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GuOrg","download_url":"https://codeload.github.com/GuOrg/Gu.Wpf.UiAutomation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247411239,"owners_count":20934653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T05:23:04.489Z","updated_at":"2025-04-05T23:12:26.948Z","avatar_url":"https://github.com/GuOrg.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gu.Wpf.UiAutomation\n\n[![Join the chat at https://gitter.im/JohanLarsson/Gu.Wpf.UiAutomation](https://badges.gitter.im/JohanLarsson/Gu.Wpf.UiAutomation.svg)](https://gitter.im/JohanLarsson/Gu.Wpf.UiAutomation?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![NuGet](https://img.shields.io/nuget/v/Gu.Wpf.UiAutomation.svg)](https://www.nuget.org/packages/Gu.Wpf.UiAutomation/)\n[![Build Status](https://github.com/GuOrg/Gu.Wpf.UiAutomation/actions/workflows/ci.yml/badge.svg)](https://github.com/GuOrg/Gu.Wpf.UiAutomation/actions)\n\n## Introduction\nGu.Wpf.UiAutomation is a .NET library which helps with automated UI testing of WPF applications.\nThe library wraps `UIAutomationClient` and tries to provide an API that is nice for WPF.\n\nThe code inspired by [FlaUI](https://github.com/Roemer/FlaUI) and [White](https://github.com/TestStack/White).\nTested on Windows 7, Windows 10, and the default AppVeyor image.\n\n### Typical test class\n\nUsing the same window and restoring state can be a good strategy as the tests run faster and generate more input to the application finding more bugs.\n\n```cs\npublic class FooTests\n{\n    // Current sln directory is searched rtecursively for this exe.\n    private const string ExeFileName = \"WpfApplication.exe\";\n    // This is optional\n    private const string WindowName = \"MainWindow\";\n\n    [SetUp]\n    public void SetUp()\n    {\n        // restore state for the next test if the application is reused.\n        using var app = Application.AttachOrLaunch(ExeFileName, WindowName);\n        app.MainWindow.FindButton(\"Reset\").Click();\n    }\n\n    [OneTimeTearDown]\n    public void OneTimeTearDown()\n    {\n        // Close the shared window after the last test.\n        Application.KillLaunched(ExeFileName);\n    }\n\n    [Test]\n    public void Test1()\n    {\n        // AttachOrLaunch uses the already open app or creates a new. Dispose does not close the app.\n        using var app = Application.AttachOrLaunch(ExeFileName, WindowName);\n        var window = app.MainWindow;\n        ...\n    }\n\n    [Test]\n    public void Test2()\n    {\n        using var app = Application.AttachOrLaunch(ExeFileName, WindowName);\n        var window = app.MainWindow;\n        ...\n    }\n\n    [Test]\n    public void Test3()\n    {\n        // If we for some reason needs a separate instance of the application in a test we use Launch()\n        using var app = Application.Launch(ExeFileName, WindowName);\n        var window = app.MainWindow;\n        ...\n    }\n}\n```\n\nUsage of the window parameter in App.Xaml.cs\n```cs\nprotected override void OnStartup(StartupEventArgs e)\n{\n    if (e is { Args: { Length: 1 } args })\n    {\n        var window = args[0];\n        this.StartupUri = new Uri($\"Windows/{window}.xaml\", UriKind.Relative);\n    }\n\n    base.OnStartup(e);\n}\n```\n\n## Table of contents\n\n  - [Introduction](#introduction)\n  - [Supported types](#supported-types)\n  - [Sample test](#sample-test)\n  - [Application](#application)\n    - [Launch](#launch)\n    - [Attach](#attach)\n    - [AttachOrLaunch](#attachorlaunch)\n      - [Arguments](#arguments)\n  - [Input](#input)\n    - [Mouse](#mouse)\n    - [Keyboard](#keyboard)\n    - [Touch](#touch)\n  - [ImageAssert](#imageassert)\n    - [OnFail](#onfail)\n  - [Azure devops pipelines](#azure-devops-pipelines)\n  - [AppVeyor](#appveyor)\n    - [Contribution](#contribution)\n\n\n## Supported types\n\n\n- Button\n- Calendar\n- CalendarDayButton\n- CheckBox\n- ColumnHeader\n- ComboBox\n- ComboBoxItem\n- ContentControl\n- ContextMenu\n- Control\n- DataGrid\n- DataGridCell\n- DataGridColumnHeader\n- DataGridColumnHeadersPresenter\n- DataGridDetailsPresenter\n- DataGridRow\n- DataGridRowHeader\n- DatePicker\n- Dialog\n- Expander\n- Frame\n- GridHeader\n- GridSplitter\n- GridViewCell\n- GridViewColumnHeader\n- GridViewHeaderRowPresenter\n- GridViewRowHeader\n- GroupBox\n- HeaderedContentControl\n- HorizontalScrollBar\n- Label\n- ListBox\n- ListBoxItem\n- ListView\n- ListViewItem\n- Menu\n- MenuBar\n- MenuItem\n- MessageBox\n- MultiSelector`1\n- OpenFileDialog\n- PasswordBox\n- ExpandCollapseControl\n- InvokeControl\n- SelectionItemControl\n- Popup\n- ProgressBar\n- RadioButton\n- RepeatButton\n- RichTextBox\n- RowHeader\n- SaveFileDialog\n- ScrollBar\n- ScrollViewer\n- Selector\n- Selector`1\n- Separator\n- Slider\n- StatusBar\n- TabControl\n- TabItem\n- TextBlock\n- TextBox\n- TextBoxBase\n- Thumb\n- TitleBar\n- ToggleButton\n- ToolBar\n- ToolTip\n- TreeView\n- TreeViewItem\n- UiElement\n- UserControl\n- VerticalScrollBar\n- Window\n\n\n## Sample test\nThe entry point is usually an application or the desktop so you get an automation element (like a the main window of the application).\nOn this, you can then search sub-elements and interact with them.\nThe `Application` class is a helper for launching, attaching and closing applications.\nSince the application is not related to any UIA library, you need to create the automation you want and use it to get your first element, which then is your entry point.\n\n```cs\n[Test]\npublic void CheckBoxIsChecked()\n{\n    using var app = Application.Launch(\"WpfApplication.exe\");\n    var window = app.MainWindow;\n    var checkBox = window.FindCheckBox(\"Test Checkbox\");\n    checkBox.IsChecked = true;\n    Assert.AreEqual(true, checkBox.IsChecked);\n}\n```\n\n## Application\nThe application class iss the way to start an application to test. There are a couple of factory methods.\n\n### Launch\nStarts a new instance of the application and closes it on dispose. There is a flag to leave the app open but the default is close on dispose.\nLaunch is useful for tests that mutate state where resetting can be slow and painful.\n\n```cs\n[Test]\npublic void IsChecked()\n{\n    using var app = Application.Launch(\"WpfApplication.exe\");\n    var window = app.MainWindow;\n    var checkBox = window.FindCheckBox(\"Test Checkbox\");\n    checkBox.IsChecked = true;\n    Assert.AreEqual(true, checkBox.IsChecked);\n}\n```\n\n### Attach\nAttaches to a running process and leaves it open when disposing disposing by default.\n\n### AttachOrLaunch\nAttaches to a running process or launches a new if not found and leaves it open when disposing by default.\n\n```cs\n[SetUp]\npublic void SetUp()\n{\n    if (Application.TryAttach(\"WpfApplication.exe\", \"ButtonWindow\", out var app))\n    {\n        using (app)\n        {\n            app.MainWindow.FindButton(\"Reset\").Invoke();\n        }\n    }\n}\n\n[OneTimeTearDown]\npublic void OneTimeTearDown()\n{\n    Application.KillLaunched(\"WpfApplication.exe\");\n}\n\n[TestCase(\"AutomationId\", \"AutomationProperties.AutomationId\")]\n[TestCase(\"XName\", \"x:Name\")]\n[TestCase(\"Content\", \"Content\")]\npublic void Content(string key, string expected)\n{\n    using var app = Application.AttachOrLaunch(\"WpfApplication.exe\", \"ButtonWindow\");\n    var window = app.MainWindow;\n    var button = window.FindButton(key);\n    Assert.AreEqual(expected, ((TextBlock)button.Content).Text);\n}\n```\n\n#### Arguments\nLaunch and AttachOrLaunch has an overload that takes an argument string. It can be used like this:\n\n```cs\n[OneTimeTearDown]\npublic void OneTimeTearDown()\n{\n    Application.KillLaunched(\"WpfApplication.exe\");\n}\n\n[Test]\npublic void SelectByIndex()\n{\n    using var app = Application.AttachOrLaunch(\"WpfApplication.exe\", \"ListBoxWindow\");\n    var window = app.MainWindow;\n    var listBox = window.FindListBox(\"BoundListBox\");\n    Assert.AreEqual(2, listBox.Items.Count);\n    Assert.IsInstanceOf\u003cListBoxItem\u003e(listBox.Items[0]);\n    Assert.IsInstanceOf\u003cListBoxItem\u003e(listBox.Items[1]);\n    Assert.IsNull(listBox.SelectedItem);\n\n    var item = listBox.Select(0);\n    Assert.AreEqual(\"Johan\", item.FindTextBlock().Text);\n    Assert.AreEqual(\"Johan\", listBox.SelectedItem.FindTextBlock().Text);\n\n    item = listBox.Select(1);\n    Assert.AreEqual(\"Erik\", item.FindTextBlock().Text);\n    Assert.AreEqual(\"Erik\", listBox.SelectedItem.FindTextBlock().Text);\n}\n```\n\n```cs\npublic partial class App\n{\n    protected override void OnStartup(StartupEventArgs e)\n    {\n        if (e is { Args: { Length: 1 } args })\n        {\n            var window = args[0];\n            this.StartupUri = new Uri($\"Windows/{window}.xaml\", UriKind.Relative);\n        }\n\n        base.OnStartup(e);\n    }\n}\n```\n\n## Input\n\n### Mouse\n\nFor mouse input like click, drag, scroll etc.\n\n```cs\n[Test]\npublic void DragFromCenterToTopLeft()\n{\n    using var app = Application.Launch(\"WpfApplication.exe\");\n    var window = app.MainWindow;\n    Mouse.Drag(window.Bound.Center(), window.Bound.TopLeft);\n    Assert.AreEqual(...);\n}\n```\n\n### Keyboard\n\nFor typing or holding modifier keys.\n\n```cs\n[Test]\npublic void ShiftDragFromCenterToTopLeft()\n{\n    using var app = Application.Launch(\"WpfApplication.exe\");\n    var window = app.MainWindow;\n    using (Keyboard.Hold(Key.SHIFT))\n    {\n        Mouse.Drag(window.Bound.Center(), window.Bound.TopLeft);\n        Assert.AreEqual(...);\n    }\n}\n```\n\n### Touch\n\nFor mouse input like click, drag, scroll etc.\n\n```cs\n[Test]\npublic void DragFromCenterToTopLeft()\n{\n    using var app = Application.Launch(\"WpfApplication.exe\");\n    var window = app.MainWindow;\n    Touch.Drag(window.Bound.Center(), window.Bound.TopLeft);\n    Assert.AreEqual(...);\n}\n```\n\n## ImageAssert\nFor asserting using an expected image of how the control will render.\n\n```cs\n[Test]\npublic void DefaultAdornerWhenNotFocused()\n{\n    using var app = Application.Launch(\"Gu.Wpf.Adorners.Demo.exe\", \"WatermarkWindow\");\n    var window = app.MainWindow;\n    var textBox = window.FindTextBox(\"WithDefaultAdorner\");\n    ImageAssert.AreEqual(\"Images\\\\WithDefaultAdorner_not_focused.png\", textBox);\n}\n```\n\n#### OnFail\n\n```cs\n[Test]\npublic void DefaultAdornerWhenNotFocused()\n{\n    using var app = Application.Launch(\"Gu.Wpf.Adorners.Demo.exe\", \"WatermarkWindow\");\n    var window = app.MainWindow;\n    var textBox = window.FindTextBox(\"WithDefaultAdorner\");\n    ImageAssert.AreEqual(\"Images\\\\WithDefaultAdorner_not_focused.png\", textBox, OnFail);\n}\n\n\nprivate static void OnFail(Bitmap bitmap, Bitmap actual, string resource)\n{\n    var fullFileName = Path.Combine(Path.GetTempPath(), resource);\n    _ = Directory.CreateDirectory(Path.GetDirectoryName(fullFileName));\n    actual.Save(fullFileName);\n    TestContext.AddTestAttachment(fullFileName);\n}\n```\n\nAnd in `appveyor.yml`\n```\non_failure:\n  - ps: Get-ChildItem $env:temp\\*.png | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }\n```\n\n#### Normalize styles\nFor image asserts to work on build servers forcing a theme may be needed:\n\n```xml\n\u003cWindow.Resources\u003e\n    \u003cResourceDictionary\u003e\n        \u003cResourceDictionary.MergedDictionaries\u003e\n            \u003cResourceDictionary Source=\"pack://application:,,,/PresentationFramework.Classic;V4.0.0.0;31bf3856ad364e35;component/themes/Classic.xaml\" /\u003e\n        \u003c/ResourceDictionary.MergedDictionaries\u003e\n    \u003c/ResourceDictionary\u003e\n\u003c/Window.Resources\u003e\n```\n\n\n## Azure devops pipelines\n\n```cs\n    public static class TestImage\n    {\n        internal static readonly string Current = GetCurrent();\n\n        // [Test]\n        public static void Rename()\n        {\n            var folder = @\"C:\\Git\\_GuOrg\\Gu.Wpf.Gauges\\Gu.Wpf.Gauges.Tests\";\n            var oldName = \"Red_border_default_visibility_width_100.png\";\n            var newName = \"Red_border_default_visibility_width_100.png\";\n\n            foreach (var file in Directory.EnumerateFiles(folder, oldName, SearchOption.AllDirectories))\n            {\n                File.Move(file, file.Replace(oldName, newName));\n            }\n\n            foreach (var file in Directory.EnumerateFiles(folder, \"*.cs\", SearchOption.AllDirectories))\n            {\n                File.WriteAllText(file, File.ReadAllText(file).Replace(oldName, newName));\n            }\n        }\n\n#pragma warning disable IDE0060 // Remove unused parameter\n        internal static void OnFail(Bitmap? expected, Bitmap actual, string resource)\n#pragma warning restore IDE0060 // Remove unused parameter\n        {\n            var fullFileName = Path.Combine(Path.GetTempPath(), resource);\n            //// ReSharper disable once AssignNullToNotNullAttribute\n            _ = Directory.CreateDirectory(Path.GetDirectoryName(fullFileName));\n            if (File.Exists(fullFileName))\n            {\n                File.Delete(fullFileName);\n            }\n\n            actual.Save(fullFileName);\n            TestContext.AddTestAttachment(fullFileName);\n        }\n\n        private static string GetCurrent()\n        {\n            if (WindowsVersion.IsWindows7())\n            {\n                return \"Win7\";\n            }\n\n            if (WindowsVersion.IsWindows10())\n            {\n                return \"Win10\";\n            }\n\n            if (WindowsVersion.CurrentContains(\"Windows Server 2019\"))\n            {\n                return \"WinServer2019\";\n            }\n\n            return WindowsVersion.CurrentVersionProductName;\n        }\n    }\n```\n\n```cs\nImageAssert.AreEqual($\"Images\\\\{TestImage.Current}\\\\{name}\", element, TestImage.OnFail);\n```\n\n## AppVeyor\nTroubleshooting failing UI-tests on AppVeyor is tricky. Here is a snippet that can be used for getting a screenshot of what things look like.\n\n```cs\n[Test]\npublic void SomeTest()\n{\n    try\n    {\n        using var app = Application.AttachOrLaunch(\"SomeApp.exe\");\n        ...\n    }\n    catch (TimeoutException)\n    {\n        Capture.ScreenToFile(Path.Combine(Path.GetTempPath(), \"SomeTest.png\"));\n        throw;\n    }\n}\n```\n\nAnd in `appveyor.yml`\n```\non_failure:\n  - ps: Get-ChildItem $env:temp\\*.png | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }\n```\n\n### Contribution\nFeel free to fork Gu.Wpf.UiAutomation and send pull requests of your modifications.\u003cbr /\u003e\nYou can also create issues if you find problems or have ideas on how to further improve Gu.Wpf.UiAutomation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguorg%2Fgu.wpf.uiautomation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguorg%2Fgu.wpf.uiautomation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguorg%2Fgu.wpf.uiautomation/lists"}