APPium自动化测试[详细使用]

2022-07-31  本文已影响0人  开心的小哈

用途和特点

  1. 自动化完成一些重复性的任务,比如微信客服机器人
  2. 爬虫自动化爬取信息,为什么不通过网页、HTTP爬取呢?有的系统产品没有网页端PC端只有app端
  3. 自动化测试,便于测试人员回归部署验证等测试

Appium自动化方案的特点

那么多自动化app的工具为什么选则appium?

  1. 开源免费
  2. 支持多个平台(android、IOS)
  3. 支持多种类型自动化:支持苹果安卓原生界面的自动化,支持应用内嵌WebView的自动化支持手机浏览器中的web网站自动化支持flutter应用的自动化
  4. 支持多种编程语言

自动化原理

android版


image.png

IOS版


image.png
appium工作原理
image.png

大家看看这幅图, 包含了 3个主体部分 : 自动化程序、Appium Server、移动设备

自动化程序

自动化程序是由我们来开发的,实现具体的 手机自动化 功能。

要发出具体的指令控制手机,也需要使用 客户端库。

和Selenium一样,Appium 组织 也提供了多种编程语言的客户端库,包括 java,python,js, ruby等,方便不同编程语言的开发者使用。

我们需要安装好客户端库,调用这些库,就可以发出自动化指令给手机。

Appium Server

Appium Server 是 Appium 组织开发的程序,它负责管理手机自动化环境,并且转发 自动化程序的控制指令 给 手机,并且转发 手机给 自动化程序的响应消息。

手机设备

我们这里说的手机设备,其实不仅仅是手机,包括所有 苹果、安卓的移动设备,比如:手机、平板、智能手表等。

为了直观方便的讲解,这里我们简称: 手机

当然手机上也包含了 我们要自动化控制的 手机应用APP。

手机设备为什么能 接收并且处理自动化指令呢?

因为,Appium Server 会在手机上 安装一个 自动化代理程序, 代理程序会等待自动化指令,并且执行自动化指令

比如:要模拟用户点击界面按钮,Appium 自动化系统的流程是这样的:

自动化程序 调用客户端库相应的函数, 发送 点击元素 的指令(封装在HTTP消息里)给 Appium Server

Appium Server 再转发这个指令给 手机上的自动化代理

手机上的自动化代理 接收到 指令后,调用手机平台的自动化库,执行点击操作,返回点击成功的结果给 Appium Server

Appium Server 转发给 自动化程序

自动化程序了解到本操作成功后,继续后面的自动化流程

其中,自动化代理控制,使用的什么库来实现自动化的呢?

如果测试的是苹果手机, 用的是苹果的 XCUITest 框架 (IOS9.3版本以后)

如果测试的是安卓手机,用的是安卓的 UIAutomator 框架 (Android4.2以后)

这些自动化框架提供了在手机设备上运行的库,可以让程序调用这些库,像人一样自动化操控设备和APP,比如:点击、滑动,模拟各种按键消息等。

环境搭建

安装包在为了方便我放在阿里云盘上面了是地址:https://sharelinkpre.rongdasoft.com/share-link/index.html?q=158e744d8a404000

步骤如下

  1. 安装client编程库
    Python语言开发则使用pip install appium-python-client
    使用java语言开发则使用:导jar包方式进行也可以使用maven进行下载
 <!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
    <groupId>io.appium</groupId>
    <artifactId>java-client</artifactId>
    <version>6.1.0</version>
</dependency>
  1. 安装appium Server
    appium Server是用nodejs运行的,基于js开发出来的.Appium组织为了方便大家安装使用制作了一个可执行程序appiumDesktoop
    把nodejs运行环境,appiumServer和一些工具打包在了里面了,只需要简单的下载安装就可以了
    「Appium-windows-1.15.1.exe」https://www.aliyundrive.com/s/vqSgSGpw78y

  2. 安装AndroidJDK
    百度androidSDK进行下载,下载完毕后配置ANDROID_HOME,配置adb命令monkey命令等地址即可

  3. 安装javaSDK
    需要用到androidSDK而androidSDK需要用到JAVA的JDK环境.所以在这里也需要安装一下java的JDK
    访问地址:配置JAVA_HOME和bin和jre\bin即可:「jdk-8u211-windows-x64.exe」https://www.aliyundrive.com/s/FSMV3bbxCvd

  4. 连接手机/模拟器
    上述环境都准备好,要自动化手机app需要,在你运行程序的电脑上用USB线或者wifi连接上你的安卓手机,进入手机设置->关于手机,不断点击版本号菜单,推出上级菜单,就会出现一个开发者模式设置按钮进入后启动USB调试即可
    连接好以后输入adb devices -l 命令来列出连接在电脑上的安卓设备.,点击回车如果输出一下内容则表示连接成功可以进行自动化了

注意:输入这个可以获取当前正在运行app的报名和activity: adb shell dumpsys window | findstr mCurrentFocus

快速入门

from appium import webdriver
from selenium.webdriver.common.by import By
from appium.webdriver.extensions.android.nativekey import AndroidKey

desired_caps = {
  'platformName': 'Android', # 被测手机是安卓
  'platformVersion': '8', # 手机安卓版本
  'deviceName': 'xxx', # 设备名,安卓手机可以随意填写
  'appPackage': 'tv.danmaku.bili', # 启动APP Package名称
  'appActivity': '.ui.splash.SplashActivity', # 启动Activity名称
  'unicodeKeyboard': True, # 使用自带输入法,输入中文时填True
  'resetKeyboard': True, # 执行完程序恢复原来输入法
  'noReset': True,       # 不要重置App
  'newCommandTimeout': 6000,
  'automationName' : 'UiAutomator2'
  # 'app': r'd:\apk\bili.apk',
}

# 连接Appium Server,初始化自动化环境
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

# 设置缺省等待时间
driver.implicitly_wait(5)

# 如果有`青少年保护`界面,点击`我知道了`
iknow = driver.find_elements(By.ID, "text3")
if iknow:
    iknow.click()

# 根据id定位搜索位置框,点击
driver.find_element(By.ID, 'expand_search').click()

# 根据id定位搜索输入框,点击
sbox = driver.find_element(By.ID, 'search_src_text')
sbox.send_keys('开心的')
# 输入回车键,确定搜索
driver.press_keycode(AndroidKey.ENTER)

# 选择(定位)所有视频标题
eles = driver.find_elements(By.ID, 'title')

for ele in eles:
    # 打印标题
    print(ele.text)

input('**** Press to quit..')
driver.quit()

appium2的find_element写法

注意:Appium Python 现在已经升级到 2.x 大版本,依赖 Selenium 4 以后, 下面这种 find_element_by* 方法都作为过期不赞成的写法

driver.find_element_by_id('username').send_keys('byhy')

运行会有告警,都要写成下面这种格式

from selenium.webdriver.common.by import By

wd.find_element(By.ID, 'username').send_keys('byhy')

而后续还有 Appium独有的查找方式,比如

driver.find_element_by_accessibility_id('byhy')
driver.find_element_by_android_uiautomator(code)

要改成

from appium.webdriver.common.appiumby import AppiumBy

driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'byhy')
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)

# 这样也可以根据ID
wd.find_element(AppiumBy.ID, 'username').send_keys('byhy')

定位元素

从示例代码,大家就可以发现,和Selenium Web自动化一样,要操作界面元素,必须先 定位(选择)元素。

Appium是基于Selenium的,所以 和 Selenium 代码 定位元素的 基本规则相同:

界面元素查看工具

做 Selenium Web 自动化的时候,要找到元素,我们是通过浏览器的开发者工具栏来查看元素的特性,根据这些特性(属性和位置),来定位元素

Appium 要自动化手机应用,同样需要工具查看界面元素的特征。

常用的查看工具是: Android Sdk包中的 uiautomateviewer 和 Appium Desktop 中的 Appium Inspector

  1. uiautomateviewer:安卓查看APP界面元素,最常用的就是 Android SDK 中的工具 uiautomateviewer ,它在SDK目录目录 的 tools\bin 目录中
    和Selenium一样,我们要定位选择元素,也是根据元素的特征,包括
    元素的属性
    元素的相对位置(相对父元素、兄弟元素等)
  2. Appium Inspector:Appium Desktop 中的 Appium Inspector 也可以查看元素。它的一个优点是可以直接验证 选择表达式是否能定位到元素
如:resource-id   com.phi.letter.letterphi:id/data_title
其中data_title就是该元素的ID
from appium.webdriver.common.appiumby import AppiumBy
eles = driver.find_elements(AppiumBy.ID, 'data_title')
from appium.webdriver.common.appiumby import AppiumBy
driver.find_elements(AppiumBy.CLASS_NAME,'android.widget.TextView')[3].click()
from appium.webdriver.common.appiumby import AppiumBy

driver.find_element(AppiumBy.ACCESSIBILITY_ID, '找人')

from appium.webdriver.common.appiumby import AppiumBy

driver.find_element(AppiumBy.XPATH, '//ele1/ele2[@attr="value"]')

注意:
selenium自动化中, xpath表达式中每个节点名是html的tagname。
但是在appium中, xpath表达式中 每个节点名 是元素的class属性值。
比如:要选择所有的文本节点,就使用如下代码


# 定位text
driver.find_element_by_xpath("//*[@text='扫一扫']").click()
# 定位 resource-id
driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text']").click()
# 也可以联合@resource-id属性和@text文本属性来下定位
driver.find_element_by_xpth("//*[@resource-id='com.taobao.taobao:id/tv_scan_text'][@text='扫一扫']").click()
# 定位搜索框 //class属性
driver.find_element_by_xpath("//android.widget.EditText").click()
# 定位搜索框  //*[@class='class属性']
driver.find_element_by_xpath("//*[@class='android.widget.EditText']").click()

dd=driver.find_element(AppiumBy.XPATH,'//androidx.recyclerview.widget.RecyclerView/android.view.ViewGroup[1]/android.widget.TextView[1]')# xpath是从1开始的
print(dd.text)

contains模糊定位

# contains匹配text
driver.find_element_by_xpath('//*[contains(@text, "注册/登录")]').click()
time.sleep(3)
# contains匹配textcontent-desc
driver.find_element_by_xpath("//*[contains(@content-desc, '帮助')]").click()

安卓UIAutomator

根据id,classname, accessibilityid,xpath,这些方法选择元素,其实底层都是利用了安卓 uiautomator框架的API功能实现的。
参考 这里的谷歌安卓官方文档介绍:https://developer.android.google.cn/training/testing/ui-automator
也就是说,程序的这些定位请求,被Appium server转发给手机自动化代理程序,就转化为为uiautomator里面相应的定位函数调用。
其实,我们的自动化程序,可以直接告诉 手机上的自动化代理程序,让它 调用UI Automator API的java代码,实现最为直接的自动化控制。
主要是通过 UiSelector 这个类里面的方法实现元素定位的,比如

code = 'new UiSelector().text("业务").className("android.widget.TextView")'
ele=driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)
ele.click()

就是通过 text 属性 和 className的属性 两个条件 来定位元素
UiSelector里面有些元素选择的方法 可以解决 前面解决不了的问题。
比如
text 方法
可以根据元素的文本属性查找元素
textContains
根据文本包含什么字符串
textStartsWith
根据文本以什么字符串开头
textmartch 方法
可以使用正则表达式 选择一些元素,如下

code = 'new UiSelector().textMatches("^我的.*")'

UiSelector 的 instance 和 index 也可以用来定位元素,都是从0开始计数, 他们的区别:
instance是匹配的结果所有元素里面 的第几个元素
index则是其父元素的几个节点,类似xpath 里面的*[n]
UiSelector 的 childSelector 可以选择后代元素,比如

code = 'new UiSelector().resourceId("tv.danmaku.bili:id/recycler_view").childSelector(new UiSelector().className("android.widget.TextView"))'

ele = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)

注意: childSelector后面的引号要框住整个 子 uiSelector 的表达式
目前有个bug:只能找到符合条件的第一个元素,参考appium 在github上的 issues:
https://github.com/appium/java-client/issues/150

界面操作

from appium.webdriver.extensions.android.nativekey import AndroidKey

# 输入回车键,确定搜索
driver.press_keycode(AndroidKey.ENTER)

按键的定义,可以参考这篇文档 https://github.com/appium/python-client/blob/master/appium/webdriver/extensions/android/nativekey.py

from appium.webdriver.common.touch_action import TouchAction
# ...
actions = TouchAction(driver)
actions.long_press(element)
actions.perform()
driver.open_notifications()

或者使用滑动

driver.swipe(start_x=10, start_y=0, end_x=10, end_y=1000, duration=500)

收起通知栏
收起通知栏,可以使用前面介绍的模拟按键, 发出返回按键 的方法
driver.back()
driver.keyevent(4)
我们自动化过程中,可能需要截屏手机,并且下载到指定目录中,就可以在我们的Python程序中这样写


import os
os.system('adb shell screencap /sdcard/screen3.png && adb pull /sdcard/screen3.png')

特别是,还可以通过adb 使用 am(activity manager) 和pm (package manager) 两个工具, 可以启动 Activity、强行停止进程、广播 intent、修改设备屏幕属性、列出应用、卸载应用等。

关闭APP

使用 driver.terminate_app(appPackage) 即可

内嵌网页自动化

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 找到 webview 对象
    WebView myWebView = (WebView) findViewById(R.id.jcywebview);
    // 添加打开 webview内容debug开关
    myWebView.setWebContentsDebuggingEnabled(true);
};

然后,编译出一个支持自动化的版本。
否则无法对webview中的内容进行自动化操作。

但是,因为网页内容在手机中呈现,不能像电脑浏览器那样,打开开发者工具,查看元素内容。
那我们怎么查看元素的属性特征,来定位选择元素呢?
这里面要分两种情况:

  1. webview不依赖App环境
    这种情况:App的内嵌 webview 和 native部分没有交互 的。
    webview 只是打开一个固定的网址(一般是该公司的手机网址)而已。
    这种情况,我们要查看webview内容非常简单。
    因为其实和手机没有关系。
    直接用Chrome浏览器F12里面的手机模式打开对应的网页,即可。

  2. webview依赖App环境
    首先,确保我们的应用运行,然后应用访问webview页面,打开电脑浏览器地址栏输入 chrome://inspect ,出现如下所示界面

    image.png

    (括号中说的是webview版本号)
    webview版本也比较老的,就会有问题, 尽量使用新手机进行自动化
    点击 inspect即可查看
    webview自动化代码 和 电脑上浏览器的自动化 基本差不多。
    但是有一点要注意:
    手机App中 webview里面的网页内容, 是在一个独立于应用native部分的环境里面的。
    而缺省情况下,find_element_by_xxx 这样的代码选择元素, Appium 只会在 native 部分的界面寻找元素。 肯定找不到元素。
    Appium 把一个界面环境 称之为一个 context 。
    native 部分的 context 名字为 NATIVE_APP , 而webview部分的context则为 WEBVIEW_XXX (XXX部分是 应用的 app package名)
    我们怎么查看当前有哪些context呢?
    我们的代码通过 driver 对象的 contexts 属性来获取,也就是 driver.contexts。
    driver 对象的 current_context 属性对应当前的 context 对象。
    大家可以打开自动化代码, 添加如下内容
    print(driver.contexts)
    执行一下,解释一下,可以发现结果如下。

['NATIVE_APP', 'WEBVIEW_stetho_com.phi.letter.letterphi']

我们的应用中, webview 的 context 就是 WEBVIEW_stetho_com.phi.letter.letterphi
而当前的context 是’NATIVE_APP', 所以当前的自动操作都是在native context里面的进行的。
要对该webview里面的网页内容进行自动化操作,必须先将当前的context切换为 webview的context,怎么切呢?
使用 switch_to.context
driver.switch_to.context('WEBVIEW_stetho_com.phi.letter.letterphi')
使用该语句如果webview版本和chrome驱动版本不一致则会报版本错误问题此时我们需要查看报错的版本,下载对应的chrome版本驱动包即可
版本对应查看地址:https://raw.githubusercontent.com/appium/appium-chromedriver/master/config/mapping.json
chrome驱动包下载地址:http://chromedriver.storage.googleapis.com/index.html

image.png
先点击 Advanced 设置项
image.png
然后在 下图位置 写上你的 老版本的 chromedriver 的路径
image.png

好,现在我们修改代码,如下所示

from selenium.webdriver.common.by import By

driver.switch_to.context('WEBVIEW_com.example.jcy.wvtest')

driver.find_element(By.ID, 'index-kw').send_keys('开心的小哈')

driver.find_element(By.ID, 'index-bn').click()

执行一下,发现可以自动化了
那么怎么切换回 native app 进行自动化呢?
当然是 继续使用 switch_to.context ,如下
driver.switch_to.context('NATIVE_APP')

手机浏览器网页自动化

有的公司开发了手机版网站,直接用手机浏览器打开的,比如,xiaomi 京东等。
并不是 手机App
这种手机网页,我们怎么 程序自动化呢?
首先,必须在手机上安装谷歌浏览器。
以百度为例,
首先启动 Appium Desktop。
然后,我们的自动化程序代码如下所示:


from appium import webdriver
from selenium.webdriver.common.by import By

desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7'
desired_caps['deviceName'] = 'test'
desired_caps['browserName'] = 'Chrome'
desired_caps['newCommandTimeout'] = 6000
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

driver.implicitly_wait(10)

driver.get('http://www.baidu.com')

driver.find_element(By.ID, 'index-kw').send_keys('开心的小哈')

driver.find_element(By.ID, 'index-bn').click()

driver.quit()

更多了解:AndroidViewClient UiAutomator2

在使用过程中出现的问题

  1. 点击web页面元素时如果使用ID,NAME定位结果出现
  raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.InvalidArgumentException: Message: invalid argument: invalid locator
  (Session info: chrome=88.0.4324.93)
Stacktrace:
Backtrace:
    GetHandleVerifier [0x000BB963+483]
    GetHandleVerifier [0x000BB941+449]
    GetHandleVerifier [0x00463308+3832712]
    GetHandleVerifier [0x004863A7+3976231]
    GetHandleVerifier [0x004A8822+4116642]
    GetHandleVerifier [0x0049B43A+4062394]
    GetHandleVerifier [0x004A7159+4110809]
    GetHandleVerifier [0x0049B2EB+4062059]
    GetHandleVerifier [0x0047ED14+3945876]
    GetHandleVerifier [0x0047FBCE+3949646]
    GetHandleVerifier [0x0047FB59+3949529]
    Ordinal0 [0x0007B5CC+46540]
    Ordinal0 [0x00079F53+40787]
    Ordinal0 [0x00079B12+39698]
    GetHandleVerifier [0x00381468+2907368]
    GetHandleVerifier [0x001C71EE+1096302]
    GetHandleVerifier [0x00183E8D+821005]
    GetHandleVerifier [0x0018396B+819691]
    GetHandleVerifier [0x00183881+819457]
    GetHandleVerifier [0x001AF463+998627]
    BaseThreadInitThunk [0x76B4FA29+25]
    RtlGetAppContainerNamedObjectPath [0x77B57A7E+286]
    RtlGetAppContainerNamedObjectPath [0x77B57A4E+238]
    (No symbol) [0x00000000]

问题原因导致:如果 chromedriver 在 W3C 模式下工作,则会出现这种情况。W3C标准只声明CSS和XPATH定位器,其中id和名称的位置已被删除为过时,因为CSS涵盖了它们。
有三种可能的解决方法:

  1. 更新不受支持的定位器
  2. 将 chromedriver 强制执行 JSONWP 模式(将 chromedriver 选项设置为w3cfalse)
  3. 将过时的定位器类型传递给客户端代码中的转换方法,该方法会自动将它们升级到CSS定位器(这是Selenium lib当前正在做的事情)
    其中第二个需要在开启时配置
desired_caps['chromeOptions'] = {'w3c': False}
desired_caps['showChromedriverLog'] = data['showChromedriverLog']

即可;

上一篇下一篇

猜你喜欢

热点阅读