桌面应用自动化winappdriver
桌面应用自动化winappdriver
关于winappdriver
介绍
- WinAppDriver全称是Windows Application Driver,它提供了一些API,使得用户可以像selenium操作web一样来操作windows的应用程序
 - 它支持的系统是Windows 10 (Home and Pro) 和Windows Server 2016
 - 源码暂未开源
 - WinAppDriver可以独立运行,也可以作为appium的一个插件来使用
 
支持应用类型
- 
UWP – Universal Windows Platform, also known as Universal Apps or Modern Apps, It's Microsoft’s latest desktop application technology. It's XAML based. Only runs on Windows 10 machines
 - 
WPF - also XAML based, much more mature, runs on any Windows version and has been around since 2006.
 - 
WinForms - one of the older technologies, now found mostly on legacy applications.
WPF和WinForms 是两套界面渲染方式。一个是对传统windows界面元素的封装,通过gdi绘制。另一个是全新的dx渲染绘制的界面,也脱离了对传统windows控件的依赖,没有历史包袱,理论上可以展现更炫酷的界面。 - 
MFC/Classic Windows - MFC is a UI library normally paired with Win32 applications. This option is normally chosen when more efficiency is needed with low-level C++ handling or when supporting non-Microsoft platforms.
 
资源
| 素材 | 地址 | 说明 | 
|---|---|---|
| FlaUInspect | https://github.com/FlaUI/FlaUInspect/releases | 定位工具 | 
| WinAppDriver | https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1 | |
| UIRecorder | https://github.com/microsoft/WinAppDriver/tree/master/Tools/UIRecorder | 定位工具 | 
| inspect | 微软官方工具集成于 Windows SDK | 定位工具 | 
- UIRecorder(下文不涉及,仅供参考与备忘)
 
- Open
 WinAppDriverUIRecorder.slnin Visual Studio- Select Debug > Start Debugging or simply Run
 
支持的定位方式
| Client API | Locator Strategy | Matched Attribute in inspect.exe | 
Example | 
|---|---|---|---|
| FindElementByAccessibilityId | accessibility id | AutomationId | AppNameTitle | 
| FindElementByClassName | class name | ClassName | TextBlock | 
| FindElementById | id | RuntimeId (decimal) | 42.333896.3.1 | 
| FindElementByName | name | Name | Calculator | 
| FindElementByTagName | tag name | LocalizedControlType (upper camel case) | Text | 
| FindElementByXPath | xpath | Any | //Button[0] | 
配置
开启windows的开发者模式
- 你没看错,不是手机,windows也有
 - 第一步:搜开发者设置
 - 第二步:打开开发人员模式
 

- 第三步:确认启用
 

启动winappdriver
- 
不开启开发人员模式的提示
C:\Program Files (x86)\Windows Application Driver>WinAppDriver.exe Developer mode is not enabled. Enable it through Settings and restart Windows Application Driver Failed to initialize: 0x80004005 - 
开启后启动winappdriver
C:\Program Files (x86)\Windows Application Driver>WinAppDriver.exe Windows Application Driver listening for requests at: http://127.0.0.1:4723/ Press ENTER to exit. - 
还可以这样启动
WinAppDriver.exe 4727 WinAppDriver.exe 10.0.0.10 4725 WinAppDriver.exe 10.0.0.10 4723/wd/hub # 推荐 
实例
appium-python-client 版本不要用2.0+,此处是1.2.0
记事本
- 
比如记事本
from appium import webdriver des_cap = {} des_cap['app'] = r'C:\Windows\System32\notepad.exe' driver = webdriver.Remote(command_executor='http://127.0.0.1:4723/wd/hub', desired_capabilities=des_cap) driver.implicitly_wait(5) driver.find_element_by_name('文件(F)').click() from time import sleep sleep(2) driver.find_element_by_name('保存(S) Ctrl+S').click() # driver.find_element_by_name('退出(X)').click() sleep(1) import pyautogui pyautogui.PAUSE = 0.5 pyautogui.typewrite(r'D:\hello.txt') pyautogui.press('enter') - 
这里的难点是
保存(S) Ctrl+S的获取 - 
这里需要用到inspect.exe
 

计算器
- 你可能会写这样的代码
 
from appium import webdriver
des_cap = {}
des_cap['app'] = r'C:\Windows\System32\calc.exe'
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723/wd/hub',
                          desired_capabilities=des_cap)
driver.implicitly_wait(5)
- 但会报错
 
Traceback (most recent call last):
  File "D:/demo_calc.py", line 5, in <module>
    desired_capabilities=des_cap)
  File "D:\Python37\lib\site-packages\appium\webdriver\webdriver.py", line 157, in __init__
    AppiumConnection(command_executor, keep_alive=keep_alive), desired_capabilities, browser_profile, proxy
  File "D:\Python37\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 157, in __init__
    self.start_session(capabilities, browser_profile)
  File "D:\Python37\lib\site-packages\appium\webdriver\webdriver.py", line 226, in start_session
    response = self.execute(RemoteCommand.NEW_SESSION, parameters)
  File "D:\Python37\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "D:\Python37\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: Failed to locate opened application window with appId: C:\Windows\System32\calc.exe, and processId: 4472
进程已结束,退出代码为 1
- 打开计算器,然后在powershell中执行如下命令
 
Get-StartApps |Select-String "计算器"
# 输出
@{Name=计算器; AppID=Microsoft.WindowsCalculator_8wekyb3d8bbwe!App} # 你要的是这里的AppID
- 代码
 
from appium import webdriver
des_cap = {}
des_cap['app'] = r'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App'
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723/wd/hub',
                          desired_capabilities=des_cap)
driver.implicitly_wait(5)
driver.find_element_by_name('一').click()
driver.find_element_by_name('二').click()
driver.find_element_by_name('加').click()
driver.find_element_by_name('三').click()
driver.find_element_by_name('四').click()
driver.find_element_by_name('等于').click()
# 通过inspect 获取 automationID 
print(driver.find_element_by_accessibility_id('CalculatorResults').text) # 得到的是   ·显示为 46·  你仍然要处理才能做测试
driver.quit()
计算器测试(官网)
我没跑,仅供参考,你可以认为是为了增加篇幅
# https://raw.githubusercontent.com/microsoft/WinAppDriver/master/Samples/Python/calculatortest.py
import unittest
from appium import webdriver
class SimpleCalculatorTests(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        #set up appium
        desired_caps = {}
        desired_caps["app"] = "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"
        self.driver = webdriver.Remote(
            command_executor='http://127.0.0.1:4723',
            desired_capabilities= desired_caps)
    @classmethod
    def tearDownClass(self):
        self.driver.quit()
    def getresults(self):
        displaytext = self.driver.find_element_by_accessibility_id("CalculatorResults").text
        displaytext = displaytext.strip("Display is " )
        displaytext = displaytext.rstrip(' ')
        displaytext = displaytext.lstrip(' ')
        return displaytext
    def test_initialize(self):
        self.driver.find_element_by_name("Clear").click()
        self.driver.find_element_by_name("Seven").click()
        self.assertEqual(self.getresults(),"7")
        self.driver.find_element_by_name("Clear").click()
    def test_addition(self):
        self.driver.find_element_by_name("One").click()
        self.driver.find_element_by_name("Plus").click()
        self.driver.find_element_by_name("Seven").click()
        self.driver.find_element_by_name("Equals").click()
        self.assertEqual(self.getresults(),"8")
    def test_combination(self):
        self.driver.find_element_by_name("Seven").click()
        self.driver.find_element_by_name("Multiply by").click()
        self.driver.find_element_by_name("Nine").click()
        self.driver.find_element_by_name("Plus").click()
        self.driver.find_element_by_name("One").click()
        self.driver.find_element_by_name("Equals").click()
        self.driver.find_element_by_name("Divide by").click()
        self.driver.find_element_by_name("Eight").click()
        self.driver.find_element_by_name("Equals").click()
        self.assertEqual(self.getresults(),"8")
    def test_division(self):
        self.driver.find_element_by_name("Eight").click()
        self.driver.find_element_by_name("Eight").click()
        self.driver.find_element_by_name("Divide by").click()
        self.driver.find_element_by_name("One").click()
        self.driver.find_element_by_name("One").click()
        self.driver.find_element_by_name("Equals").click()
        self.assertEqual(self.getresults(),"8")
    def test_multiplication(self):
        self.driver.find_element_by_name("Nine").click()
        self.driver.find_element_by_name("Multiply by").click()
        self.driver.find_element_by_name("Nine").click()
        self.driver.find_element_by_name("Equals").click()
        self.assertEqual(self.getresults(),"81") 
    def test_subtraction(self):
        self.driver.find_element_by_name("Nine").click()
        self.driver.find_element_by_name("Minus").click()
        self.driver.find_element_by_name("One").click()
        self.driver.find_element_by_name("Equals").click()
        self.assertEqual(self.getresults(),"8")
if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(SimpleCalculatorTests)
    unittest.TextTestRunner(verbosity=2).run(suite)