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.
Prepare an Environment
- Enable Windows Developer Mode.
- Install WinAppDriver.
- Download the WinAppDriver UI Recorder.
Create a Test
- 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.
DesktopSession
class. The class allows you to use the code that the WinAppDriver UI Recorder will generate.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();
}
}
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();
}
}
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
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
- Run the WinAppDriver UI Recorder as an administrator.
- Set a break point in the
Test1
method. - Debug the
Test1
test. This will run the OutlookInspired Demo App and enable UI testing mode. - Click the Record button in the WinAppDriver UI Recorder window.
- 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.
- Mouse over the First Name text field and wait until the Recorder is ready to capture input. Enter a value.
- Repeat the previous step for Last Name, Title, Mobile Phone, and Email text fields.
- Record the Save & Close button click.
- In the Recorder window, click Pause and copy buttons to copy the generated code to the clipboard.
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 Names, ClassNames, 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();
}