天天看点

移动端UI自动化之appium的使用(二)

一、appium属性获取与断言

1.1、get_attrbute原理分析

官网:https://appium.io/docs/en/commands/element/attributes/attribute/

所有可以获取的属性

CHECKABLE(new String[]{"checkable"}),
CHECKED(new String[]{"checked"}),
CLASS(new String[]{"class", "className"}),
CLICKABLE(new String[]{"clickable"}),
CONTENT_DESC(new String[]{"content-desc", "contentDescription"}),
ENABLED(new String[]{"enabled"}),
FOCUSABLE(new String[]{"focusable"}),
FOCUSED(new String[]{"focused"}),
LONG_CLICKABLE(new String[]{"long-clickable", "longClickable"}),
PACKAGE(new String[]{"package"}),
PASSWORD(new String[]{"password"}),
RESOURCE_ID(new String[]{"resource-id", "resourceId"}),
SCROLLABLE(new String[]{"scrollable"}),
SELECTION_START(new String[]{"selection-start"}),
SELECTION_END(new String[]{"selection-end"}),
SELECTED(new String[]{"selected"}),
TEXT(new String[]{"text", "name"}),
// The main difference of this attribute from the preceding one is that
// it does not replace null values with empty strings
ORIGINAL_TEXT(new String[]{"original-text"}, false, false),
BOUNDS(new String[]{"bounds"}),
INDEX(new String[]{"index"}, false, true),
DISPLAYED(new String[]{"displayed"}),
CONTENT_SIZE(new String[]{"contentSize"}, true, false);
           
def test_get_attr(self):
	"""获取属性"""
	search_ele = self.driver.find_element_by_id('com.xueqiu.android:id/tv_search')
	print(search_ele.get_attribute('content-desc'))
	print(search_ele.get_attribute('text'))
	print(search_ele.get_attribute('resource-id'))
	print(search_ele.get_attribute('clickable'))
	print(search_ele.get_attribute('enabled'))
	print(search_ele.get_attribute('bounds'))
           

1.2、断言

1.2.1、普通断言 assert

如果存在两个assert,则第一个assert断言失败,就不会执行第二个断言。

1.2.2、Hamcrest断言

github地址:https://github.com/hamcrest/PyHamcrest

安装:

pip install PyHamcrest

hamcrest 框架介绍

  • Hamcrest是一个为了测试为目的,能组合成灵活表达式的匹配器类库。用于编写断言的框架,使用这个框架编写断言,提高可读性及开发测试的效率。
  • Hamcrest提供了大量被称为“匹配器”的方法。每个匹配器都设计用于执行特定的比较操作。
  • Hamcrest的可扩展性强,让你能够创建自定义的匹配器。
def test_hamcrest(self):
	"""hamcrest断言"""
	assert_that(10, equal_to(10))
	assert_that(10.0, close_to(10.0, 0.25))
           

二、appium参数化用例

雪球app下载,提取码:cd8v

import pytest
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from hamcrest import *


class TestParam:

	def setup(self):
		des_caps = {
			'platformName': 'Android',
			'platformVersion': '7.1.2',
			'deviceName': '127.0.0.1:62001',
			'appPackage': 'com.xueqiu.android',
			'appActivity': '.common.MainActivity',
			'unicodeKeyboard': True,
			'resetKeyboard': True,
			'noReset': True,
			'dontStopAppOnReset': True,
			'skipDeviceInitialization': True
		}

		self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', des_caps)
		self.driver.implicitly_wait(10)

	def teardown(self):
		# 点击取消
		self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/action_close').click()
		self.driver.quit()

	@pytest.mark.parametrize('name, code, expect', [
		('阿里巴巴', 'BABA', 230),
		('小米集团', '01810', 27)
	])
	def test_param(self, name, code, expect):
		"""
		参数化
		1、打开 雪球 app
		2、点击搜索输入框
		3、向搜索框里面输入搜索词
		4、在搜索结果里面选择第一个搜索结果,然后进行点击
		5、获取股价,并判断这只股价的价格
		:return:
		"""
		self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/tv_search').click()
		self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/search_input_text').send_keys(name)
		self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/name').click()
		current_price = self.driver.find_element(MobileBy.XPATH,
		                                         f'//*[@text="{code}"]/../../..'
		                                         f'//*[@resource-id="com.xueqiu.android:id/current_price"]').text
		current_price = float(current_price)
		print(current_price)
		assert_that(current_price, close_to(expect, 10))
           

三、Android webview测试

appium支持 Native App(原生)、Hybrid App(混合)、Web App(H5)三种应用。

上下文
原生:NATIVE_APP
H5: WEBVIEW_xxx
           

3.1、环境准备

手机端:被测浏览器(不可以是第三方浏览器,一般使用自带的浏览器),IOS:Safari、Chrome,Android:Chromium,Browser

PC端:安装Chrome浏览器(或chromium),并且能登录https://www.google.com/

下载对应手机浏览器对应的driver版本:

  • 国内镜像地址:https://npm.taobao.org/mirrors/chromedriver/
  • appium github:https://github.com/appium/appium/blob/master/docs/cn/writing-running-appium/web/chromedriver.md

获取手机默认浏览器的版本:

mac/linux 只需要把findstr改成grep即可

C:\Users\18367>adb shell pm list package |findstr webview  <== 获取webview的包名
package:com.google.android.webview

C:\Users\18367>adb shell pm dump com.google.android.webview |findstr version  <== 默认浏览器的版本
      versionCode=377014315 minSdk=0 targetSdk=29
      versionName=75.0.3770.143  <== 默认浏览器的版本
           

下载最接近的驱动版本,这个版本是支持75版本的。

移动端UI自动化之appium的使用(二)

启动参数:

appium默认有个驱动版本,但是和手机或者模拟器版本不匹配,需要通过chromedriverExecutable参数指定对应的驱动。

'browserName'='Browser' 或者 'browserName'='Chrome'
'chromedriverExecutable'='指定驱动路径'
           

3.2、元素定位

使用谷歌浏览器需要科学上网什么的,比较麻烦,建议可以使用最新版本的edge浏览器。

edge浏览器中输入

edge://inspect

,点击对应的网页的inspect,进入调试模式,查找元素定位。

移动端UI自动化之appium的使用(二)
移动端UI自动化之appium的使用(二)
from os.path import dirname, join

from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from time import sleep

from appium import webdriver


class TestBrowser:

    def setup(self):
        des_caps = {
            'platformName': 'Android',
            'platformVersion': '7.1.2',
            'deviceName': '127.0.0.1:62001',
            'browserName': 'Browser',
            'chromedriverExecutable': join(dirname(__file__), 'driver', 'chromedriver.exe'),
            'noReset': True,
        }
        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', des_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.quit()

    def test_browser(self):
        self.driver.get('https://m.baidu.com/')
        sleep(5)
        self.driver.find_element(MobileBy.ID, 'index-kw').click()
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((MobileBy.ID, 'index-kw'))).send_keys('appium')
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((MobileBy.ID, 'index-bn'))).click()
        sleep(5)
           

3.3、Android混合页面测试

混合应用就是在原生的页面中嵌入了H5页面。

如何判断页面是webview

  1. 断网查看
  2. 看加载条
  3. 看顶部是否有关闭按钮
  4. 下拉刷新,页面是否刷新
  5. 下拉刷新的时候是否有网页提供方
  6. 用工具查看

webview

  • android系统提供能显示网页的系统控件(特殊的view)
  • <Android 4.4 WebView底层实现WebKit内核
  • =Android 4.4 Google采用chromium作为系统WebView底层支持,API没变,支持H5、CSS3、js

前提条件

PC

  1. 浏览器能访问:https://www.google.com/,edge浏览器也可以定位原生
  2. chromedriver下载对应的版本

手机端:应用app需要打开webview开关,android 6.0 不开启webview也可以获取html结构

代码

  1. appPackage,appActivity
  2. 启动参数里面添加:chromedriverExecutable参数,指定驱动的版本的路径

通过appium日志查看Chromedriver日志,查看手机内置浏览器的chrome版本。

移动端UI自动化之appium的使用(二)

示例

from os.path import join, dirname
from time import sleep

from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestWebView:

	def setup(self):
		des_caps = {
			'platformName': 'Android',
			'platformVersion': '7.1.2',
			'deviceName': '127.0.0.1:62001',
			'udid': '127.0.0.1:62001',
			'appPackage': 'com.xueqiu.android',
			'appActivity': '.common.MainActivity',
			'chromedriverExecutable': join(dirname(__file__), 'driver', 'chromedriver224.exe'),
			'unicodeKeyboard': True,
			'resetKeyboard': True,
			'noReset': True,
			'dontStopAppOnReset': True,
			'skipDeviceInitialization': True
		}

		self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', des_caps)
		self.driver.implicitly_wait(10)

	def teardown(self):
		self.driver.quit()

	def test_webview(self):
		"""
		:return:
		"""
		self.driver.find_element(MobileBy.XPATH, '//*[@text="交易"]').click()
		# 切换上下文
		self.driver.switch_to.context(self.driver.contexts[-1])
		print(self.driver.page_source)
		# 进入港美股开户页面
		WebDriverWait(self.driver, 10).until(
			EC.element_to_be_clickable((MobileBy.XPATH, '//*[@text()="港美股开户"]'))).click()
		# 切换句柄,每打开一个网页,就会有一个新的句柄
		self.driver.switch_to.window(self.driver.window_handles[-1])
	
		WebDriverWait(self.driver, 10).until(
			EC.element_to_be_clickable((MobileBy.XPATH, '//*[text()="请输入手机号"]'))).send_keys('13000000000')
		self.driver.find_element(MobileBy.XPATH, '//*[text()="请输入验证码"').send_keys('1234')
		self.driver.find_element(MobileBy.XPATH, '//*[text()="立即开户"').send_keys('1234')
           

常遇到的坑

设备

  • android模拟器6.0默认支持webview操作(mumu不可以,genimotion和sdk自带的emulator可以)
  • 其它模拟器和物理机需要打开app内开关(webview调试开关)

PC浏览器定位元素

  • chrome浏览器-Chrome 62 才可以更好的看见webview的内部,其它版本都有bug
  • 也换成chromeium浏览器可以避免很多坑,展示效果和速度都比chrome要快

代码

  • 有的设备可以直接使用find_element_by_accessibility_id(),不同的设备渲染的页面不同,兼容性不适合
  • switch_to.context():切换上下文
  • switch_to.window():切换句柄

多版本的驱动处理

添加启动参数:chromedriverExecutableDir,会自动找匹配的驱动版本,前提是文件夹下有这个匹配的驱动。

添加json配置文件,指定版本映射,通过chromedriverChromeMappingFile 参数指定对应json文件,添加到启动参数中

driver.json文件

{
  "2.42": "63.0.3239",
  "2.41": "62.0.3202"
}
           

先通过chromedriverChromeMappingFile获取对应版本的映射驱动,再在chromedriverExecutableDir目录中找对应的驱动。

四、微信小程序测试

4.1、小程序的运行环境

移动端UI自动化之appium的使用(二)

微信小程序运行在多种平台上:iOS(iPhone/iPad) 微信客户端、Android 微信客户端、PC 微信客户端、Mac 微信客户端和用于调试的微信开发者工具。

各种平台脚本执行环境以及用于渲染非原生组件的环境是各不相同的:

  • 在iOS上,小程序逻辑层的javascript代码运行在JavaScriptCore中,视图层是由WKWebView来渲染的,环境有iOS12、iOS13等。
  • 在Android上,小程序逻辑层的javascript代码运行在V8中,视图层是由自研XWeb引擎基于Mobile Chrome 内核来渲染的。
  • 在开发工具上,小程序逻辑层的javascript代码是运行在NWJS中,视图层是由Chrome WebView来渲染的。

4.2、平台差异

尽管各运行环境是十分相似的,但是还是有些许区别:

  • JavaScript 语法和 API 支持不一致:语法上开发者可以通过开启 ES6 转 ES5 的功能来规避;此为,小程序基础库内置了必要的Polyfill,来弥补API的差异。
  • WXSS渲染表现不一致:尽管可以通过开启样式补偿来规避大部分的问题,还是建议开发者需要在iOS和Android上分别检查小程序的真实表现。

4.3、微信调试开关

微信每个版本都很"善变"

  • 可手工开启调试开关
  • 默认关闭了调试开关而且无法开启
  • 默认开启调试开关

手工开启办法

  • 文件传输助手发送
  • debugtbs.qq.com
  • debugx5.qq.com
  • 打开微信小程序调试开关

4.4、微信小程序自动化测试

关键步骤

  • 设置chromedriver正确版本
  • 设置chrome option 传递给 chromedriver
  • 使用 adb proxy 解决 fix chromedriver的bug

为什么有些手机无法自动化微信小程序

  • 低版本的chromedriver在高版本的手机上有bug
  • chromedriver与微信定制的chrome内核实现上有问题

解决方案:fix it

  • chromedriver没有使用adb命令,而是使用了adb协议
  • 参考adb proxy源代码
mitmdump -p 5038 --rawtcp --mode reverse:http://localhost:5037/ -s adb_proxy.py 
           

微信小程序自动化测试辅助工具adb proxy

from mitmproxy.utils import strutils
from mitmproxy import ctx
from mitmproxy import tcp


def tcp_message(flow: tcp.TCPFlow):
    message = flow.messages[-1]
    old_content = message.content
    #message.content = old_content.replace(b"foo", b"bar")
    # 核定代码,替换符合微信的webview
    message.content = old_content.replace(b"@webview_devtools_remote_", b"@.*.*.*._devtools_remote_")

    ctx.log.info(
        "[tcp_message{}] from {} to {}:\n{}".format(
            " (modified)" if message.content != old_content else "",
            "client" if message.from_client else "server",
            "server" if message.from_client else "client",
            strutils.bytes_to_escaped_str(message.content))
    )
           

五、Capability高级用法

更多详细功能参考官网中文文档

newCommandTimeout:用于客户端在退出或者结束 session 之前,Appium 等待客户端发送一条新命令所花费的时间(秒为单位),默认是60秒

udid:连接真机的唯一设备号,支持多设备运行。

autoGrantPermissions:让Appium自动确定您的应用需要哪些权限,并在安装时将其授予应用。默认设置为 false

测试策略相关

  • noReset:在当前 session 下不会重置应用的状态。默认值为 false
  • fullReset:(iOS)删除所有的模拟器文件夹。(Android) 要清除 app 里的数据,请将应用卸载才能达到重置应用的效果。在 Android, 在 session 完成之后也会将应用卸载掉。默认值为 false
  • dontStopAppOnReset:在使用 adb 启动应用之前,不要终止被测应用的进程。如果被测应用是被其他钩子(anchor)应用所创建的,设置该参数为 false 后,就允许钩子(anchor)应用的进程在使用 adb 启动被测应用期间仍然存在。换而言之,设置 dontStopAppOnReset 为 true 后,我们在 adb shell am start 的调用中不需要包含 -S标识(flag)。忽略该 capability 或 设置为 false 的话,就需要包含 -S 标识(flag)。默认值为 false

性能相关

  • skipServerInstallation:跳过 uiautomator2 server的安装,非首次启动使用
  • skipDeviceInitialization:跳过设备初始化
  • skipUnlock:
  • skipLogcatCapture:跳过日志的获取
  • systemPort:
  • ignoreUnimportantViews:跳过不重要的组件获取
  • -relaxed-security:启动的时候设置

继续阅读