WPF - UI Automation & Appium Testing Support (v20.2)

WPF Team Blog
05 April 2021

In our last major update (v20.2) we enhanced UI Test Automation support for our WPF product line. UI Automation now includes a more comprehensive range of automated testing capabilities:

  • DevExpress WPF controls form a theme-independent AutomationPeer hierarchy.
  • You can search for AutomationPeer properties in the automation tree. Both generated and assigned XAML/code AutomationPeer properties are available for search.
  • Our AutomationPeers include a variety of automation patterns, such as Invoke, ExpandCollapse, Selection, Scroll, etc.

You can create automated tests with the UIAutomationClient library API or use any UI testing library based on the UI Automation technology.

DevExpress WPF Controls include a UI Testing mode option. When used, the following changes are made to an application:

  • Animations are disabled.
  • Context menus are only activated on a mouse click and are not opened when the mouse pointer hovers a menu item.
  • The UI Automation tree is modified to produce more stable and reliable tests.
For more information, please refer to the following topic: ClearAutomationEventsHelper.UITestingEnabled.
Note: We tested our controls with Appium WinAppDriver API. In this blog post, we'll show you how to test our OutlookInspired Demo App.

Prepare an Environment

  1. Enable Windows Developer Mode.
  2. Install WinAppDriver.
  3. Download the WinAppDriver UI Recorder.

Create a Test

Follow the steps below to create a new test project:
  1. Open the Windows command prompt, create a project folder or navigate to an existing folder, and use the following commands:
    • dotnet new nunit --framework netcoreapp3.1 - Creates an empty nunit test project.
    • dotnet add package Appium.WebDriver - References the Appium.WebDriver package in your project.
  2. Open the nunit test project in the Visual Studio.
  3. Create a DesktopSession class. The class allows you to use the code that the WinAppDriver UI Recorder will generate.
  4. You can see our implementation below:
    public class DesktopSession {
        const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723/";
      	WindowsDriver < WindowsElement > desktopSession;
      	public DesktopSession(WindowsDriver < WindowsElement > source) {
        	desktopSession = source;
      	}
      	public WindowsDriver < WindowsElement > DesktopSessionElement {
        	get {
          	return desktopSession;
        	}
      	}
      	public WindowsElement FindElementByAbsoluteXPath(string xPath, int nTryCount = 10) {
    	    WindowsElement uiTarget = null;
        	var index = xPath.IndexOf(value: '/', startIndex: 1);
        	xPath = xPath.Substring(startIndex: index);
        	while (nTryCount-->0) {
          		try {
            		uiTarget = desktopSession.FindElementByXPath(xpath: $ "/{xPath}");
          		}
          		catch {
            		Console.WriteLine($@"Find failed: ""{xPath}""");
          		}
          		if (uiTarget != null) break;
          		Thread.Sleep(millisecondsTimeout: 100);
        	}
        	return uiTarget;
      	}
      	public IOptions Manage() {
        	return this.desktopSession.Manage();
      	}
      	public void CloseApp() {
        	this.desktopSession.CloseApp();
      	}
    }
  5. Copy and paste the following test fixture to the UnitTest1.cs file:
  6. public class Tests {
    	Process pWad;
      	const string PathToTheDemo = @"C:\Users\Public\Documents\DevExpress Demos 20.2\Components\WPF\DevExpress.OutlookInspiredApp.Wpf\bin\DevExpress.OutlookInspiredApp.Wpf.exe";
      	protected DesktopSession desktopSession {
        	get;
        	private set;
      	} 
      
      	[OneTimeSetUp]
      	public void FixtureSetup() {
        	StartWAD();
        	var options = new AppiumOptions();
        	options.AddAdditionalCapability(capabilityName: "app", capabilityValue: PathToTheDemo);
        	options.AddAdditionalCapability(capabilityName: "deviceName", capabilityValue: "WindowsPC");
        	options.AddAdditionalCapability(capabilityName: "platformName", capabilityValue: "Windows");
        	var driver = new WindowsDriver < WindowsElement > (new Uri("http://127.0.0.1:4723"), options);
        	desktopSession = new DesktopSession(driver);
        	WaitSplashScreen(driver);
      	}
      	static void WaitSplashScreen(WindowsDriver < WindowsElement > driver) {
    	    var cwh = driver.CurrentWindowHandle;
    	    while (driver.WindowHandles.Contains(cwh))
    	    Thread.Sleep(1000);
    	    driver.SwitchTo().Window(driver.WindowHandles[0]);
      	}
      	private void StartWAD() {
    	    var psi = new ProcessStartInfo(@"C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe");
    	    psi.EnvironmentVariables.Add("DX.UITESTINGENABLED", "1");
    	    pWad = Process.Start(psi);
      	} 
    	  
      	[OneTimeTearDown]
      	public void FixtureTearDown() {
    	    desktopSession.CloseApp();
    	    pWad.Kill();
      	} 
    	  
      	[SetUp]
      	public void Setup() {} 
      	
      	[Test]
      	public void Test1() {
        	Assert.Pass();
      	}
    }
The FixtureSetup method executes the following actions:
  • Invokes the StartWAD method. The method launches the WinAppDriver.exe and enables UI testing mode.
  • Creates a new Appium testing session.
  • Invokes the WaitSplashScreen method and suspends the test during app load operations.

UI Testing Mode

Switch the DevExpress WPF controls to UI Testing mode. To do this, set the DX.UITESTINGENABLED environment variable to 1 or the ClearAutomationEventsHelper.UITestingEnabled property to true for the application under test (on application startup). This mode produces the following changes:
  • Animations are disabled.
  • Context menus are only activated on a mouse click and do not open when the mouse pointer is above a menu.
  • The UI Automation tree is modified to produce more stable and reliable UI tests.

Record a Test

Follow the steps below to record a test:
  1. Run the WinAppDriver UI Recorder as an administrator.
  2. Set a break point in the Test1 method.
  3. Debug the Test1 test. This will run the OutlookInspired Demo App and enable UI testing mode.
  4. Click the Record button in the WinAppDriver UI Recorder window.
    1. Mouse over the New Employee button and wait until the Recorder displays the blue border around the button. This means that the Recorder is ready to capture input. Click the button.
      UI Automation - Record a Test
    2. Mouse over the First Name text field and wait until the Recorder is ready to capture input. Enter a value.
    3. Repeat the previous step for Last Name, Title, Mobile Phone, and Email text fields.
    4. Record the Save & Close button click.
  5. In the Recorder window, click Pause and copy buttons to copy the generated code to the clipboard.
Refer to the Record Tests commit on our GitHub repository to obtain the necessary code for this tutorial.

Disadvantages

The approach outlined above has a few disadvantages:

  • These tests use the FindElementByXPath method to find elements. This approach is slow because it parses the entire visual tree. On our test machine, test took 1 min and 32 seconds
  • These tests are difficult to maintain because they use absolute XPaths to locate elements. Changes to an app's layout may break tests.
  • These tests are difficult to read. Refer to the commit on our GitHub repository to compare the recorded test and Appium API-based test readability.


Rewrite a Test

We can rewrite the test to address the issues mentioned above (and speed up your test). You can analyze the recorded xpathes or use the Inspect tool to obtain element properties, such as NamesClassNames, and AccessibilityIds

Use the WinAppDriver's FindElementByName, FindElementByClassName, and FindElementByAccessibilityId methods to find application elements. These methods are faster than the FindElementByAbsoluteXPath method. Tests based on these methods do not fail when you modify the app's layout.



[Test][Order(0)]
public void CreateEmployee() {
	var desktopElement = desktopSession.DesktopSessionElement;
	var bNewEmployee = desktopElement.FindElementByName("New Employee");
  	bNewEmployee.Click();
  	WindowsElement newEmployeeWindow = null;
  	while (newEmployeeWindow == null)
  	newEmployeeWindow = desktopElement.FindElementByName("Employee (New)");
  	newEmployeeWindow.FindElementByName("First Name").FindElementByClassName("TextEdit").SendKeys("John");
  	newEmployeeWindow.FindElementByName("Last Name").FindElementByClassName("TextEdit").SendKeys("Doe");
  	newEmployeeWindow.FindElementByName("Title").FindElementByClassName("TextEdit").SendKeys("CTO");
  	newEmployeeWindow.FindElementByName("Mobile Phone").FindElementByClassName("ButtonEdit").SendKeys("1111111111");
  	newEmployeeWindow.FindElementByName("Email").FindElementByClassName("ButtonEdit").SendKeys("john.doe@dx-email.com");
  	newEmployeeWindow.FindElementByName("Save & Close").Click();
}
This rewritten test only takes 25 seconds - instead of 1 min and 32 seconds.


To download the complete sample project, please point your browser to the DevExpress Examples repository: Test the OutlookInspiredApp with WinAppDriver.
Should you have any questions or issues on test automation, please post a support ticket on the DevExpress Support Center. Thank you.

Showcase Your Apps on DevExpress.com

Highlight your business app and share your development experiences with the DevExpress community. To include your app in our upcoming App Showcase, please forward an application screenshot to clientservices@devexpress.com and tell us which DevExpress products you currently use within your organization.
No Comments

Please login or register to post comments.