APP自动化测试appium(一)

为啥要学习APP的自动化测试?它有什么用?

提高效率

  • 融入企业迭代流水线,与 CI/CD/DevOps 结合

  • 回归测试、功能测试加速

提高质量:

  • 兼容性测试

  • 专项/非功能测试

  • 自动化探索测试

背景:

按月发布->按周发布->按小时发布

多端发布:Android、iOS、微信小程序、h5

多环境发布:联调环境、测试环境、预发布环境、线上环境

多机型发布:众多设备型号、众多系统版本

多版本共存:用户群体中存在多个不同的版本

历史回归测试任务:成百上千条业务用例如何回归

测试工具(框架)的选择:

Appium

Airtest

其他框架:calabash macaca atx

iOS:KIF WDA XCUITest

Android:Robotium Uiautomator2

为什么使用Appium:

  • 跨语言:Java、Python、nodejs 等

  • 跨平台

    • Andoid、iOS

    • Windows、Mac

  • 底层多引擎可切换

  • 生态丰富,社区强大

Appium 安装:

需要JDK和SDK

JDK安装:

[ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)_java安装和环境变量配置-CSDN博客

SDK安装:

【Andriod】SDK下载安装及环境配置完整教程_安卓sdk下载-CSDN博客

环境搭建:

【App自动化测试】Appium(一)环境搭建 - 知乎 (zhihu.com)

对于第三方模拟器(mumu, 夜神等),windows需要手动连接。
推荐:mumu模拟器,需要手动输入下面的连接命令:

adb connect 127.0.0.1:7555

上面的 127.0.0.1:7555 作为一个整体,是 模拟器的名称,也叫序列号(serial number)。是根据本地的 IP 和端口号生成的, 不同的模拟器对应的端口不一样,具体用的哪个端口可以去百度搜索一下或者官网查看 。

mac上直接执行 adb devices 。如果没有识别,需要重启 adb server,执行下面两条命令即可。

adb kill-server
adb devices

appium-inspector安装

Releases · appium/appium-inspector (github.com)

安装完成后连接到appium

这里的参数有很多:

Android和iOS公共的部分

描述

platformName

使用的手机操作系统

iOSAndroid,或者 FirefoxOS

platformVersion

手机操作系统的版本

例如 7.1, 4.4

deviceName

使用的手机或模拟器类型

iPhone Simulator, iPad Simulator, iPhone Retina 4-inch, Android Emulator, Galaxy S4, 等等… 在 iOS 上,使用 Instruments 的 instruments -s devices 命令可返回一个有效的设备的列表。在 Android 上虽然这个参数目前已被忽略,但仍然需要添加上该参数

automationName

使用哪个自动化引擎

android默认使用uiautomator2,ios默认使用XCUTest

noReset

在当前 session 下不会重置应用的状态。默认值为 false

true, false
对安卓来说是不清除数据、不停止应用

dontStopAppOnReset

也就是用例连续执行,接管上一个用例的结尾

true, false
其实也就是adb shell am start 有没有加-S

加了再次输入会重新启动

fullReset

清除数据、停止应用,并删除apk
新会话之前完全卸载被测应用程序

默认为 false

true, false

udid

连接的真实设备的唯一设备编号 (Unique device identifier)
支持多设备同时运行
需要模拟器多开器

例如 1ae203187fc012g

不同设备可以配置

autoGrantPermissions

自动点击接受

Android特有部分:

描述

appActivity

Activity 的名字是指从你的包中所要启动的 Android activity。他通常需要在前面添加.

MainActivity, .Settings

appPackage

行的 Android 应用的包名

com.example.android.myApp, com.android.settings

appWaitActivity

用于等待启动的 Android Activity 名称

SplashActivity

unicodeKeyboard

启用 Unicode 输入,默认为 false

true or false

resetKeyboard

在测试结束后恢复到原来的键盘

true or false

dontStopAppOnReset

首次启动的时候,不停止 app

true or false

skipDeviceInitialization

跳过安装,权限设置等操作

true or false

IOS特有部分:

描述

bundleId

被测应用的 bundle ID。用于在真实设备中启动测试,也用于使用其他需要 bundle ID 的关键字启动测试。在使用 bundle ID 在真实设备上执行测试时,你可以不提供 app 关键字,但你必须提供 udid

例如 io.appium.TestApp

autoAcceptAlerts

当 iOS 的个人信息访问警告 (如位置、联系人、图片) 出现时,自动选择接受 (Accept)。默认值 false

true 或者 false

showIOSLog

是否在 appium 日志中显示从设备捕获的任何日志。默认 false

true or false

如果想要对app进行操作,需要知道app的appPackage和appActivity

app 入口,两种方式获取:

  • 1、通过 logcat 日志获取

    • Mac/Linux: adb logcat ActivityManager:I | grep "cmp"

    • Windows: adb logcat ActivityManager:I | findstr "cmp" 这里注意一定是CMD才可以,PowerShell 不太行

其实就是实时的日志,我们对模拟器进行操作,可以获得我们需要的appPackage和appActivity

  • 2、通过 aapt 获取

    • Mac/Linux: aapt dump badging wework.apk | grep launchable-activity

    • Windows: aapt dump badging wework.apk | findstr launchable-activity

  • 启动应用命令 adb shell am start -W -n <package-name>/<activity-name> -S

deviceName可以随便起,建议使用adb devices指令获得的端口信息:

使用录制功能:

from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy

# 用于 W3C actions 的引入
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput
from selenium.webdriver.common.actions.interaction import POINTER_TOUCH

# 实例化 Appium 配置
options = AppiumOptions()

# 添加配置信息
options.load_capabilities({
    "platformName": "Android",  # 指定操作系统平台
    "appPackage": "com.android.settings",  # 指定要启动的包名
    "appActivity": ".Settings",  # 指定要启动的 Activity
    "deviceName": "127.0.0.1111",  # 指定要连接的设备
    "ensureWebviewsHavePages": True,# 是否等待页面加载完成才进行操作
    "nativeWebScreenshot": True,# 使用原生方法获取截图
    "newCommandTimeout": 3600,# 设置超时时间,3600(秒),超时后将自动结束会话
    "connectHardwareKeyboard": True	#是否模拟键盘硬件连接 也是
})

# 启动远程 WebDriver 连接
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)

# 寻找“存储”选项并点击
el_storage = driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[@resource-id='android:id/title' and @text='存储']")
el_storage.click()


# 执行向上滑动的动作
actions = ActionChains(driver)
action_builder = ActionBuilder(driver, mouse=PointerInput(POINTER_TOUCH, "touch"))
action_builder.pointer_action.move_to_location(356, 889)
action_builder.pointer_action.pointer_down()
action_builder.pointer_action.move_to_location(391, 381)
action_builder.pointer_action.release()
actions.w3c_actions = action_builder
actions.perform()

# 寻找“文档和其他”选项并点击
el_documents = driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[@resource-id='android:id/title' and @text='文档和其他']")
el_documents.click()

# 点击搜索图标
el_search = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "搜索")
el_search.click()

# 在搜索框中输入文本“app”
el_search_box = driver.find_element(AppiumBy.ID, "com.android.documentsui:id/search_src_text")
el_search_box.send_keys("app")

# 结束会话
driver.quit()

简单优化之后:

# This sample code supports Appium Python client >=2.3.0
# pip install Appium-Python-Client
# Then you can paste this into a file and simply run with Python

from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy

# For W3C actions
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput
import config
#配置信息放到config或者其他文件位置,也是POM的设计方法,增加可维护性
class TestSetting:
    def setup(self):
        self.driver=config.driver
        self.driver.implicitly_wait(3)
    def teardown(self):
        self.driver.quit()

    def test_search_func(self):
        el1 = self.driver.find_element(by=AppiumBy.XPATH,
                                  value="//android.widget.TextView[@resource-id=\"android:id/title\" and @text=\"存储\"]")

        actions = ActionChains(self.driver)
        actions.w3c_actions = ActionBuilder(self.driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
        actions.w3c_actions.pointer_action.move_to_location(356, 889)
        actions.w3c_actions.pointer_action.pointer_down()
        actions.w3c_actions.pointer_action.move_to_location(391, 381)
        actions.w3c_actions.pointer_action.release()
        actions.perform()

        el2 = self.driver.find_element(by=AppiumBy.XPATH,
                                  value="//android.widget.TextView[@resource-id=\"android:id/title\" and @text=\"文档和其他\"]")
        el2.click()
        el3 = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="搜索")
        el3.click()

        el4 = self.driver.find_element(by=AppiumBy.ID, value="com.android.documentsui:id/search_src_text")
        el4.clear()
        el4.send_keys("app")

正式开始appium的内容:

基础概念

  • Android 是通过容器的布局属性来管理子控件的位置关系,布局关系就是把界面上的所有的空间,根据他们的间距的大小,摆放在正确的位置

Android基础知识

Android 七大布局

  • LinerLayout(线性布局)


#

  • RelativeLayout(相对布局)

  • FrameLayout(帧布局)

一般是最外的标签

  • AboluteLayout(绝对布局)

就是绝对位置

  • TableLayout(表格布局)

类似于日历,可以往里添加内容

  • GridLayout(网格布局)

类似于日历,可以往里添加内容

  • ConstraintLayout(约束布局)

绝对布局的升级版

Android 四大组件

  • activity 与用户交互的可视化界面

  • service 实现程序后台运行的解决方案(也就是后台运行的程序,比如正在监听消息的qq)

  • content provider 内容提供者,提供程序所需要的数据

  • broadcast receiver 广播接收器,监听外部事件的到来(比如来电)

常用的控件

  • TextView(文本控件),EditText(可编辑文本控件)

  • Button(按钮),ImageButton(图片按钮),ToggleButton(开关按钮)

  • ImageView(图片控件)

  • CheckBox(复选框控件),RadioButton(单选框控件)

其他注意事项:

Android是有布局这个概念的,布局里会有多个布局或组件

iOS是没有布局这个概念的,使用的是变量之间的相对关系

使用 Appium 测试 iOS 应用需要使用 MacOS 操作系统!!!

启动方式:

我们已经熟悉了第一种:

driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)

设置好options,然后再用webdriver.Remote方法来启动

options.load_capabilities({
    "platformName": "Android",  # 指定操作系统平台
    "appPackage": "com.android.settings",  # 指定要启动的包名
    "appActivity": ".Settings",  # 指定要启动的 Activity
    "deviceName": "127.0.0.1111",  # 指定要连接的设备
    "ensureWebviewsHavePages": True,# 是否等待页面加载完成才进行操作
    "nativeWebScreenshot": True,# 使用原生方法获取截图
    "newCommandTimeout": 3600,# 设置超时时间,3600(秒),超时后将自动结束会话
    "connectHardwareKeyboard": True	#是否模拟键盘硬件连接 
})

# 启动远程 WebDriver 连接
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)

第二种启动是热启动:

self.driver.launch_app()

这个启动是软件进入后台,可以将它唤醒。

数据清理:

self.driver.clear()

元素定位

三要素

  • 定位、交互、断言

四大简单定位方式:

id定位

  • 原生元素的标识符,Android 系统对应的属性名为resource-id,iOS 为name

accessibilty_id定位

  • 标识唯一一个ui元素,对应的属性名为accessibility-id,对于Android系统的页面元素,对应的属性名为content-desc

xpath定位

  • 不推荐,没有其他标识再用

    • 存在性能问题

      • 使用 xpath 表达式查找页面所对应的 xml 的路径(不推荐,存在性能问题)

      • 页面复杂的情况查寻速度会慢

classname 定位

  • 不推荐

    • 对于 iOS 系统,它的 class 属性对应的属性值会以XCUIElementType开头,对于 Android 系统,它对应的是 UIAutomator2 的 class 属性(e.g.: android.widget.TextView)

定位方式进阶:

Image

  • 匹配base 64编码的图像文件进行定位

Android UiAutomator

  • 使用 UI Automator 提供的 API, 尤其是 UiSelector 类来定位元素,在 Appium 中,会发送 Java 代码作为字符串发送到服务器,服务器在应用程序的环境中执行这段代码,并返回一个或多个元素

Android View Tag(Espresso only)

  • 使用view Tag进行定位

Android Data Matcher(Espresso only)

  • 使用Espresso数据匹配定位元素

IOS UIAutomation

  • 在 iOS 应用程序自动化时,可以使用苹果的 instruments 框架查找元素

代码:

driver.find_element(AppiumBy.ID, "ID属性值")
driver.find_element(AppiumBy.XPATH, "xpath表达式")
driver.find_element(AppiumBy.CLASS_NAME, "CLASS属性值")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ACCESSIBILITY_ID表达式")
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, "android uiautomator 表达式")
driver.find_element(AppiumBy.IOS_UIAUTOMATION, "ios uiautomation 表达式")
driver.find_element(AppiumBy.ANDROID_VIEWTAG, "ESPRESSO viewtag 表达式")
driver.find_element(AppiumBy.ANDROID_DATA_MATCHER, "ESPRESSO data matcher 表达式")
driver.find_element(AppiumBy.IMAGE, "IMAGE图片")

元素交互方法

element.click()

element.send_keys("appium")

element.set_value("appium")

element.clear()

element.is_displayed()

返回True或者False

element.is_enable()

返回True或者False

element.is_selected()

返回True或者False

get_attribute(name)

获取属性值

#config.py
from appium import webdriver
from appium.options.common.base import AppiumOptions
options = AppiumOptions()
options.load_capabilities({
    "platformName": "Android",
    "appium:appPackage": "io.appium.android.apis",
    "appium:appActivity": ".ApiDemos",
    "appium:deviceName": "127.0.0.1111",
    "appium:ensureWebviewsHavePages": True,
    "appium:nativeWebScreenshot": True,
    "appium:newCommandTimeout": 3600,
    "appium:connectHardwareKeyboard": True
})
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)

#test_demo.py
class TestDem:	
	def setup(self):
	    self.driver = config.driver
	    self.driver.implicitly_wait(3)
	
	def teardown(self):
	    self.driver.quit()
	
	def test_seeking(self):
	    """
	    1.打开demo.apk
	    setup完成
	    2.点击 Animation 进入下个页面
	    3.查看 run 按钮是否可点击/显示
	    4.查看 滑动条 是否显示、是否可用、是否可点击
	    5.获取 滑动条 长度
	    6.点击 滑动条 中心位置
	
	    :return:
	    """
	    el1 = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Animation")
	    el1.click()
	    el2 = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Seeking")
	    el2.click()
	    el3 = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Run")
	    assert el3.is_enabled() is True
	    assert el3.is_displayed() is True
	    el4 = self.driver.find_element(AppiumBy.ID, "io.appium.android.apis:id/seekBar")
	    assert el4.is_displayed() is True
	    assert el4.is_enabled() is True
	    assert el4.is_selected() is False
	    size = el4.size
	    print(size["width"])
	    location = el4.location
	    x = location.get("x")
	    y = location.get("y")
	    self.driver.tap([(size["width"]/2+x,y)])
	    time.sleep(10)