小白也能学会的模拟屏幕滑动与手势密码绘制 - 51Testing软件测试网
小白也能学会的模拟屏幕滑动与手势密码绘制
发表于:2019-8-13 13:32 作者:linux超 来源:博客园
前言 App自动化测试中有两个很重要的操作,屏幕滑动与绘制手势密码。目前很多App在启动时,都存在启动时的引导动画或者加载上下文内容时需要手动上滑或者下滑加载页面,所以在自动化测试的过程中模拟手的滑动操作看起来就很重要了;第二个比较重要的是模拟手动绘制九宫格完成手势密码的设置,这种手势密码在我了解的范围内,大多在金融类的app中最常见,还有一些对用户信息保密性较好的app中,所以,模拟绘制手势密码也是app自动化测试中必须掌握的操作,那么接下来我们就开始讲解两种操作该如何实现, 在进入正题之前,你还应该知道,手机中横纵坐标的原点是从屏幕的左上角顶点(0, 0)的位置开始的 滑动屏幕 swipe方法 模拟滑动屏幕的操作,我们通过swipe方法实现,先看一下这个方法的源代码
def swipe(self, start_x, start_y, end_x, end_y, duration=None): """Swipe from one point to another point, for an optional duration. Args: start_x (int): x-coordinate at which to start start_y (int): y-coordinate at which to start end_x (int): x-coordinate at which to stop end_y (int): y-coordinate at which to stop duration (:obj:`int`, optional): time to take the swipe, in ms. Usage: driver.swipe(100, 100, 100, 400) Returns: `WebElement` """ # `swipe` is something like press-wait-move_to-release, which the server # will translate into the correct action action = TouchAction(self) action \ .press(x=start_x, y=start_y) \ .wait(ms=duration) \ .move_to(x=end_x, y=end_y) \ .release() action.perform() return self
参数 start_x, start_y : 表示开始滑动时的初始坐标,也就是从哪里开始滑动 end_x, end_y : 表示滑动后的坐标,也就是滑动到哪里 duration: : 表示滑动过程的时间间隔,模拟操作时,我们**设置个时间间隔,避免由于代码运行太快,而真机或者模拟器反应比较慢,而操作失败,单位以毫秒计算 通过源码,我们发现swipe方法实际上是使用TouchAction实现的,这个类在后面我们仍然会使用,主要是模拟一些触屏动作 实现思路 大家可以想象一下,平时我们滑动屏幕时,是如何操作的?例如向左滑动屏幕,我们往往是把手放在屏幕的右侧,然后按住屏幕向左滑动,那么代码如何知道我们从屏幕的哪个位置开始讷?那就是坐标了,我们可以先获取屏幕的宽,高,然后按照它的比例计算鼠标的位置坐标,我这里取的起始坐标点为屏幕宽度的0.9倍,高度的0.5倍,大概就是我们实际中滑屏时手指的操作位置。大家可以根据下面播放的动画观察鼠标开始的大概位置和结束位置 接下来我们开始模拟动画中鼠标的操作(人手的操作,我用的模拟器所以有鼠标) 首先我们通过get_window_size()方法获取屏幕的宽和高(这个方法返回一个字典),然后计算鼠标的初始位置和结束为止
def get_phone_size(self): """获取屏幕的大小""" width = self.driver.get_window_size()['width'] # 获取屏幕的宽 height = self.driver.get_window_size()['height'] # 获取屏幕的高 return width, height
通过模拟动画不难看出,鼠标大概从起始点坐标(屏幕宽的3/4,高的1/2)位置滑动到结束点坐标(屏幕宽1/4,高1/2),ok,接下来通过swipe()方法实现滑动操作
def swipe_left(self, duration=300): """左滑""" width, height = self.get_phone_size start = width * 0.9, height * 0.5 end = width * 0.1, height * 0.5 return self.driver.swipe(*start, *end, duration) def swipe_right(self, duration=300): """右滑""" width, height = self.get_phone_size start = width * 0.9, height * 0.5 end = width * 0.1, height * 0.5 return self.driver.swipe(*start, *end, duration) def swipe_up(self, duration): """上滑""" width, height = self.get_phone_size start = width * 0.9, height * 0.5 end = width * 0.1, height * 0.5 return self.driver.swipe(*start, *end, duration) def swipe_down(self, duration): """下滑""" width, height = self.get_phone_size start = width * 0.9, height * 0.5 end = width * 0.1, height * 0.5 return self.driver.swipe(*start, *end, duration)
方法优化 以上每一个方法调用一次只能滑动一次,而且不同的滑动方向需要调用不同的方法,使用时比较麻烦。所以我们可以优化一下代码,通过调用一个函数实现不同次数,不同方向的滑动 使用for循环实现连续的滑动,引入direction参数,结合字典及反射机制,实现根据不同的参数执行不同滑动方向的方法,传递num参数控制滑动的次数,具体代码如下
def skip_welcome_page(self, direction, num=3): """ 滑动页面跳过引导动画 :param direction: str 滑动方向,left, right, up, down :param num: 滑动次数 :return: """ direction_dic = { "left": "swipe_left", "right": "swipe_right", "up": "swipe_up", "down": "swipe_down" } time.sleep(3) if hasattr(self, direction_dic[direction]): for _ in range(num): getattr(self, direction_dic[direction])() # 使用反射执行不同的滑动方法 else: raise ValueError("参数{}不存在, direction可以为{}任意一个字符串". format(direction, direction_dic.keys()))
以上就是所有滑动屏幕的操作了,具体效果,我们后面再看(你也可以先试试) 手势密码 TouchAction类 模拟手势密码的绘制我们使用TouchAction类,这个类提供了短按压press()方法,wait()方法,move_to()方法,release()方法,perform()方法等常用方法,下面我简单说明一下这几个方法的作用 press(element, x, y) : 其中element参数是一个元素对象,当element不为空时,x和y必须位None,如果element为None时,x如果不为None,那么y也不能位None,也就是说在安卓操作系统中,element和(x,y)必要传递一个,苹果系统可以不传,这里不做介绍 wait(duration) : duration是时间,以毫秒为单位,这个方法的作用是等待一段时间,和sleep的作用类似,**区别sleep不能被TouchAtion对象访问 release() : 这个方法的作用是结合press等按压动作使用的,表示抬起动作 perform():这个方法的作用是使所有的按压及等待,release等动作生效 实现思路 模拟大多app中的手势设置密码操作会遇见两种情况,一种是9宫格中每一个点的元素都可以通过定位表达式定位到,另一种是每个点无法通过定位表达式定位到的,只能定位到整体9宫格元素,每个点只能通过获取坐标的方式定位,那么我们今天模拟绘制手势密码的情况就是第二种,如果这种掌握了,那么第一种更简单,下面我们分析一下该如何获取每一个点的坐标,先来看下面的图 上图中的x轴,y轴是手机的坐标表示方式,请区别数学中的二维坐标,其中x轴方向表示手机屏幕的宽度width,y轴方向表示屏幕的高度height,原点为(0, 0); 蓝色方框代表9宫格手势操作的整体元素(内部包含9个点),start_x, start_y 代表9宫格元素的起始坐标点,start_x也是9宫格起始点距离y轴的距离,start_y也是9宫格起始点距离x轴的距离,请大家一定理解这几个值的关系,下面我们可以通过WebElement对象的rect方法获取9宫格元素的宽,高及起始点坐标
def get_element_size_location(element): width = element.rect["width"] # 9宫格元素的宽度 height = element.rect["height"] # 9宫格坐标的高度 # 9宫格元素的起始坐标点 start_x = element.rect["x"] start_y = element.rect["y"] return width, height, start_x, start_y
除了使用rect方法外,你还可以使用location和size方法分别获取元素的起始点坐标和宽,高,两个方法同样返回字典 element.location ->{"x": start_x, "y": start_y} element.size ->{"width": width, "height": height} 接下来我们通过9宫格元素的width,height,start_x, start_y分别计算每个点的坐标, 我们按照上图,把9宫格元素的width和height分别等分为6等分 前3个点(1, 2, 3)的坐标分别是
width, height, start_x, start_y = self.get_element_size_location(element) point_1 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 1)} point_2 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 1)} point_3 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 1)}
中间3个点(4, 5, 6)的坐标分别为
point_4 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 3)} point_5 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 3)} point_6 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 3)}
最后3个点(7, 8, 9)的坐标分别为
point_7 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 5)} point_8 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 5)} point_9 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 5)}
下面我们使用TouchAction类中的move_to,wait,release,perform方法实现从一个点移动到另一个点,进而实现模拟手势密码的连线操作(链接1-2-3-6-9)
TouchAction(driver).press(x=point_1["x"], y=point_1["y"]).wait(300)\ .move_to(x=point_2["x"], y=point_2["y"]).wait(500)\ .move_to(x=point_3["x"], y=point_3["y"]).wait(500)\ .move_to(x=point_6["x"], y=point_6["y"]).wait(500)\ .move_to(x=point_9["x"], y=point_9["y"]).wait(500).release().perform()
完整代码 不包含滑动屏幕的代码 base.py
""" ------------------------------------ @Time : 2019/8/6 20:22 @Auth : linux超 @File : base.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ from appium.webdriver import WebElement from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.webdriver import WebDriver from selenium.webdriver.support.wait import WebDriverWait from selenium.common.exceptions import NoSuchElementException, TimeoutException class Base(object): def __init__(self, driver: WebDriver): self.driver = driver @staticmethod def get_element_size_location(element): width = element.rect["width"] height = element.rect["height"] start_x = element.rect["x"] start_y = element.rect["y"] return width, height, start_x, start_y def gesture_password(self, element: WebElement): width, height, start_x, start_y = self.get_element_size_location(element) point_1 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 1)} point_2 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 1)} point_3 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 1)} point_4 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 3)} point_5 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 3)} point_6 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 3)} point_7 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 5)} point_8 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 5)} point_9 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 5)} TouchAction(self.driver).press(x=point_1["x"], y=point_1["y"]).wait(300) \ .move_to(x=point_2["x"], y=point_2["y"]).wait(500) \ .move_to(x=point_3["x"], y=point_3["y"]).wait(500) \ .move_to(x=point_6["x"], y=point_6["y"]).wait(500) \ .move_to(x=point_9["x"], y=point_9["y"]).wait(500).release().perform() def find_element(self, locator: tuple, timeout=30) -> WebElement: wait = WebDriverWait(self.driver, timeout) try: element = wait.until(lambda driver: driver.find_element(*locator)) return element except (NoSuchElementException, TimeoutException): print('no found element {} by {}', format(locator[1], locator[0])) if __name__ == '__main__': pass
【测试入门必备】细说Sqlserver与第三方工具相互间的关系