读后笔记 -- 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 的相关信息,会显示在浏览器中

 

posted on 2022-10-17 22:47  bruce_he  阅读(136)  评论(0编辑  收藏  举报