How to test a WPF user interface?

.NetWpfTesting

.Net Problem Overview


Using win forms with an MVC/MVP architecture, I would normally use a class to wrap a view to test the UI while using mocks for the model and controller/presenter. The wrapper class would make most everything in the UI an observable property for the test runner through properties and events.

Would this be a viable approach to testing a WPF app? Is there a better way? Are there any gotchas to watch out for?

.Net Solutions


Solution 1 - .Net

As for the testing itself, you're probably best off using the UI Automation framework. Or if you want a more fluent and wpf/winforms/win32/swt-independent way of using the framework, you could download White from Codeplex (provided that you're in a position to use open source code in your environment).

For the gotchas; If you're trying to test your views, you will probably run in to some threading issues. For instance, if you're running NUnit the default testrunner will run in MTA (Multi-Threaded Appartment), while as WPF needs to run as STA (Single-threaded Appartment). Mike Two has a real easy getting-started on unit testing WPF, but without considering the threading issue. Josh Smith has some thoughts on the threading issue in this post, and he also points to this article by Chris Hedgate. Chris uses a modified version of Peter Provost's CrossThreadTestRunner to wrap the MTA/STA issues in a bit more friendly way.

Solution 2 - .Net

2016 Update: Use the free TestStack.White framework to automate WPF UI testing

  • Project White has been abandoned, but its successor TestStack.White is available via a NuGet package.

  • TestStack.White has utility methods for starting WPF apps, finding window/user control elements, clicking buttons/elements, simulating mouse and keyboard events, waiting, etc..

  • An example that will launch a WPF app, click a button, and check for result looks like the following:

     using TestStack.White;
     using TestStack.White.UIItems;
     using TestStack.White.Factory;
    
     [TestMethod]
     public void TestDoSomething()
     {
         //Opens the app
         var app = Application.Launch("MyApp.exe");
    
         //Finds the main window (this and above line should be in [TestInitialize])
         var window = app.GetWindow("My App Window Title", InitializeOption.NoCache);
    
         //Finds the button (see other Get...() methods for options)
         var btnMyButton = window.Get<Button>("btnMyButtonWPFname");
    
         //Simulate clicking
         btnMyButton.Click();
    
         //Gets the result text box 
         //Note: TextBox/Button is in TestStack.White.UIItems namespace
         var txtMyTextBox = window.Get<TextBox>("txtMyTextBox");
    
         //Check for the result
         Assert.IsTrue(txtMyTextBox.Text == "my expected result");
    
         //Close the main window and the app (preferably in [TestCleanup])
         app.Close();
     }
    

Solution 3 - .Net

@Matt David,

Please read documentation and take a look at the code samples for Microsoft CompositeWPF (aka Prism). It's a project created specifically to teach how to deal with MVP/MVC architecture in test-driven manner. Their sample application contains unit tests for presenters\controllers and very cool acceptance tests for UI (they use White framework to simulate user actions)

Solution 4 - .Net

Manually. I'm not a big fan of automated UI testing if that is what you're getting at. I'm not sure about the WPF guidances (need to read thru aku's links).. because they are still solidifying so to speak... WPF has not stabilized from the point of 'what is the right way'. Unless you're using one of these evolving frameworks.. I'd be conservative w.r.t. effort

  • Test (Automated preferably TDDed) the logic/presenters/controllers ruthlessly. I'm not advocating sloppiness or lethargy.
  • Keep the UI skin thin and get some nasty testers to go have a (manual) crack at it with exploratory testing - nothing is as good as a 'tester from Hell' when it comes to UIs. The effort : gain ratio from automating this kind of testing is huge, doesn't catch everything and doesn't make sense... except to pacify the higher ups 'Look Mgr! No hands! self-testing UIs!'

PS: you may want to watch this (Mary Poppendieck's Google Talk on Lean).. especially the part about what to automate in testing

Solution 5 - .Net

Because the Coded UI framework expires after Visual Studio version 2019 (Deprecated Coded UI), Microsoft recommends Appium with WinAppDriver for testing Windows applications (Desktop and UWP). You can use Appium (with WinAppDriver) or WinAppDriver directly to run the tests (WinAppDriver with or without Appium).

WinAppDriver directly

Here is a short description to work with the WinAppDriver directly:

  • download and install WinAppDriver:

    WinAppDriver Release

  • enable Developer Mode in Windows settings

  • start the WinAppDriver:

    C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe

  • create a new Visual Studio 2019 Unit Test Project (.NET Framework)

  • add the NuGet-Package: Appium.WebDriver Microsoft.WinAppDriver.Appium.WebDriver (comment from Microsoft: it is recommenced to use the WinAppDriver NuGet package to take full advantage of the advanced input with the Actions API.)

  • add a new class DesktopSession:

    public class DesktopSession
    {
        protected const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
        private const string NotepadAppId = @"C:\Windows\System32\notepad.exe";
    
        protected static WindowsDriver<WindowsElement> session;
        protected static WindowsElement editBox;
    
        public static void Setup(TestContext context)
        {
            // Launch a new instance of Notepad application
            if (session == null)
            {
                // Create a new session to launch Notepad application
                var appCapabilities = new DesiredCapabilities();
                appCapabilities.SetCapability("app", NotepadAppId);
                appCapabilities.SetCapability("platformName", "Windows");
                appCapabilities.SetCapability("deviceName ", "WindowsPC");
                session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appCapabilities);
                Assert.IsNotNull(session);
                Assert.IsNotNull(session.SessionId);
    
                // Set implicit timeout to 1.5 seconds to make element search to retry every 500 ms for at most three times
                session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1.5);
    
                // Keep track of the edit box to be used throughout the session
                editBox = session.FindElementByClassName("Edit");
                Assert.IsNotNull(editBox);
            }
        }
    
        public static void TearDown()
        {
            // Close the application and delete the session
            if (session != null)
            {
                session.Close();
    
                try
                {
                    // Dismiss Save dialog if it is blocking the exit
                    session.FindElementByName("Nicht speichern").Click();
                }
                catch { }
    
                session.Quit();
                session = null;
            }
        }
    
        [TestInitialize]
        public void TestInitialize()
        {
            // Select all text and delete to clear the edit box
            editBox.SendKeys(Keys.Control + "a" + Keys.Control);
            editBox.SendKeys(Keys.Delete);
            Assert.AreEqual(string.Empty, editBox.Text);
        }
    } 
  • Change the code from the UnitTest1 class
        [TestClass]
        public class UnitTest1 : DesktopSession
        {
            [TestMethod]
            public void EditorEnterText()
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                editBox.SendKeys("abcdeABCDE 12345");
                Assert.AreEqual(@"abcdeABCDE 12345", editBox.Text);
            }
    
            [ClassInitialize]
            public static void ClassInitialize(TestContext context)
            {
                Setup(context);
            }
    
            [ClassCleanup]
            public static void ClassCleanup()
            {
                TearDown();
            }
        }
  • run your test

(the sample code is mainly copied from WinAppDriver .NotepadTest).

Appium with WinAppDriver

If you want to run your tests using Appium, then you must have installed the correct version of the WinAppDriver on your machine. The installer of Appium should also install the WinAppDriver with the correct version on your machine (please install Appium for all users). In my case, unfortunately, this did not work. So I take a look in the file:

C:\Program Files\Appium\resources\app\node_modules\appium\node_modules\appium-windows-driver\lib\installer.js

Here you will find the correct version and the download path:

const WAD_VER = "1.1";
const WAD_DL = `https://github.com/Microsoft/WinAppDriver/releases/download/v${WAD_VER}/WindowsApplicationDriver.msi`;

If you have installed the correct WinAppDriver you can start Appium.

Important: you have to change the ApplicationDriverUrl

protected const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723/wd/hub";

Tools:

WindowsAppDriver and UI REcorder releases or Donwload the WinAppDriver repository and build the WinAppDriverUIRecorder.sln in the subdirectory tools\UIRecorder

Introducing WinAppDriver UI Recorder

  • inspect.exe: Windows SDK is required (look in C:\Program Files (x86)\Windows Kits\10\bin)

Other links: WinAppDriver FAQ Appium

Solution 6 - .Net

The question is still relevant, but many of the answers got obsolete. @deadpikle suggested a very good solution in the comment, I tried it and I want to make it as an answer in order to more people see it.

So, here is a library https://github.com/FlaUI/FlaUI. Here is a quick start guide for WPF app:

  1. Install FlaUI.UIA3 from nuget

  2. Write this to test if app runs correctly (but insert your string literals):

    using FlaUI.Core;
    using FlaUI.Core.AutomationElements;
    using FlaUI.UIA3;
    using FluentAssertions;
    using System;
    using Xunit;
    
    namespace Functional
    {
        public sealed class General : IDisposable
        {
            private readonly Application _app = Application.Launch(@"..\App.exe");
    
            [Fact]
            public void AppStarts()
            {
                using var automation = new UIA3Automation();
                Window window = _app.GetMainWindow(automation, TimeSpan.FromSeconds(3));
    
                window.Should().NotBeNull("null means the window failed to load");
    
                window.Title.Should().Be("App title",
                    "otherwise, it could be message box with error in case of the wrong configuration");
            }
    
            public void Dispose()
            {
                _app.Close();
                _app.Dispose();
            }
        }
    }
    

This code also works well in the GitHub Actions pipeline.

Solution 7 - .Net

Definitely look at TestAutomationFX.com. One can invest (OK, I did) a lot of time trying to capture / record events with White. (At the start of my quest I ignored the post or two in other places referring to it).

I of course second the other points about the best type of testing not being UI testing.

But if someone is going to do something automatable in the UI to get around shortcomings in other types of testing coverage, TAFX seems the quickest route there.

Solution 8 - .Net

Try Ranorex V2.0 for WPF automation. With RanoreXPath and Ranorex repository test automation code could be completely seperated from identification information. Ranorex also provides a capture/replay editor based on RanoreXPath objects.

Solution 9 - .Net

I would recommend TestAutomationFX as well for simple automation of ui testing. TestAutomationFX lets you work with netAdvantage tools for wpf aswell , which doesnt work with white and QTP. TestAutomationFX has a easy to use interface , it integrates with visual studio and has a good recorder for recording user events.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatt DavidView Question on Stackoverflow
Solution 1 - .NetKjetil KlaussenView Answer on Stackoverflow
Solution 2 - .NetJustasView Answer on Stackoverflow
Solution 3 - .NetakuView Answer on Stackoverflow
Solution 4 - .NetGishuView Answer on Stackoverflow
Solution 5 - .NetHHennView Answer on Stackoverflow
Solution 6 - .NetLevView Answer on Stackoverflow
Solution 7 - .NetRuben BartelinkView Answer on Stackoverflow
Solution 8 - .NetMichaelView Answer on Stackoverflow
Solution 9 - .NetGokulView Answer on Stackoverflow