首页 > 其他分享 >移动端测试 - appium usage

移动端测试 - appium usage

时间:2022-12-26 17:09:01浏览次数:59  
标签:appium driver element width 测试 usage height com id

appium的操作跟selenium操作差不多,这里简要聊聊。

元素定位

 

通过id定位

 

我们通过Python代码打开APP(此时appium是启动并监听4723端口),来到首页,然后使用uiautomatorviewer工具获取(这时要结束appium连接)页面结构。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"

}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

真巧!我们发现了​​跳过​​​按钮的id是​​com.jd.app.reader:id/skip_tv​​。那么我们就可以直接通过id定位,然后点击就完了。

移动端测试 - appium usage_ico

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"

}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
button = driver.find_element_by_id('com.jd.app.reader:id/skip_tv')
print(button)
button.click()

如果你学过selenium,你会发现,selenium和appium的用法很像。所以,代码就无需多言了,根据标签定位,然后点击。

通过class属性

 

class属性定位就是通过uiautomatorviewer工具查看到的class属性。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

res = driver.find_elements_by_class_name('android.widget.TextView')
print(len(res)) # 1
res[0].click()

由于class的属性的特性,一般都是有多个标签引用,所以,我们不能上来就用​​driver.find_element_by_class_name('android.widget.TextView')​​这种形式,当然这种是默认从多个中选择第一个,但我们不能保证我们想要的标签它正好处于第一个。

所以,我们应该拿到class属性,如上例中,先找多个,看看它这个页面中到底找到了多少个(列表的形式),然后根据其下标定位即可。

上例中恰好页面中只有一个标签,但由于是列表的形式,所以我们取0。

其它形式的定位就不多扯,可以参考selenium。

层级定位

 

所谓的层级定位,有相对定位的意思,也就是根据儿子找他爹,找他爹的爹;根据父亲找儿子;各种找就完了。

这是一种定位思想。使用不同的定位方法相结合,能轻松解决问题。

移动端测试 - appium usage_ico_02

如上图,这个页面中有很多的class属性为​​android.widget.RelativeLayout​​​的标签,也有很多class属性为​​android.widget.ImageView​​​的标签。但​​RelativeLayout​​标签恰好有id,那么我们可以根据这个id定位到其子标签。说白了就是先找个好定位的点,这就进一步缩小了我们的目标,然后在这个范围内,再找最终的目标。一步一步缩小范围。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()

# 直接在整个页面中找 android.widget.ImageView, 发现9个
res = driver.find_elements_by_class_name('android.widget.ImageView')
print(len(res)) # 9
# 换个套路,先缩小一步范围
res1 = driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookshelf')
# 在这个范围内,再去找 android.widget.ImageView 找到1个
res2 = res1.find_elements_by_class_name('android.widget.ImageView')
print(len(res2)) # 1
# 只有一个就好办了,轻松拿下
res2[0].click()

xpath定位

 

首先,你可能说,咱们appium用的好好地,怎么突然用上了UIautomatorviewer了?因为啊,appium更擅长于xpath定位!其他的UIautomatorviewer更胜一筹。所以,相结合着来使用更好。

基本属性定位

 

说起xpath,满满都是泪,记性不好的我,压根记不住那些规则和符号(这难道就是我正则不好的原因么,手动滑稽)。

在基本属性定位这里,我们有几种定位方式。

举的例子还是以京东阅读APP的跳过起始页广告为例。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
time.sleep(3)

不变的代码先摘出来。

通过text文本定位

//*[@text=“文本”]

driver.find_element_by_xpath('//*[@text="跳过"]').click()

通过id定位

//*[@resource-id='id属性']

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/skip_tv"]').click()

通过class定位

如果class是唯一的话:

//class属性

driver.find_element_by_xpath('//android.widget.TextView').click()

如果是多个:

//*[@class='class属性']

driver.find_element_by_xpath('//*[@class="android.widget.TextView"]').click()

层级定位

 

通过爹爹找儿子

移动端测试 - appium usage_ico_03

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_layout"]/android.widget.RelativeLayout').click()
# 如果是多个,默认找第一个

注意:如果父节点下有多个子节点的话,可以通过数组下标来确定子节点的位置,xpath是从1开始数的

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_layout"]/android.widget.RelativeLayout[3]').click()

通过儿子找爹爹

移动端测试 - appium usage_android_04

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_bookshelf_icon"]/..').click()

再来看个parent语法的:

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_bookshelf_icon"]/parent::android.widget.RelativeLayout[1]').click()

注意,通过子节点找父节点的,parent后都使用class属性。

android_uiautomator定位

 

最后再来看一个跟uiautomatorviewer不一样的定位方式——android_uiautomator定位。

android_uiautomator提供的常用方法如下:

  • 通过text文本定位语法:​​new UiSelector().text("text文本")​
  • 模糊匹配:​​new UiSelector().textContains("包含text文本")​​,一般应用于文本较长中。
  • 匹配以指定字符开头:​​new UiSelector().textStartsWith("以text文本开头")​
  • 正则匹配:​​new UiSelector().textMatches("正则表达式")​
  • resourceId匹配:​​new UiSelector().resourceId("id")​​,这个鬼东西其实跟id一样,但不保证唯一性!
  • class定位:​​new UiSelector().className("className")​​,页面上的class属性一般不唯一,多半用在复数定位时候。比如通过class属性定位'排行'这个按钮下标就是2。
import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()
time.sleep(3)

# 通过text定位
driver.find_element_by_android_uiautomator('new UiSelector().text("书架")').click()
time.sleep(2)

# 通过 resourceId 定位
driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.jd.app.reader:id/main_tab_bookcity_txt")').click()

在输入时屏蔽键盘

 

很明显我们要考虑一个问题,我们遇到输入框时,如何往里面输入值?难道真的要模拟拼音输入?那岂不是要疯?

那你可能会说,我们有send_keys呀!果然,英雄出少年,你说对了,我们有send_keys解决问题。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 点击跳过首页广告
driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()
time.sleep(1)
# 点击顶部的输入框
driver.find_element_by_id('com.jd.app.reader:id/mSearchLayout').send_keys()
time.sleep(1)
# 在新的搜索页面输入值
driver.find_element_by_id('com.jd.app.reader:id/mSearchKeyInput').send_keys('上古卷轴')
time.sleep(1)
# 有了值,就可以点击搜索按钮进行搜索
driver.find_element_by_id('com.jd.app.reader:id/mSearchBtn').click()

如果你运行了上述代码,会发现,除了无法输入值之外,其他都好使!

那原因就是,我们需要屏蔽键盘,然后将我们想要输入的值send进去就OK啦,这用到两个参数:

  • unicodeKeyboard:使用Unicode编码方式发送字符串。
  • resetKeyboard:屏蔽键盘。

将这两个值放到请求的字典中即可。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 点击跳过首页广告
driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()
time.sleep(1)
# 点击顶部的输入框
driver.find_element_by_id('com.jd.app.reader:id/mSearchLayout').send_keys()
time.sleep(1)
# 在新的搜索页面输入值
driver.find_element_by_id('com.jd.app.reader:id/mSearchKeyInput').send_keys('上古卷轴')
time.sleep(1)
# 有了值,就可以点击搜索按钮进行搜索
driver.find_element_by_id('com.jd.app.reader:id/mSearchBtn').click()

这么着是不是就好使了。

等待机制

 

要想在appium中使用等待机制怎么办?好吧,我们借助selenium提供的等待机制来完成显式等待。隐式等待就是用appium自带的吧。

​https://www.cnblogs.com/Neeo/articles/11005164.html​

from appium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()

# 隐式等待
driver.implicitly_wait(10)

driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookshelf').click()

# 显式等待,直到找到了这个标签
WebDriverWait(driver, 10).until(driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookcity'))
driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookcity').click()

截屏

 

from appium import webdriver
desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

try:
# 根据一个不存在的 id 定位
driver.find_element_by_id("xxxxxxxxx").click()
except Exception as e:
print(e) # Message: An element could not be located on the page using the given search parameters.
driver.save_screenshot('./error.png')

这玩意儿跟selenium的截图功能一样,没啥好聊的了。

滑动:driver.swipe

 

首先要知道,手机屏幕的X、Y坐标定位一个像素点。并且原点是从左上角开始的。

移动端测试 - appium usage_android_05

来看相关参数:

def swipe(self, start_x, start_y, end_x, end_y, duration=None):
"""
从一个点滑动到另一个点。

Args:
start_x (int): 开始滑动的x坐标
start_y (int): 开始滑动的y坐标
end_x (int): 结束点x坐标
end_y (int): 结束点y坐标
duration (:obj:`int`, optional): 持续时间,单位毫秒

Usage:
driver.swipe(100, 100, 100, 400)

Returns:
`appium.webdriver.webelement.WebElement`
"""

duration是滑动屏幕持续的时间,时间越短速度越快。默认为None可不填,一般设置500-1000毫秒比较合适。

获取屏幕大小

由于每个手机屏幕的分辨率不一样,所以同一个元素在不同手机上的坐标也是不一样的,滑动的时候坐标不能写死了。可以先获取屏幕的宽和高,再通过比例去计算。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"
}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
print(driver.get_window_size()) # {'width': 720, 'height': 1280}
print(driver.get_window_size()['width']) # 720
print(driver.get_window_size()['height']) # 1280

封装滑动方法

参数分别是:

  • 驱动driver。
  • 滑动时间。
  • 滑动次数。
import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()

def swipeUp(driver, t=500, n=1):
'''向上滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.5 # x坐标
y1 = l['height'] * 0.75 # 起始y坐标
y2 = l['height'] * 0.25 # 终点y坐标
for i in range(n):
driver.swipe(x1, y1, x1, y2, t)


def swipeDown(driver, t=500, n=1):
'''向下滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.5 # x坐标
y1 = l['height'] * 0.25 # 起始y坐标
y2 = l['height'] * 0.75 # 终点y坐标
for i in range(n):
driver.swipe(x1, y1, x1, y2, t)


def swipLeft(driver, t=500, n=1):
'''向左滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.75
y1 = l['height'] * 0.5
x2 = l['width'] * 0.25
for i in range(n):
driver.swipe(x1, y1, x2, y1, t)


def swipRight(driver, t=500, n=1):
'''向右滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.25
y1 = l['height'] * 0.5
x2 = l['width'] * 0.75
for i in range(n):
driver.swipe(x1, y1, x2, y1, t)

if __name__ == '__main__':
time.sleep(4)
swipLeft(driver, n=2)
time.sleep(4)
swipRight(driver, n=2)
time.sleep(4)
swipeUp(driver, n=2)
time.sleep(4)
swipeDown(driver, n=2)

触摸操作:TouchAction

 

源码:​​Lib\site-packages\appium\webdriver\common\touch_action.py​

class TouchAction(object):
def __init__(self, driver=None):
self._driver = driver
self._actions = []

def tap(self, element=None, x=None, y=None, count=1):
模拟手指触摸屏

def press(self, el=None, x=None, y=None):
短按:模拟手指按住一个元素,或者坐标

def long_press(self, el=None, x=None, y=None, duration=1000):
长按:模拟按住一个元素,或者坐标

def wait(self, ms=0):
按住元素后的等待时间

def move_to(self, el=None, x=None, y=None):
移动手指到另外一个元素,或者坐标,注意这里坐标不是绝对坐标,是偏移量

def release(self):
释放手指

def perform(self):
执行前面的动作

TouchAction里面有这几个动作:

  • 触摸 (tap)
  • 短按 (press)
  • 长按 (long_press)
  • 等待 (wait)
  • 移动到 (moveTo)
  • 释放 (release)
  • 执行 (perform)

九宫格示例:点击指定图标

这里用到了​​jiugongge.apk​​了,你需要将它安装到模拟器中去。

先能连上这个apk。

import time
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.duome.locus",
"appActivity": "com.jiugongge.java.LoginActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

解决问题的思路是,先获取元素坐标位置,再获取元素大小,然后切割图片,分别计算出每个点的坐标。

但观察上图,九宫格上下边距距离较大,我们为了方便,手动各添加一排假图标(黄色)。我们要拿到整个屏幕的大小:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

有了宽和高,我们就来定位一下元素1的位置。

元素1的坐标:

  • x轴,将x轴分6份取第1份,也就是​​x=width / 6 * 1​
  • y轴,将y轴分9份取第3份。也就是​​y=height / 10 * 3​

也就是说坐标1,它位于x轴的第1份,y轴的第3份。

经这么分割,我们就可以尝试点击它了,这里使用短按方法:

width, height = element.size['width'] / 6 * 1, element.size['height'] / 9 * 3
TouchAction(driver).press(x=width, y=height).perform()

九宫格示例:按照从小到大的顺序点击所有图标

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y

# 第1个图标
width, height = get_coordinate(1, 3)
TouchAction(driver).press(None, x=width, y=height).perform()
# 第2个图标
time.sleep(1)
width, height = get_coordinate(3, 3)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第3个图标
time.sleep(1)
width, height = get_coordinate(5, 3)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第4个图标
time.sleep(1)
width, height = get_coordinate(1, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第5个图标
time.sleep(1)
width, height = get_coordinate(3, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第6个图标
time.sleep(1)
width, height = get_coordinate(5, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第7个图标
time.sleep(1)
width, height = get_coordinate(1, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第8个图标
time.sleep(1)
width, height = get_coordinate(3, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第9个图标
time.sleep(1)
width, height = get_coordinate(5, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

这个根据之前的图,不难计算它的位置。但是代码有些冗余啊.....

如果你观察细致的话,整个点击过程是有一定的规律的,所以,我们可以做些优化:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y
for y in range(3, 8, 2): # 3 5 7
for x in range(1, 6, 2): # 1 3 5
width, height = get_coordinate(x, y)
TouchAction(driver).press(None, x=width, y=height).wait(500).perform()

这个示例也可以使用tap来完成:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y

for y in range(3, 8, 2): # 3 5 7
for x in range(1, 6, 2): # 1 3 5
width, height = get_coordinate(x, y)
driver.tap([(width, height)], 500)

tap是模拟手指点击,一般页面上元素 的语法有两个参数,第一个是positions,是list类型最多五个点,duration是持续时间,单位毫秒。

def tap(self, positions, duration=None):
"""
模拟手指点击(最多5个手指),保持一定的时间
Args:
positions (:obj:`list` of :obj:`tuple`): list类型,里面对象是元组,最多五个。如:[(100, 20), (100, 60)]
duration (:obj:`int`, optional): 持续时间,单位毫秒,如:500
Usage:
driver.tap([(100, 20), (100, 60), (100, 100)], 500)
Returns:
`appium.webdriver.webelement.WebElement`
"""

他是有弊端的:通过坐标定位是元素定位的下下下策,实在没办法才用这个,另外如果换了手机分辨率,这个坐标就不能写死了,得算出所在屏幕的比例。

九宫格示例:Z形滑动

我们能点击了,是不是也就是能滑动了?尝试一下!

from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.duome.locus",
"appActivity": "com.jiugongge.java.LoginActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}


def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y


def gen():
""" 生成每一个图标的坐标位置 """
for y in range(3, 8, 2):
for x in range(1, 6, 2):
yield get_coordinate(x, y)

def main():
# 首先拿到生成器
generator = gen()
# 先拿到第一个点的坐标位置
width, height = next(generator)
# 有了第一个点的width和height,就按住不松手
obj = TouchAction(driver)
obj.press(None, width, height).wait(500).perform()
# 然后循环移动到后续的每个点
for width, height in generator:
obj.move_to(None, width, height).wait(500).perform()
# 最后再释放即可
obj.release()

if __name__ == '__main__':
main()

效果如下:

移动端测试 - appium usage_ico_06

多点触控:MultiAction

 

多点触摸对象是触摸动作的集合。
多点触控手势只有两种方法,即添加和执行。
add用于添加另一个触摸操作到多点触摸。
当perform执行被调用时,添加到多点触摸的所有触摸动作都被发送到Appium,并执行,就像它们同时发生一样。appium首先执行所有触摸动作的第一个事件,然后执行第二个,等等。

来个百度地图的示例,首先你要安装​​baiduditu_948.apk​​:

移动端测试 - appium usage_ico_07

思路就是主要就是两个点(A, D),然后他们各自(同时)滑动到(B,C),只是让这两件事看起来是同时一样。

下面的示例演示这一过程。

为了简单,我们让x保持不变,改变y的坐标达到这一目的。

import time
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
from appium.webdriver.common.multi_action import MultiAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.baidu.BaiduMap",
"appActivity": "com.baidu.baidumaps.WelcomeScreen",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.implicitly_wait(10)
# 跳过一些基础的设置和提示
driver.find_element_by_id('com.baidu.BaiduMap:id/ok_btn').click()
driver.find_element_by_id('com.baidu.BaiduMap:id/btn_enter_map').click()
driver.find_element_by_id('com.baidu.BaiduMap:id/guide_close').click()
time.sleep(5)

def get_window_size():
""" 获取窗口大小 """
size_dict = driver.get_window_size()
return size_dict.get('width'), size_dict.get('height')

def shrink():
""" 缩小窗口 """
x, y = get_window_size()
action1 = action2 = TouchAction(driver)
mu_obj = MultiAction(driver)
# x不变,使y从小0.4滑动到大0.45
action1.press(x=x * 0.4, y=y * 0.4).wait(1000).move_to(x=x * 0.4, y=y * 0.45).wait(1000).release()
# x不变,使y从大0.8滑动到小0.7位置
action2.press(x=x * 0.4, y=y * 0.8).wait(1000).move_to(x=x * 0.4, y=y * 0.7).wait(1000).release()
mu_obj.add(action1, action2)
mu_obj.perform()

def magnify():
""" 放大窗口 """
x, y = get_window_size()
action1 = action2 = TouchAction(driver)
mu_obj = MultiAction(driver)
# x不变,使y从大0.45滑动到小0.4
action1.press(x=x * 0.4, y=y * 0.45).wait(1000).move_to(x=x * 0.4, y=y * 0.4).wait(1000).release()
# x不变,使y从小0.7滑动到大0.8
action2.press(x=x * 0.4, y=y * 0.7).wait(1000).move_to(x=x * 0.4, y=y * 0.8).wait(1000).release()
mu_obj.add(action1, action2)
mu_obj.perform()

if __name__ == '__main__':
shrink()
time.sleep(5)
magnify()




元素定位

 

通过id定位

 

我们通过Python代码打开APP(此时appium是启动并监听4723端口),来到首页,然后使用uiautomatorviewer工具获取(这时要结束appium连接)页面结构。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"

}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

真巧!我们发现了​​跳过​​​按钮的id是​​com.jd.app.reader:id/skip_tv​​。那么我们就可以直接通过id定位,然后点击就完了。

移动端测试 - appium usage_ico

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"

}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
button = driver.find_element_by_id('com.jd.app.reader:id/skip_tv')
print(button)
button.click()

如果你学过selenium,你会发现,selenium和appium的用法很像。所以,代码就无需多言了,根据标签定位,然后点击。

通过class属性

 

class属性定位就是通过uiautomatorviewer工具查看到的class属性。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

res = driver.find_elements_by_class_name('android.widget.TextView')
print(len(res)) # 1
res[0].click()

由于class的属性的特性,一般都是有多个标签引用,所以,我们不能上来就用​​driver.find_element_by_class_name('android.widget.TextView')​​这种形式,当然这种是默认从多个中选择第一个,但我们不能保证我们想要的标签它正好处于第一个。

所以,我们应该拿到class属性,如上例中,先找多个,看看它这个页面中到底找到了多少个(列表的形式),然后根据其下标定位即可。

上例中恰好页面中只有一个标签,但由于是列表的形式,所以我们取0。

其它形式的定位就不多扯,可以参考selenium。

层级定位

 

所谓的层级定位,有相对定位的意思,也就是根据儿子找他爹,找他爹的爹;根据父亲找儿子;各种找就完了。

这是一种定位思想。使用不同的定位方法相结合,能轻松解决问题。

移动端测试 - appium usage_ico_02

如上图,这个页面中有很多的class属性为​​android.widget.RelativeLayout​​​的标签,也有很多class属性为​​android.widget.ImageView​​​的标签。但​​RelativeLayout​​标签恰好有id,那么我们可以根据这个id定位到其子标签。说白了就是先找个好定位的点,这就进一步缩小了我们的目标,然后在这个范围内,再找最终的目标。一步一步缩小范围。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()

# 直接在整个页面中找 android.widget.ImageView, 发现9个
res = driver.find_elements_by_class_name('android.widget.ImageView')
print(len(res)) # 9
# 换个套路,先缩小一步范围
res1 = driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookshelf')
# 在这个范围内,再去找 android.widget.ImageView 找到1个
res2 = res1.find_elements_by_class_name('android.widget.ImageView')
print(len(res2)) # 1
# 只有一个就好办了,轻松拿下
res2[0].click()

xpath定位

 

首先,你可能说,咱们appium用的好好地,怎么突然用上了UIautomatorviewer了?因为啊,appium更擅长于xpath定位!其他的UIautomatorviewer更胜一筹。所以,相结合着来使用更好。

基本属性定位

 

说起xpath,满满都是泪,记性不好的我,压根记不住那些规则和符号(这难道就是我正则不好的原因么,手动滑稽)。

在基本属性定位这里,我们有几种定位方式。

举的例子还是以京东阅读APP的跳过起始页广告为例。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
time.sleep(3)

不变的代码先摘出来。

通过text文本定位

//*[@text=“文本”]

driver.find_element_by_xpath('//*[@text="跳过"]').click()

通过id定位

//*[@resource-id='id属性']

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/skip_tv"]').click()

通过class定位

如果class是唯一的话:

//class属性

driver.find_element_by_xpath('//android.widget.TextView').click()

如果是多个:

//*[@class='class属性']

driver.find_element_by_xpath('//*[@class="android.widget.TextView"]').click()

层级定位

 

通过爹爹找儿子

移动端测试 - appium usage_ico_03

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_layout"]/android.widget.RelativeLayout').click()
# 如果是多个,默认找第一个

注意:如果父节点下有多个子节点的话,可以通过数组下标来确定子节点的位置,xpath是从1开始数的

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_layout"]/android.widget.RelativeLayout[3]').click()

通过儿子找爹爹

移动端测试 - appium usage_android_04

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_bookshelf_icon"]/..').click()

再来看个parent语法的:

driver.find_element_by_xpath('//*[@resource-id="com.jd.app.reader:id/main_tab_bookshelf_icon"]/parent::android.widget.RelativeLayout[1]').click()

注意,通过子节点找父节点的,parent后都使用class属性。

android_uiautomator定位

 

最后再来看一个跟uiautomatorviewer不一样的定位方式——android_uiautomator定位。

android_uiautomator提供的常用方法如下:

  • 通过text文本定位语法:​​new UiSelector().text("text文本")​
  • 模糊匹配:​​new UiSelector().textContains("包含text文本")​​,一般应用于文本较长中。
  • 匹配以指定字符开头:​​new UiSelector().textStartsWith("以text文本开头")​
  • 正则匹配:​​new UiSelector().textMatches("正则表达式")​
  • resourceId匹配:​​new UiSelector().resourceId("id")​​,这个鬼东西其实跟id一样,但不保证唯一性!
  • class定位:​​new UiSelector().className("className")​​,页面上的class属性一般不唯一,多半用在复数定位时候。比如通过class属性定位'排行'这个按钮下标就是2。
import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()
time.sleep(3)

# 通过text定位
driver.find_element_by_android_uiautomator('new UiSelector().text("书架")').click()
time.sleep(2)

# 通过 resourceId 定位
driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.jd.app.reader:id/main_tab_bookcity_txt")').click()

在输入时屏蔽键盘

 

很明显我们要考虑一个问题,我们遇到输入框时,如何往里面输入值?难道真的要模拟拼音输入?那岂不是要疯?

那你可能会说,我们有send_keys呀!果然,英雄出少年,你说对了,我们有send_keys解决问题。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 点击跳过首页广告
driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()
time.sleep(1)
# 点击顶部的输入框
driver.find_element_by_id('com.jd.app.reader:id/mSearchLayout').send_keys()
time.sleep(1)
# 在新的搜索页面输入值
driver.find_element_by_id('com.jd.app.reader:id/mSearchKeyInput').send_keys('上古卷轴')
time.sleep(1)
# 有了值,就可以点击搜索按钮进行搜索
driver.find_element_by_id('com.jd.app.reader:id/mSearchBtn').click()

如果你运行了上述代码,会发现,除了无法输入值之外,其他都好使!

那原因就是,我们需要屏蔽键盘,然后将我们想要输入的值send进去就OK啦,这用到两个参数:

  • unicodeKeyboard:使用Unicode编码方式发送字符串。
  • resetKeyboard:屏蔽键盘。

将这两个值放到请求的字典中即可。

import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 点击跳过首页广告
driver.find_element_by_id('com.jd.app.reader:id/skip_tv').click()
time.sleep(1)
# 点击顶部的输入框
driver.find_element_by_id('com.jd.app.reader:id/mSearchLayout').send_keys()
time.sleep(1)
# 在新的搜索页面输入值
driver.find_element_by_id('com.jd.app.reader:id/mSearchKeyInput').send_keys('上古卷轴')
time.sleep(1)
# 有了值,就可以点击搜索按钮进行搜索
driver.find_element_by_id('com.jd.app.reader:id/mSearchBtn').click()

这么着是不是就好使了。

等待机制

 

要想在appium中使用等待机制怎么办?好吧,我们借助selenium提供的等待机制来完成显式等待。隐式等待就是用appium自带的吧。

​https://www.cnblogs.com/Neeo/articles/11005164.html​

from appium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()

# 隐式等待
driver.implicitly_wait(10)

driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookshelf').click()

# 显式等待,直到找到了这个标签
WebDriverWait(driver, 10).until(driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookcity'))
driver.find_element_by_id('com.jd.app.reader:id/main_tab_bookcity').click()

截屏

 

from appium import webdriver
desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

try:
# 根据一个不存在的 id 定位
driver.find_element_by_id("xxxxxxxxx").click()
except Exception as e:
print(e) # Message: An element could not be located on the page using the given search parameters.
driver.save_screenshot('./error.png')

这玩意儿跟selenium的截图功能一样,没啥好聊的了。

滑动:driver.swipe

 

首先要知道,手机屏幕的X、Y坐标定位一个像素点。并且原点是从左上角开始的。

移动端测试 - appium usage_android_05

来看相关参数:

def swipe(self, start_x, start_y, end_x, end_y, duration=None):
"""
从一个点滑动到另一个点。

Args:
start_x (int): 开始滑动的x坐标
start_y (int): 开始滑动的y坐标
end_x (int): 结束点x坐标
end_y (int): 结束点y坐标
duration (:obj:`int`, optional): 持续时间,单位毫秒

Usage:
driver.swipe(100, 100, 100, 400)

Returns:
`appium.webdriver.webelement.WebElement`
"""

duration是滑动屏幕持续的时间,时间越短速度越快。默认为None可不填,一般设置500-1000毫秒比较合适。

获取屏幕大小

由于每个手机屏幕的分辨率不一样,所以同一个元素在不同手机上的坐标也是不一样的,滑动的时候坐标不能写死了。可以先获取屏幕的宽和高,再通过比例去计算。

from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity"
}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
print(driver.get_window_size()) # {'width': 720, 'height': 1280}
print(driver.get_window_size()['width']) # 720
print(driver.get_window_size()['height']) # 1280

封装滑动方法

参数分别是:

  • 驱动driver。
  • 滑动时间。
  • 滑动次数。
import time
from appium import webdriver

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.jd.app.reader",
"appActivity": "com.jingdong.app.reader.logo.JdLogoActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.find_element_by_id("com.jd.app.reader:id/skip_tv").click()

def swipeUp(driver, t=500, n=1):
'''向上滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.5 # x坐标
y1 = l['height'] * 0.75 # 起始y坐标
y2 = l['height'] * 0.25 # 终点y坐标
for i in range(n):
driver.swipe(x1, y1, x1, y2, t)


def swipeDown(driver, t=500, n=1):
'''向下滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.5 # x坐标
y1 = l['height'] * 0.25 # 起始y坐标
y2 = l['height'] * 0.75 # 终点y坐标
for i in range(n):
driver.swipe(x1, y1, x1, y2, t)


def swipLeft(driver, t=500, n=1):
'''向左滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.75
y1 = l['height'] * 0.5
x2 = l['width'] * 0.25
for i in range(n):
driver.swipe(x1, y1, x2, y1, t)


def swipRight(driver, t=500, n=1):
'''向右滑动屏幕'''
l = driver.get_window_size()
x1 = l['width'] * 0.25
y1 = l['height'] * 0.5
x2 = l['width'] * 0.75
for i in range(n):
driver.swipe(x1, y1, x2, y1, t)

if __name__ == '__main__':
time.sleep(4)
swipLeft(driver, n=2)
time.sleep(4)
swipRight(driver, n=2)
time.sleep(4)
swipeUp(driver, n=2)
time.sleep(4)
swipeDown(driver, n=2)

触摸操作:TouchAction

 

源码:​​Lib\site-packages\appium\webdriver\common\touch_action.py​

class TouchAction(object):
def __init__(self, driver=None):
self._driver = driver
self._actions = []

def tap(self, element=None, x=None, y=None, count=1):
模拟手指触摸屏

def press(self, el=None, x=None, y=None):
短按:模拟手指按住一个元素,或者坐标

def long_press(self, el=None, x=None, y=None, duration=1000):
长按:模拟按住一个元素,或者坐标

def wait(self, ms=0):
按住元素后的等待时间

def move_to(self, el=None, x=None, y=None):
移动手指到另外一个元素,或者坐标,注意这里坐标不是绝对坐标,是偏移量

def release(self):
释放手指

def perform(self):
执行前面的动作

TouchAction里面有这几个动作:

  • 触摸 (tap)
  • 短按 (press)
  • 长按 (long_press)
  • 等待 (wait)
  • 移动到 (moveTo)
  • 释放 (release)
  • 执行 (perform)

九宫格示例:点击指定图标

这里用到了​​jiugongge.apk​​了,你需要将它安装到模拟器中去。

先能连上这个apk。

import time
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.duome.locus",
"appActivity": "com.jiugongge.java.LoginActivity",
"unicodeKeyboard": True,
"resetKeyboard": True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

解决问题的思路是,先获取元素坐标位置,再获取元素大小,然后切割图片,分别计算出每个点的坐标。

但观察上图,九宫格上下边距距离较大,我们为了方便,手动各添加一排假图标(黄色)。我们要拿到整个屏幕的大小:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

有了宽和高,我们就来定位一下元素1的位置。

元素1的坐标:

  • x轴,将x轴分6份取第1份,也就是​​x=width / 6 * 1​
  • y轴,将y轴分9份取第3份。也就是​​y=height / 10 * 3​

也就是说坐标1,它位于x轴的第1份,y轴的第3份。

经这么分割,我们就可以尝试点击它了,这里使用短按方法:

width, height = element.size['width'] / 6 * 1, element.size['height'] / 9 * 3
TouchAction(driver).press(x=width, y=height).perform()

九宫格示例:按照从小到大的顺序点击所有图标

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y

# 第1个图标
width, height = get_coordinate(1, 3)
TouchAction(driver).press(None, x=width, y=height).perform()
# 第2个图标
time.sleep(1)
width, height = get_coordinate(3, 3)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第3个图标
time.sleep(1)
width, height = get_coordinate(5, 3)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第4个图标
time.sleep(1)
width, height = get_coordinate(1, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第5个图标
time.sleep(1)
width, height = get_coordinate(3, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第6个图标
time.sleep(1)
width, height = get_coordinate(5, 5)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第7个图标
time.sleep(1)
width, height = get_coordinate(1, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第8个图标
time.sleep(1)
width, height = get_coordinate(3, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

# 第9个图标
time.sleep(1)
width, height = get_coordinate(5, 7)
TouchAction(driver).press(None, x=width, y=height).perform()

这个根据之前的图,不难计算它的位置。但是代码有些冗余啊.....

如果你观察细致的话,整个点击过程是有一定的规律的,所以,我们可以做些优化:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y
for y in range(3, 8, 2): # 3 5 7
for x in range(1, 6, 2): # 1 3 5
width, height = get_coordinate(x, y)
TouchAction(driver).press(None, x=width, y=height).wait(500).perform()

这个示例也可以使用tap来完成:

element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}

def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y

for y in range(3, 8, 2): # 3 5 7
for x in range(1, 6, 2): # 1 3 5
width, height = get_coordinate(x, y)
driver.tap([(width, height)], 500)

tap是模拟手指点击,一般页面上元素 的语法有两个参数,第一个是positions,是list类型最多五个点,duration是持续时间,单位毫秒。

def tap(self, positions, duration=None):
"""
模拟手指点击(最多5个手指),保持一定的时间
Args:
positions (:obj:`list` of :obj:`tuple`): list类型,里面对象是元组,最多五个。如:[(100, 20), (100, 60)]
duration (:obj:`int`, optional): 持续时间,单位毫秒,如:500
Usage:
driver.tap([(100, 20), (100, 60), (100, 100)], 500)
Returns:
`appium.webdriver.webelement.WebElement`
"""

他是有弊端的:通过坐标定位是元素定位的下下下策,实在没办法才用这个,另外如果换了手机分辨率,这个坐标就不能写死了,得算出所在屏幕的比例。

九宫格示例:Z形滑动

我们能点击了,是不是也就是能滑动了?尝试一下!

from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.duome.locus",
"appActivity": "com.jiugongge.java.LoginActivity",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
element = driver.find_element_by_id('com.duome.locus:id/mLocusPassWordView')
print(element.size) # {'height': 1141, 'width': 720}


def get_coordinate(x, y):
""" 获取指定图标的width和height,x, y表示你想选中的指定坐标x,y轴 """
return element.size['width'] / 6 * x, element.size['height'] / 9 * y


def gen():
""" 生成每一个图标的坐标位置 """
for y in range(3, 8, 2):
for x in range(1, 6, 2):
yield get_coordinate(x, y)

def main():
# 首先拿到生成器
generator = gen()
# 先拿到第一个点的坐标位置
width, height = next(generator)
# 有了第一个点的width和height,就按住不松手
obj = TouchAction(driver)
obj.press(None, width, height).wait(500).perform()
# 然后循环移动到后续的每个点
for width, height in generator:
obj.move_to(None, width, height).wait(500).perform()
# 最后再释放即可
obj.release()

if __name__ == '__main__':
main()

效果如下:

移动端测试 - appium usage_ico_06

多点触控:MultiAction

 

多点触摸对象是触摸动作的集合。
多点触控手势只有两种方法,即添加和执行。
add用于添加另一个触摸操作到多点触摸。
当perform执行被调用时,添加到多点触摸的所有触摸动作都被发送到Appium,并执行,就像它们同时发生一样。appium首先执行所有触摸动作的第一个事件,然后执行第二个,等等。

来个百度地图的示例,首先你要安装​​baiduditu_948.apk​​:

移动端测试 - appium usage_ico_07

思路就是主要就是两个点(A, D),然后他们各自(同时)滑动到(B,C),只是让这两件事看起来是同时一样。

下面的示例演示这一过程。

为了简单,我们让x保持不变,改变y的坐标达到这一目的。

import time
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
from appium.webdriver.common.multi_action import MultiAction

desired_caps = {
"platformName": "android",
"platformVersion": "4.4.2",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.baidu.BaiduMap",
"appActivity": "com.baidu.baidumaps.WelcomeScreen",
"unicodeKeyboard": True,
"resetKeyboard": True

}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.implicitly_wait(10)
# 跳过一些基础的设置和提示
driver.find_element_by_id('com.baidu.BaiduMap:id/ok_btn').click()
driver.find_element_by_id('com.baidu.BaiduMap:id/btn_enter_map').click()
driver.find_element_by_id('com.baidu.BaiduMap:id/guide_close').click()
time.sleep(5)

def get_window_size():
""" 获取窗口大小 """
size_dict = driver.get_window_size()
return size_dict.get('width'), size_dict.get('height')

def shrink():
""" 缩小窗口 """
x, y = get_window_size()
action1 = action2 = TouchAction(driver)
mu_obj = MultiAction(driver)
# x不变,使y从小0.4滑动到大0.45
action1.press(x=x * 0.4, y=y * 0.4).wait(1000).move_to(x=x * 0.4, y=y * 0.45).wait(1000).release()
# x不变,使y从大0.8滑动到小0.7位置
action2.press(x=x * 0.4, y=y * 0.8).wait(1000).move_to(x=x * 0.4, y=y * 0.7).wait(1000).release()
mu_obj.add(action1, action2)
mu_obj.perform()

def magnify():
""" 放大窗口 """
x, y = get_window_size()
action1 = action2 = TouchAction(driver)
mu_obj = MultiAction(driver)
# x不变,使y从大0.45滑动到小0.4
action1.press(x=x * 0.4, y=y * 0.45).wait(1000).move_to(x=x * 0.4, y=y * 0.4).wait(1000).release()
# x不变,使y从小0.7滑动到大0.8
action2.press(x=x * 0.4, y=y * 0.7).wait(1000).move_to(x=x * 0.4, y=y * 0.8).wait(1000).release()
mu_obj.add(action1, action2)
mu_obj.perform()

if __name__ == '__main__':
shrink()
time.sleep(5)
magnify()

标签:appium,driver,element,width,测试,usage,height,com,id
From: https://blog.51cto.com/u_15920572/5969412

相关文章

  • Selenium26-测试固件
    Fixture(测试固件)Fixture(测试固件、也叫作测试夹具),书写在TestCase代码中的代码结构,用于前期初始化准备和后期清理收尾工作本质就是一些名称固定的方法测试固件分......
  • Java类MemoryUsage查看虚拟机的使用情况
     Arthas是阿里巴巴开源的一款监控java进程的工具,可以有效监控CPU、内存使用情况,更厉害的是可以帮助开发人员深入排查java代码的问题,比如java进程占用cpu过高是哪一个线程......
  • 很实用的web性能测试插件:Yslow , PageSpeed
     packageorg.springframework.web.servlet.resource;importjava.io.IOException;importjava.io.UnsupportedEncodingException;importjava.net.URLDecoder;importjava......
  • 基于Python语言单元测试框架unittest和pytest的区别
    1.用例编写规则:unittest:提供了testcases测试用例,testsuites测试套件,testfixtures测试固件或者夹具,testloader测试加载器,testrunner测试运行器。必须遵守以下规则:(1)测试文件......
  • JMeter性能测试
    JMeter性能测试影响性能测试指标:响应时间、用户并发数、吞吐量、系统性能计数器、思考时间多:用户并发数快:响应时间好:稳定性省:资源使用率文档+fiddle录制脚本方法:1.......
  • Selenium25-TestCase(测试用例)
    TestCase(测试用例)#导入unittest包importunittest#测试类(名称自定义)只要继承unittest模块中TestCase类即可classMyTestCase(unittest.TestCase):#书写测......
  • DDS测试策略探讨与协议测试工具介绍
    软件定义汽车对测试的影响 OEM和供应商之间传统的合作模式是由OEM释放技术需求,供应商按照需求进行软件和硬件实现,最终交付的是完整的软硬件系统。随着集中式架构的逐步......
  • 液体眼线笔BCOP测试报告
    什么产品需要这个认证呢?像接触眼睛外贸论坛外贸论坛的眼影,液体眼线笔,磁性睫毛,假睫毛,等都可能会对眼睛产生eBay论坛eBay论坛一定外贸论坛外贸论坛的刺激,所以亚马逊现在也在严......
  • 测试监控和测试控制
    在软件测试领域,QA管理者和高阶的测试人员必须实施不同的测试管理方法,例如测试监控和控制,以确保测试活动按照计划顺利执行。管理人员需要这些基本的管理策略来跟踪和调整测试......
  • centos9上布置pxe+kickstart测试
    实验环境:vmwareesxi6.7.0纳管平台:vmwarevcenter1.装包yum-yinstalldhcp-servertftphttpd2.配置dhcp[root@centos9~]#cat/etc/dhcp/dhcpd.conf#DHCP......