本文为霍格沃兹测试学院学员学习笔记,进阶学习文末加群。
本系列文章汇总了从 Appium 自动化测试从基础到框架高级实战中,所涉及到的方方面面的知识点精华内容(如下所示),希望对大家快速总结和复习有所帮助。
Appium 自动化测试从基础到框架实战
Appium 基础 1 (环境搭建和简介)
Appium 基础 2 (元素定位和元素常用方法)
Appium 基础 3 (手势操作和 uiautomator 查找元素)
Appium 基础 4 (显式等待)
Appium 基础 5 (toast 和参数化)
Appium 基础 6 (webview)
Appium_ 企业微信练习 (非 PO,增加和删除联系人)
Appium_ 企业微信练习 ( PO–增加联系人)
本文为第二篇,主要讲解 Appium 手势操作、查找元素、显示等待(附实例代码)。
Appium 的触屏操作
滑动小案例
进入雪球应用
再主页从下往上滑动
避免使用坐标(代码用获取屏幕的长宽来解决这个问题)
代码
from time import sleep
from appium import webdriver
from .touch_action import TouchAction
class TestFind():
def setup(self):
self.desire_cap= {
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"",
"appActivity":".",
"noReset":"true",
"unicodeKeyboard":True
}
=("http://127.0.0.1:4723/wd/hub",self.desire_cap)
.implicitly_wait(5)
def test_search(self):
"""
1.进入雪球应用
2.再主页从下往上滑动
3.避免使用坐标
:return:
"""
#由于雪球真的是太慢了,所以睡10秒
sleep(10)
#定义一个TouchAcion对象
aciton=TouchAction()
#获取整个屏幕的右下角的坐标
window_rect=.get_window_rect()
#提取屏幕的最大的宽
width=window_rect["width"]
#提取屏幕的最大的高度
height=window_rect['height']
#x的坐标定义为最大宽的一半,也就是中心的x坐标
x1=int(width/2)
#定义起始的y坐标,在4/5的底部位置
y_start=int(height* 4/5)
#定义终点的y坐标,在1/5顶部的位置,这样就可以模拟从下往上滑动的动作
y_end=int(height* 1/5)
#先press点击初始的坐标,然后按住不放等2秒再move_to到终点坐标,然后再release()释放坐标点,用perform()去执行一系列action操作
(x=x1,y=y_start).wait(2000).move_to(x=x1,y=y_end).release().perform()
#重复两次,看的效果更明显
(x=x1, y=y_start).wait(2000).move_to(x=x1, y=y_end).release().perform()
(x=x1, y=y_start).wait(2000).move_to(x=x1, y=y_end).release().perform()
sleep(3)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
滑动多点解锁
得下载一个叫手势密码锁的 App,百度一下有
进入解锁的页面
设置解锁密码为一个7字
意外发现 Appium 可以指定去不同的初始的 activity,好像也是看应用的
代码
from time import sleep
from appium import webdriver
from import MobileBy as By
from .touch_action import TouchAction
class TestFind():
def setup(self):
self.desire_cap = {
"platformName": "android",
"deviceName": "127.0.0.1:7555",
"appPackage": "",
"appActivity": "",
"noReset": "true"
}
= ("http://127.0.0.1:4723/wd/hub", self.desire_cap)
.implicitly_wait(5)
def test_search(self):
"""
1.进入解锁的页面
2.设置解锁密码为一个7字
3.意外发现appium可以指定去不同的初始的activity,好像也是看应用的
:return:
"""
sleep(3)
#定义一个TouchAcion对象
aciton=TouchAction()
#找到7个坐标点,通过连续的press,wait,move_to,最后释放手势release(),然后perform()执行即可
(x=142,y=190).wait(200).move_to(x=408,y=190).wait(200).move_to(x=678,y=190).wait(200).move_to(x=678,y=464) \
.wait(200).move_to(x=678,y=740).release().perform()
sleep(2)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
UIAutomator 查找元素
优缺点
- 优点
- xpath 定位速度慢
- UIAutomator 是 Android 的工作引擎,速度快
- 滚动查找很方便
- 缺点
- 表达式书写复杂,容易写错 IDE 没有提示
定位方式
-
通过 resource-id 定位
-
通过 classname 定位
-
通过 content-desc 定位
-
通过文本定位
-
组合定位
-
通过父子关系定位
用法
- driver.find_element_by_android_uiautomator(“表达式”)
- 注:外层要用单引号,内层的字符串用双引号,因为本来就是 Java,Java 双引号才表示字符串
- 通过文本定位
- new UiSelector().text(“text文本”)
- 通过 textContains 模糊匹配
- new UiSelector().textContains(“text文本”)
- 通过某个文本开头匹配
- new UiSelector().textStartWith(“text文本”)
- 正则表达式匹配
- new UiSelector().textMatches(“text文本”)
- 组合定位
- 比如 id 与 text 的属性组合:driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“:id/login_account”).text(“我的”)’)
- 父子关系定位:childSelector,先定位到父类,再用 childSelector 来定位子类
- driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“:id/login_account”).childSelector(text(“股票”))’)
- 兄弟定位:fromParent
- driver.find_element_by_android_uiautomator(‘new UiSelector().resourceId(“:id/login_account”).fromParent(text(“股票”))’)
代码
from time import sleep
from appium import webdriver
from .touch_action import TouchAction
class TestFind():
def setup(self):
self.desire_cap= {
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"",
"appActivity":".",
"noReset":"true",
"unicodeKeyboard":True
}
=("http://127.0.0.1:4723/wd/hub",self.desire_cap)
.implicitly_wait(5)
def test_search(self):
"""
1.打开雪球app
2.点击我的,进入到个人信息页面
3.点击登录,进入到登录页面
4.输入用户名,输入密码
5.点击登录
6.弹出手机号输入失败的提示,并assert这个提示对不对
:return:
"""
#雪球太慢了,只能10秒了,懒得用显示等等
sleep(10)
#在首页找到我的元素,然后点击
.find_element_by_android_uiautomator('new UiSelector().text("我的")').click()
#不睡2秒回导致下一个页面的元素刷新太快识别不到
sleep(2)
#识别账号密码登录的元素,然后点击
.find_element_by_android_uiautomator('new UiSelector().text("帐号密码登录")').click()
#不睡2秒回导致下一个页面的元素刷新太快识别不到
sleep(2)
#输入账号名为tongtong
.find_element_by_android_uiautomator('new UiSelector().resourceId(":id/login_account")').send_keys("tongtong")
#输入密码为tongtong
.find_element_by_android_uiautomator('new UiSelector().resourceId(":id/login_password")').send_keys("tongtong")
#不睡2秒回导致下一个页面的元素刷新太快识别不到
sleep(2)
#点击登录按钮
.find_element_by_android_uiautomator('new UiSelector().text("登录")').click()
#不睡2秒回导致下一个页面的元素刷新太快识别不到
sleep(2)
#找到错误提示框,里面有一个确定的元素
login_incorrect=.find_element_by_android_uiautomator('new UiSelector().text("确定")')
#当确定的元素可见,表示登录失败,用例pass
assert login_incorrect.is_displayed()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
滑动元素查找
- 有一些页面有持续滑动的能力,比如微博,没有分页,可以一直滑动,UIAutomator 提供了滑动的很好的方法。
- driver.find_element_by_android_uiautomator(‘new UiScrollable(new UiSelector().’‘scrollable(true).instance(0)).’‘scrollIntoView(new UiSelector().textContains(“病人”).’‘instance(0));’).click()
- 注意:虚拟机和真机不一样,有时候真机的滑动是ok的,有时候虚拟机的不ok
代码
from time import sleep
from appium import webdriver
from .touch_action import TouchAction
class TestFind():
def setup(self):
self.desire_cap= {
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"",
"appActivity":".",
"noReset":"true",
"unicodeKeyboard":True
}
=("http://127.0.0.1:4723/wd/hub",self.desire_cap)
.implicitly_wait(5)
def test_search(self):
"""
0.你的雪球app先关注一个人,然后往下滑,找到一个关键字,用textContains来模糊匹配
1.打开雪球app
2.点击关注,让屏幕往下滑,直到找到病人的模糊匹配的text元素后点击
:return:
"""
#雪球太慢了,只能10秒了,懒得用显示等等
sleep(10)
#点击关注的元素
.find_element_by_android_uiautomator('new UiSelector().text("关注")').click()
#睡4秒,怕跳转页面太快,搜索不到元素
sleep(4)
.find_element_by_android_uiautomator('new UiScrollable(new UiSelector().'
'scrollable(true).instance(0)).'
'scrollIntoView(new UiSelector().textContains("病人").'
'instance(0));').click()
sleep(4)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
强制等待、隐式等待、显示等待
三者的特点
-
强制等待是 sleep,强烈不推荐,设定的时间太固定,如果是模拟器等待3秒,真机可能只需要等待2秒
-
(timeout),贯穿全部元素的等待,只需要设定一次即可,通常是在创建 driver 的时候后的代码运行,是 dom 建立之后的等待;
-
显示等待是在客户端的等待:引用两个包和一个例子
-
from selenium. import WebDriverWait
-
from import expected_conditions
-
WebDriverWait(,10).until(expected_conditions.element_to_be_clickable(locator))
显示等待的简介 -
显示等待与隐式等待相对,显示等待必须在每一个需要等待的元素前面进行声明
-
是针对某个特定的元素设置等待时间,在设置时间内,默认美格一段时间检测一次当前某个元素是否存在
-
如果在规定的时间内找到元素,则直接执行,即找到元素就执行相关操作
-
如果超过设置时间检测不到就抛出异常,默认检测频率为0.5s,默认抛出的异常时NoSuchElementException
-
用到的两个常用类
-
WebDriverWait
-
expected_condition
-为什么要用显示等待,为什么隐式等待无法替代显示等待? -
显示等待可以等待动态加载的 AJax 元素,需要配合 expected_condition 来检查条件
-
一般页面上元素的呈现顺序是
-
首先出现 title;
-
然后是 dom 树的出现,presence 还不完整,dom 树出现就是隐式等待了,但此时的元素可能还没有是可点击的状态,所以只用隐式等待,使用 click 方法,肯定会报错的;
-
CSS 出现:可见 visbility;
-
JS 的出现,JS 特效执行:可点击 clickable;
-
HTML 文档是自上而下加载的
-
JS 文件加载会阻塞 HTML 内容的加载,有些 JS 异步加载的方式来完成 JS 的加载
-
样式表下载完成之后跟之前的样式表一起进行解析,会对之前那的与元素重新渲染
-
presence-visibility-clickabe,元素出现-可见-可点击,是元素的三个性质,当 DOM 树出现时,定位元素可能已经显示出来了,但是可见和可点击的属性可能还没加载出来,这时候元素的一些方法是不可用的,比如 element.click(),要等到 JS 渲染出来以后,元素的 click 属性才可以用
-
对应element.is_displayed()
-
对应element.is_selected()
-
对应element.is_enabled()
JS 的同步加载和异步加载 -
同步加载:同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像)、渲染、代码执行。
-
异步加载:异步加载又叫非阻塞,浏览器在下载执行 JS 同时,还会继续进行后续页面的处理。
WebDriverWait 用法 -
WebDriverWait(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None)
-
driver:浏览器驱动
-
timeout:超时时间,单位秒
-
poll_frequency:检查的间隔步长,默认是0.5s
-
ignored_exceptions:超时最后的抛出的异常,默认是NoSuchElementException
-
通常我们只会用到 driver 和 timeout
-
WebDriverWait().unti(self, method, message=’’) or until_not()的方法:
-
method:在等待期间,每个一段时间(init中的poll_frequency)调用这个传入的方法,直到返回值不是 False
-
message:如果超时,抛出 TimeoutException,将 message 传入异常
-
until not 是当某个元素小时或什么条件则继续执行,参数也相同
expected_conditions 类
-
Appium 直接帮我们封装好了类,只需要传参数即可,比如我们使用的是 click(),只需要判断这个元素是否可点击属性才继续点击
-
-用法:expected_conditions.element_to_be_clickable(locator),其中 locator 就是:(, “:id/tv_search”)
常用的几个如下:
-
expected_conditions.element_to_be_clickable:元素是否可点击
-
expected_conditions.presence_of_element_located:元素是否被加到 Dom 树里面
-
expected_conditions.visibility_of_element_located:元素是否可见
Lambda 获取元素
#可以获取到元素
element = WebDriverWait(, 10).until(lambda x: x.find_element(,'//*[@text="我的"]'))
#这里找到元素后,不用等待,实测证明过了
()
- 1
- 2
- 3
- 4
实例代码
from appium import webdriver
from import expected_conditions
from import WebDriverWait
from import MobileBy as By
class TestFind():
def setup(self):
self.desire_cap= {
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"",
"appActivity":".",
"noReset":"true",
"unicodeKeyboard":True
}
=("http://127.0.0.1:4723/wd/hub",self.desire_cap)
.implicitly_wait(5)
def test_search(self):
"""
1.打开雪球app
2.点击我的,进入到个人信息页面
3.点击登录,进入到登录页面
4.输入用户名,输入密码
5.点击登录
6.弹出手机号输入失败的提示,并assert这个提示对不对
:return:
"""
#方法不是对应text的,千万不要用
#.find_element(,"我的")
#用显示等待,element_to_be_clickable(locator),里面的locator记得用元祖
WebDriverWait(, 15).until(expected_conditions.element_to_be_clickable((,'//*[@text="我的"]')))
#在首页找到我的元素,然后点击
.find_element(,'//*[@text="我的"]').click()
'''
lambda返回元素
element = WebDriverWait(, 10).until(lambda x: x.find_element(,'//*[@text="我的"]'))
这里找到元素后,不用等待,实测证明过了
()
'''
#显示等待找到账号密码登录的元素
WebDriverWait(, 15).until(expected_conditions.element_to_be_clickable((,'//*[@text="帐号密码登录"]')))
#识别账号密码登录的元素,然后点击
.find_element_by_android_uiautomator('new UiSelector().text("帐号密码登录")').click()
#显示等待找到账号的元素
WebDriverWait(, 15).until(
expected_conditions.element_to_be_clickable((, '//*[@resource-]')))
#输入账号名为tongtong
.find_element_by_android_uiautomator('new UiSelector().resourceId(":id/login_account")').send_keys("tongtong")
#输入密码为tongtong
.find_element_by_android_uiautomator('new UiSelector().resourceId(":id/login_password")').send_keys("tongtong")
#显示等待找到登录的按钮
WebDriverWait(, 15).until(
expected_conditions.element_to_be_clickable((, '//*[@text="登录"]')))
#点击登录按钮
.find_element_by_android_uiautomator('new UiSelector().text("登录")').click()
#显示等待找到确定的元素
WebDriverWait(, 15).until(
expected_conditions.element_to_be_clickable((, '//*[@text="确定"]')))
#找到错误提示框,里面有一个确定的元素
login_incorrect=.find_element_by_android_uiautomator('new UiSelector().text("确定")')
#当确定的元素可见,表示登录失败,用例pass
assert login_incorrect.is_displayed()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
更多内容,我们在后续文章分享。
(文章来源于霍格沃兹测试学院)