WinAppDriver做PC客户端自动化
1、WinAppDriver介绍
我们平时网页自动化用到的技术一般是使用selenium,selenium在网页端功能十分强大,基本你能想到的操作都能做到,但是对于传统的pc客户端而言,ui自动化的技术就比较稀少了,本文主要就是介绍一个由微软提供的ui自动化技术-----WinAppDriver。
2、如何搭建并使用WinAppDriver
环境搭建可以参考这篇博文https://blog.csdn.net/qq_24373725/article/details/104572249,在搭建好环境后,就是看如何通过代码调用了。从官方github的示例代码我们可以看到,我们可以通过C#、Java、 JavaScript、Python、Ruby调用。网上Python的调用资料会比较多,而我本身是比较习惯用Java的,所以这里的代码都会用Java进行实现
a、官方示例代码讲解
打开官方的Java示例代码 CalculatorTest.java
,这是一个通过计算器软件自动化单元测试的一个示例程序,我们从 setup 这个函数开始看
首先是 DesiredCapabilities 这个类,这个类可以用于添加参数配置,比如 capabilities.setCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"); 就相当于给app这个参数赋值计算器的打开参数,当然这里也可以直接填应用程序exe的路径,或者填 root 表明获取的是已打开的最顶层的应用。
在参数设置完成后,我们就开始和 WinAppDriver 进行通信。WinAppDriver 采用的CS模式,默认情况下会在本地 4723 端口启动一个服务,给代码连接访问。 通过 new WindowsDriver(new URL("http://127.0.0.1:4723"), capabilities) 我们就可以把我们刚刚配置的参数发送给 WinAppDriver,并完成通信获得 WindowsDriver 的对象。通过这个对象我们就可以对所需要的应用程序进行操作了。
CalculatorSession.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS); 这段代码会让我们在操作某个控件后进行一段延时。相信用过 selenium 的人会知道,如果频繁快速的使用 selenium 操作元素会出现代码报错的问题,这也是 selenium 隐藏的一个性能问题。同样,对于使用了 selenium 方式的 WinAppDriver 也会有这个问题,所以添加这一行代码可以让我们频繁操作控件时不会那么容易出现错误的情况。
接下来其实就是查找控件并对控件进行操作了,这一部分其实和 Selenium 操作网页控件的方式完全一致,具体可以看下面这个链接 https://blog.csdn.net/qq_22003641/article/details/79137327?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162098237116780264092277%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162098237116780264092277&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-79137327.first_rank_v2_pc_rank_v29&utm_term=java+Selenium&spm=1018.2226.3001.4187。需要注意的是,有一些操作只在网页有效,而在客户端其实是没有效果的,比如通过 link text 和 通过 css 方式定位,以及 selenium 自带的执行 js 的方法,在这里都是不可用的,会报该方法没有实现的错误。
b、一些优化方式
上面的内容其实已经囊括了基本所有 WinAppDriver 的操作,接下来就是讲一下实际开发时可能会遇到的问题。
首先就是窗口切换的问题,和网页版的窗口不一样,传统PC桌面的窗口其实是通过句柄获取的,当你的窗口关闭了,你的句柄就失效了,这时候你就需要重新获取窗口。而网页版的窗口只需要通过 switchTo() 的方法就可以跳转到对应的窗口。
举个例子就是QQ,我们登录QQ时应该就会发现,QQ的登录窗口和登录完成后的好友列表窗口其实是两个完全不同的窗口,这时候如果你想从登录窗口点击登陆后跳转到好友列表窗口,直接使用 switchTo() 是会报出窗口已关闭的错误,所以对于这种情况,通常我们会重新获取当前窗体句柄来进行窗口的切换,示例代码如下:
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("app", "Root");
try {
long startTime = System.currentTimeMillis();
WindowsDriver rootDriver = new WindowsDriver(new URL("http://127.0.0.1:4723"),
capabilities);
WebElement element = rootDriver.findElement(By.xpath("//Window"));
if (element != null){
String newWindowHandle =
Integer.toHexString(Integer.parseInt(element.getAttribute("NativeWindowHandle")));
// Create session by attaching to Cortana top level window
DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.setCapability("appTopLevelWindow", newWindowHandle);
WindowsDriver windowsDriver = new WindowsDriver(new URL("http://127.0.0.1:4723"),
appCapabilities);
return windowsDriver;
}
} catch (Exception e){
e.printStackTrace();
}
讲解一下这段示例代码,首先我们会通过给 app 这个参数赋值 root 并发送配置给 WinAppDriver 进行重新连接,就可以拿到顶层程序的 WindowsDriver 对象,通过 xpath 的方式遍历所有窗口,我们就可以拿到第一个的窗体对象,然后通过 NativeWindowHandle 拿到该窗体的句柄,并通过设置 appTopLevelWindow 这个参数,重新发送配置给 WinAppDriver 后,我们就可以拿到当前第一个窗口的 WindowsDriver 对象了。
当然这个方法也是有问题的,比如他只会拿到第一个窗体,可能我们想要的并不是这个窗体,其次是会遇到很严重的性能问题,用过 selenium 的人会知道,当进行xpath方式进行遍历的话,耗时会特别的长。所以如果能拿到你所要跳转的窗体的名称的话,
都优先推荐使用窗体名称进行查询。
/**
* 通过名字找到windowsdriver
* @param windowName
* @return
*/
public WindowsDriver getDriverByName(String windowName){
/**
* 通过窗口名称,从桌面对象获取webdriver对象
*/
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("app", "Root");
try {
WindowsDriver rootDriver = new WindowsDriver(new URL("http://127.0.0.1:4723"),
capabilities);
WebElement windowElement = rootDriver.findElementByName(windowName);
if (windowElement != null){
String newWindowHandle =
Integer.toHexString(Integer.parseInt(windowElement.getAttribute("NativeWindowHandle")));
// Create session by attaching to Cortana top level window
DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.setCapability("appTopLevelWindow", newWindowHandle);
WindowsDriver windowsDriver = new WindowsDriver(new
URL("http://127.0.0.1:4723"), appCapabilities);
return windowsDriver;
}
}catch (NoSuchElementException e){
log.error("没有找到对应的名字的窗口:" + windowName);
}catch (MalformedURLException e){
log.error(e.getMessage());
}catch (WebDriverException e){
log.error(e.getMessage() + ", 连接winappdriver失败,请检查是否启动");
}
return null;
}
这段代码关键的部分就在于把 WebElement element = rootDriver.findElement(By.xpath("//Window")); 替换为 WebElement windowElement = rootDriver.findElementByName(windowName); ,这样查找的效率上会大幅度提升。
接下来就是我们刚刚遇到的性能问题,这里主要是针对一些特别的场景或者界面元素批量获取的场景,比如我想要拿到这个窗体下所有的控件的坐标,那么首先我们想到的就是会用 xpath 的方式进行获取。
List<WebElement> allElements = window.getDriver().findElements(By.xpath("//*"));
但是如果执行这行代码,你就会发现,你有两三分钟可能就卡在这里了。同样的问题也会发生在 selenium 网页端上,这是 selenium 一个致命的问题,并且如果你还要对这个 WebElement 进行获取属性的操作,比如 getName() ,那么你等待的时间可能就不是那么简单了。
那么该怎么解决这个问题呢。WinAppDriver 给我们提供了一个获取页面源码的功能,通过这个方式可以大大加快获取页面所有控件以及属性的方式。
String pageSource = appWindow.getDriver().getPageSource();
返回的 pageSource 是一个 xml 格式的字符串,里面包含所有的控件以及其属性信息。比如我们执行下面的代码
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("app", "C:\\Windows\\System32\\win32calc.exe");
CalculatorSession = new WindowsDriver(new URL("http://127.0.0.1:4723"), capabilities);
CalculatorSession.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
System.out.println(CalculatorSession.getPageSource());
打印出来的信息如下:
<?xml version="1.0" encoding="utf-16"?>
<Window AcceleratorKey="" AccessKey="" AutomationId="" ClassName="CalcFrame" FrameworkId="Win32" HasKeyboardFocus="False" HelpText="" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="窗口" Name="计算器" Orientation="None" ProcessId="18476" RuntimeId="42.592970" x="0" y="0" width="214" height="316" CanMaximize="False" CanMinimize="True" IsModal="False" WindowVisualState="Normal" WindowInteractionState="ReadyForUserInteraction" IsTopmost="False" CanRotate="False" CanResize="False" CanMove="True" IsAvailable="True">
</Window>
时间耗时可能就会减少90%以上,当然对于一个 xml 的字符串,我们不能直接使用,需要进行一个解析的操作获取到需要的元素信息。
对于 WinAppDriver 的内容大致就是这些,相信看完后会对你有所帮助