读后笔记 -- Python 全栈测试开发 Chapter7:移动自动化测试框架
7.1 主流框架优缺点剖析
1. APP 主要测试策略
- 安装、卸载测试:
- 安装:1)安装路径;2)安装环境(平台、安全软件);3)安全权限(获取位置、摄像头、通讯录、ROOT管理员等权限);4)安装的版本;5)严酷测试:a)安装过程中取消;b)安装过程中重启、关机;c)内存不足下安装;d)无网或弱网下进行安装验证;
- 卸载:1)安装路径下删除对应的文件;2)写入系统部分的信息进行删除(PC注册表);3)卸载过程中取消、重启;4)卸载释放空间(软件所产生的数据信息、缓存、应用程序空间)
- UI测试:
- (如菜单、对话框、窗口和其它可规控件)布局、风格是否满足客户要求、文字是否正确、页面是否美观、文字、图片组合是否完美、操作是否友好等
- 主要包含三部分内容:导航、图形、内容
- 功能测试:
- 1)运行APP(安装完成后打开APP、加载进度、APP页面切换、注册/登录/注销)、2)应用的前后台切换(前后台切换、锁屏、电话、强杀APP再开启、需要处理的提示框时前后台切换、数据交互时前后台切换)、
- 3)免登陆(app有免登陆时考虑版本差异、无网络时是否可免登陆、切换用户登录后,校验登录信息及数据更新,原用户退出、一用户登录另外一设备,原设备退出、app前后台切换、更改密码后,数据交互身份校验、用户退出后,下次是登录界面)、
- 4)数据更新、离线浏览、APP更新、定位/相机服务、时间测试、PUSH测试
- 性能测试
- 交叉事件测试
- 升级、更新测试
- 用户体验测试
- 硬件环境测试
- 客户端数据库测试
- 安全测试
2. 几个主流的框架:
Android UI 自动化的两大类: 1)Android(UI Automator):通过 Android 提供的服务获取当前窗口的视图信息
2)基于 Instrumentation 把测试APK 和被测 APK,运行一个进程中,通过 Java 反射机制获取当前窗口的所有视图,根据该视图获取目标控件的属性
- UI Automator:优点:1)Android 提供,基本支持所有的 Android 事件;2)相对 Instrumentation,不需要了解代码实现细节; 3)基于 Java,测试代码简单;4)跨APP,可打开如相册等其他功能;
- 缺点:只支持 SDK16(Android 4.1)及以上,不支持 Hybrid APP、Web APP
- Espresso(基于 Instrumentation):Google开源自动化框架,相对 Robotium 和 UI Automator,规模小、简洁,测试代码简单,易上手。不能跨 APP
- Selendroid(基于 Instrumentation):可测试 Native APP、Hybrid APP、Web APP。 缺点:资料少
- Robotium (基于 Instrumentation):应用较多。缺点:测试需要有 java 基础,了解 Android 基本租金,不能跨 APP
- Appium:目前主流
7.2 Appium 框架
1. Appium 是开源、跨平台的测试框架,可测试 iOS、Android、Firefox OS 的模拟器和真机。使用 WebDriver 的 JSON wire 协议来驱动 Apple 的 UI Automation 及 Android 的 UI Automator 框架。
Q: 自动化测试过程中,涉及的相关数据有哪些类型及如何处理? 分析:测试过程中涉及到哪些数据? => 业务流所需的(测试数据、用户数据)、环境数据、程序中的固定数据(常量数据) 通常, 1)程序中固定数据,一般会设置为常量数据; 2)业务测试数据,一般直接提取分离到相应的数据格式文件(csv/yaml/json/xml),有可以将该部分数据直接从数据库中读取(access/sqlite/mysql/oracle) 3)环境配置相关数据一般会提取到 ini、data、xml 等格式文件
2. Appium 日志分析
# 1. 获取当前模拟器的 android 版本 adb shell getprop ro.build.version.release # 2. 获取当前模拟器的 sdk 版本 adb shell getprop ro.build.version.sdk # 3. 获取当前模拟器的分辨率 adb shell vm size # 4. 获取机型、品牌 adb shell getprop ro.product.model adb shell getprop ro.product.manufactuer # 5. 如设定 app 参数,实际是将 app 指定路径的 apk 先上传到模拟器或手机中,然后再进行安装;默认的上传路径是: /data/local/tmp/appium_cache # 6. dos 下安装app 命令是 adb install -r xxxx.apk,如进入手机终端其命令是 adb shell pm install -r xxxx.apk
7.3 Desired Capabilities
1. 官方说明文档:http://appium.io/docs/en/writing-running-appium/caps/index.html
2. 如需要与模拟器建立会员,需申明的值有:
desired_caps = { "platformName": "Android", "platformVersion": "7.1.2", "deviceName": "3141a881", "appPackage": "com.gece.intelligence", "appActivity": "com.hdme.hdjy.controller.SplashActivity", "skipServerInstallation": True, "noReset": True }
3. 需要注意的事项有:
- 1)capbilities 的键名严格区分大小写,是关键字
- 2)capbilites 的键名不能有空格
- 3)start appium server before create session
- 4)appium-server 与模拟器建立连接时需保证 adb devices 时能正常获取到设备,如提示版本不一致,可将 模拟器路径下的 adb 备份,再讲 android sdk 下的 adb 复制到模拟器路径下
7.4 Appium 元素定位
7.4.1. app 元素定位工具:
- 1)appium-server 的 appium-inspect:appium-server 中新建一个 session,填入对应的 desired_capabilities,打开 app
- 2)android-sdk \ tools \ uiautomatorviewer.bat 【注意:JDK1.8 可以正常使用,JDK17 时会运行不了,后续有方法时再更新】
- 前提:手机/模拟器 与其他连接(如 appium-server)断开,否则被占用
7.4.2. 元素定位方法:
需要注意 app 的类型:
- 1)原生 app: 支持 id,class_name, xpath。 其他5种在 HTML5 上支持
- 2)web app:支持 selenium 的 8 种定位
- 3)混合 app:即 原生+HTML5 的混合应用,定位视情况而定
7.4.3 原生 app 的定位方式
1)id:-> resource-id (app 里 id 一般是唯一的) self.driver.find_element(By.ID, "com.tencent.mm:id/f3d").click() 2)class_name: -> class (app 里,class_name 一般不唯一,通常使用复数形式) self.driver.find_elements(By.CLASS_NAME, "android.widget.Buton")[1].click() 3)Xpath 3.1) resource-id 唯一 self.driver.find_element(By.XPATH, "//*[@resource-id='com.tencent.mm:id/fam']").click() 3.2)class 属性唯一 3.2.1)作为标签 self.driver.find_element(By.XPATH, "//android.widget.Button").click() 3.2.2)class 作为属性 self.driver.find_element(By.XPATH, "//*[@class='//android.widget.Button']").click() 3.3)text 值唯一 self.driver.find_element(By.XPATH, "//*[@text='登录']").click() 3.4) contains 模糊匹配 self.driver.find_element(By.XPATH, "//*[contains(@text, '登')]").click() 3.5)组合定位 # 如 class_name 和 id 的组合 id_class = '//android.widget.Button[@resource-id="com.tencent.mm:id/fam"]' self.driver.find_element(By.XPATH, id_class).click() 3.6) 层级定位 father_son = '//*[@resource-id="com.tencent.mm:id/fam"]/android.widget.Button[1]' self.driver.find_element(By.XPATH, father_son).click()
7.5 Appium 高级元素定位及扩展
7.5.1. accessibility_id
android 上其属性对应 content-desc;IOS 上对应 label 或 name
self.driver.find_element_by_accessibility_id("More Info").click()
7.5.2. 坐标点定位
一般需要根据 屏幕大小计算相对比例。适应于 实在无法定位的情况下,或跳转第三方软件如微信分享
# driver 下的 tap() 传 1个或多个 (x,y) 元组的列表
self.driver.tap([(x1, y1), (x2, y2)], duration)
7.5.3. uiautomator 定位
1)text 方法,-> 对应 text 属性
# 1)text login_text = 'text("登录")' driver.find_element_by_android_uiautomator(login_text).click() # 2)textContains login_textContains = 'textContains ("录")' driver.find_element_by_android_uiautomator(login_textContains ).click() # 3)textStartWith login_textStart = 'textStart("登")' driver.find_element_by_android_uiautomator(login_textStart).click() # 4)textMatches login_textMatches = 'textMatches (".*")' // 参数需要传入一个正则表达式 driver.find_element_by_android_uiautomator(login_textMatches ).click()
2)resouceId 方法 -> 对应 resouce-id
id = 'resourceId("com.tencent.mm:id/fam")' driver.find_element_by_android_uiautomator(id).click()
3)className 方法 -> 对应 class 属性
className = 'className("android.widget.Button")' driver.find_element_by_android_uiautomator(className).click()
4)组合定位
# 如 resouceId 和 text 组合 IdText = 'resouceId("com.tencent.mm:id/fam").text("登录")' driver.find_element_by_android_uiautomator(IdText).click() # className 和 text 组合 classText = 'className("android.widget.Button").text("登录")' driver.find_element_by_android_uiautomator(classText).click()
5)父子定位
# 通过父元素定位子元素:父元素.childSelector(子元素) fatherChild = 'resourceId("com.tencent.mm:id/fan").childSelector(className("android.widget.Button"))' driver.find_element_by_android_uiautomator(fatherChild).click()
6)兄弟定位
# 通过同级元素定位同级元素,方式:好定位的兄弟属性.fromParent(较难定位的属性) brother = 'resourceId("com.tencent.mm:id/faw").fromParent(className("android.widget.Button"))' driver.find_element_by_android_uiautomator(brother).click()
注意: 1. 所有方法前面都省略了 创建对象 如 login.text = 'text("登录")', 其完整的是: login.text = 'new UiSelector().text("登录")' 2. 所有方法体内的参数必须是 双引号 "",外面是 单引号 '',原因是 new UiSelector() 是 java 对象,其字符串参数必须 双引号。
======== 最新的 selenium 4.5 语法 ========= from appium import webdriver from Read_Ini import ReadIni from appium.webdriver.common.appiumby import AppiumBy from time import sleep class StartAPP(object): def __init__(self, desired_caps, **kwargs): self.driver = webdriver.Remote(command_executor="http://%s:%s/wd/hub" % (kwargs["server_address"], kwargs["sever_port"]), desired_capabilities=desired_caps) self.driver.implicitly_wait(10) def to_custom_page(self): sleep(3) hangqing_icon = 'className("android.widget.TextView").text("行情")' self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, hangqing_icon).click() my = "//android.widget.TextView[@text='我的']" self.driver.find_element(AppiumBy.XPATH, my).click() if __name__ == '__main__': appium_config = ReadIni() ini_path = "app_appium_config.ini" start_app = StartAPP(appium_config.read_ini(ini_path, "desired_capabilities"), **appium_config.read_ini(ini_path, "server_info")) start_app.to_custom_page()
7)TouchAction 坐标/元素定位
from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.common.touch_action import TouchAction from selenium.webdriver.support.wait import WebDriverWait def qq_login_TouchAction2(self): # 另外一个方式,该元素可定位,但无法点击。通过 selenium 的 WebDriverWait 的显式等待 # tap() 参数可以是元素,也可以是坐标点 id = 'resourceId("com.tencent.mobileqq:id/btn_login")' try: get_element = WebDriverWait(self.driver, 10, 1).until(lambda dir:dir.find_element(AppiumBy.ANDROID_UIAUTOMATOR, id)) TouchAction(self.driver).tap(x=500, y=1700).perform() except: print(sys.exc_info())
7.6 Appium API 及对象识别
1. appium 常用的 API
appium 官方文档: http://appium.io/docs/en/about-appium/intro/
driver.launch_app() | 重新启动 desired capabilities 中定义的 app | |
driver.close_app() | 关闭 desired capabilities 中定义的 app | driver.quit() 和 driver.close() 是针对 H5的,原生 app 需要使用 close_app() |
以上两个方法通常是针对初始化的 app 实现启动和关闭操作 | ||
driver.start_activity(app_package, app_activity) driver.active_app(app_package) |
启动一个指定包的 app |
当 app 处于后台运行时,可以用该命令重新唤起 launch_app 是重新启动 app |
driver.terminate_app(app_package) | 结束一个指定包的 app | |
driver.background_app(secs) |
将初始化 app 置于后台 多少秒 其中 -1/null 表示一直处于后台 |
1). 过 n 秒后,会重新回到前台; 2). -1/null 会一直置于后台,可以使用 start_activity() 再将其调用出来 |
3)driver.press_keycode(3) 按下 home 的方式让app 置于后台 | ||
driver.press_keycode(code) driver.long_press() driver.send_keys() |
对应手机的键盘操作
|
1. 对应关系:https://developer.android.com/reference/android/view/KeyEvent (需要FQ) 2. 如需发送中文,需要在 desired capabilities 中定义: unicodeKeyboard = true, resetKeyboard = true |
driver.swipe(start_x, start_y, end_x, end_y, duration) 坐标滑动 driver.scroll(from_element, to_element) 基于元素位置滑动 driver.drag_and_drop(from_element, to_element) 基于元素位置拖拽 |
||
1)tap 单击: TouchAction(driver).tap(element).perform() TouchAction(driver).tap(x, y).perform() 2)press and release TouchAction(driver).press(x, y).perform() TouchAction(driver).press(x, y).release().perform() TouchAction(driver).press(x, y).wait(1000).release().perform() 3)move_to 移动,可用于手势密码操作 TouchAction(driver).press(x,y).move_to(x1, y1).move_to(x2,y2).\ ...release().perform() |
from appium.webdriver.common.touch_action import TouchAction |
7.7 Native 与 WebView
1. 小程序:基于微信或支付宝平台上的 H5 页面的运行方式(依赖浏览器)
2. WebView:基于 WebKit 引擎,采用 chromium (android 4.4+)及 WebKit (< android 4.4)
3. 如何判定当前页面是原生的还是 H5
- 1)原生是 activity 窗口间的切换;H5 是访问页面,会出现加载进度条。如果有进度条则说明有 H5 嵌套
- 2)无网情况下,原生可以正常加载当前页面元素;H5 则无法显示
- 3)如果页面有关闭操作,则基本为 H5
4. 如果需要获取当前程序中的所有 context(上下文)状态,则 app 必须为 debug 状态
如果是使用模拟器,则需要进行如下操作:
- 1)安装逍遥模拟器
- 2)模拟器中安装 xposed 框架(xposed 版本与模拟器的操作系统版本要一致)
- 3)将 webviewhook.apk 组件加载到 框架中
- 4)模拟器中安装 chrome 浏览器,且正常打开
- 5)PC 上 chrome 浏览器输入 chrome://inspect/#devices,至此,app 上如果有 webview 的相关信息,会显示在浏览器中