小白也能学会的模拟屏幕滑动与手势密码绘制

发表于:2019-8-13 13:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:linux超    来源:博客园

分享:
   测试代码
  test_gesture_password.py
   import time
  import unittest
  from appium import webdriver
  from appium.webdriver.common.mobileby import MobileBy
  from base import Base
  class TestGesture(unittest.TestCase):
  def setUp(self):
  desired = {
  "automationName": "uiautomator1",
  "platformName": "Android",
  "platformVersion": '5.1.1',
  "deviceName": "127.0.0.1:62001",
  "appPackage": "com.xxzb.fenwoo",
  "appActivity": "com.xxzb.fenwoo.activity.addition.WelcomeActivity",
  "app": r"D:\AppAutoTest\appPackage\Future-release-2018.apk",
  "unicodeKeyboard": True,  # 屏蔽键盘
  "resetKeyboard": True
  }
  self.driver = webdriver.Remote(command_executor="http://127.0.0.1:4723/wd/hub",
  desired_capabilities=desired)
  self.base = Base(self.driver)
  def test_gesture_password(self):
  # 直接切换到手势密码页面
  self.driver.start_activity(app_package="com.xxzb.fenwoo",
  app_activity=".activity.user.CreateGesturePwdActivity")
  commit_btn = (MobileBy.ID, 'com.xxzb.fenwoo:id/right_btn')
  password_gesture = (MobileBy.ID, 'com.xxzb.fenwoo:id/gesturepwd_create_lockview')
  element_commit = self.base.find_element(commit_btn)
  element_commit.click()
  # 9宫格元素
  password_element = self.base.find_element(password_gesture)
  self.base.gesture_password(password_element)
  time.sleep(5) # 为了看效果
  def tearDown(self):
  self.driver.quit()
  if __name__ == '__main__':
  unittest.main()
  以上就是完整的模拟手势密码操作的代码, 但是问题来了 , 我这里执行的时候不成功,很尴尬,但是我确实看到过别人通过这种获取每个点的坐标,从一个点的坐标移动到另一个点的坐标的方式成功画线了,当然你可以先试试能不能成功再往下看!
  方法重写
  如果上边的方式你也不成功,那么就试试下面的方法吧,原理是一样的,主要不同点在,move_to方法传递的不是每个点的坐标,而是相对点的坐标,也就是从一个点移动到另一个点的距离坐标,例如点1的坐标为(360, 579), 点2的坐标为(580, 579), 那么移动的距离应该是横向220,纵向为0, 传递的参数应该是这样的move_to(x=220, y=0)(这里传递的参数叫做相对位置坐标,但是move_to的源码就是按照我之前的写法传参的,具体为啥,我也不得而知了),修改部分代码如下
    TouchAction(self.driver).press(x=point_1["x"], y=point_1["y"]).wait(300) \
  .move_to(x=point_2["x"]-point_1["x"], y=point_2["y"]-point_1["y"]).wait(500) \
  .move_to(x=point_3["x"]-point_2["x"], y=point_3["y"]-point_2["y"]).wait(500) \
  .move_to(x=point_6["x"]-point_3["x"], y=point_6["y"]-point_3["y"]).wait(500) \
  .move_to(x=point_9["x"]-point_6["x"], y=point_9["y"]-point_6["y"]).wait(500).release().perform()
  相对坐标的计算方法:用后一个目标点坐标减去前一个点的坐标即为相对坐标,你可以把这个段代码替换一下,你会发现确实成功了
  代码优化
  上述代码你会发现, 每次绘制的只能是同一个密码,如果我想绘制不同的密码,那么就必须修改绘制时传递的坐标,作为一枚优秀的程序员怎么可以这样讷?冲这句话,我就必须得想办法做到绘制任何密码组合的情况。我的需求是,当我给绘制函数getsture_password()传递不同密码时(例如这样的方式getsture_password(1, 2, 3, 6, 9))那么程序应该帮我把1-2-3-6-9链接起来,所以我想到了使用字典,把每个数字分别对应每一个坐标点,像下面这样
   def get_password_location(self, element: WebElement) -> dict:
  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)}
  keys = {
  1: point_1,
  2: point_2,
  3: point_3,
  4: point_4,
  5: point_5,
  6: point_6,
  7: point_7,
  8: point_8,
  9: point_9
  }
  return keys
  然后我通过另一个方法来实现绘制连线的功能
   def gesture_password(self, element: WebElement, *pwd):  # pwd是个元组,pwd[0]表示第一个密码
  """手势密码: 直接输入需要链接的点对应的数字,最多9位
  pwd: 传你想连接的点构成的密码,如:1, 2, 3, 6, 9
  """
  if len(pwd) > 9:
  raise ValueError("需要设置的密码不能超过9位!")
  keys_dict = self.get_password_location(element)  # 9个点的坐标组成的字典
  start_point = "TouchAction(self.driver).press(x={0}, y={1}).wait(200)".\  # keys_dict[pwd[0]] 得到第一位密码数字对应的坐标的字典
  format(keys_dict[pwd[0]]["x"], keys_dict[pwd[0]]["y"])  # 起始点的坐标
  for index in range(len(pwd)-1):  # 0,1,2,3
  follow_point = ".move_to(x={0}, y={1}).wait(200)".\
  format(keys_dict[pwd[index+1]]["x"] - keys_dict[pwd[index]]["x"],
  keys_dict[pwd[index+1]]["y"] - keys_dict[pwd[index]]["y"])  #  后续的点坐标
  start_point = start_point + follow_point  # 把起始点的表达式和后续链接的点表达式链接在一起组成一个模拟连线的完整过程
  full_point = start_point + ".release().perform()"  # 完整的过程通过.release().perform()使其生效
  return eval(full_point)  # 执行一串表达式
  上面的方法还可以这样写,比上面的好点吧
   def gesture_password(self, element: WebElement, *pwd):
  """手势密码: 直接输入需要链接的点对应的数字,最多9位
  pwd: 1, 2, 3, 6, 9
  """
  if len(pwd) > 9:
  raise ValueError("需要设置的密码不能超过9位!")
  keys_dict = self.get_password_location(element)
  action = TouchAction(self.driver)
  action.press(x=keys_dict[pwd[0]]["x"], y=keys_dict[pwd[0]]["y"]).wait(200)
  for index in range(len(pwd)-1):  # 0,1,2,3
  action.move_to(x=keys_dict[pwd[index+1]]["x"] - keys_dict[pwd[index]]["x"],
  y=keys_dict[pwd[index+1]]["y"] - keys_dict[pwd[index]]["y"]).wait(200)
  return action.release().perform()
  比较难理解的地方,我已经详细注释了,当然,你可以复制我的代码先验证能否绘制成功再分析代码的实现原理
  所有代码
  修改后的绘制手势密码代码&滑屏代码
"""
  ------------------------------------
  @Time : 2019/8/6 20:45
  @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
  ------------------------------------
  """
  import time
  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
  @property
  def get_phone_size(self):
  """获取屏幕的大小"""
  width = self.driver.get_window_size()['width']
  height = self.driver.get_window_size()['height']
  return width, height
  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.1, height * 0.5
  end = width * 0.9, height * 0.5
  return self.driver.swipe(*start, *end, duration)
  def swipe_up(self, duration):
  """上滑"""
  width, height = self.get_phone_size
  start = width * 0.5, height * 0.9
  end = width * 0.5, height * 0.1
  return self.driver.swipe(*start, *end, duration)
  def swipe_down(self, duration):
  """下滑"""
  width, height = self.get_phone_size
  start = width * 0.5, height * 0.1
  end = width * 0.5, height * 0.9
  return self.driver.swipe(*start, *end, duration)
  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()))
  @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 get_password_location(self, element: WebElement) -> dict:
  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)}
  keys = {
  1: point_1,
  2: point_2,
  3: point_3,
  4: point_4,
  5: point_5,
  6: point_6,
  7: point_7,
  8: point_8,
  9: point_9
  }
  return keys
  def gesture_password(self, element: WebElement, *pwd):
  """手势密码: 直接输入需要链接的点对应的数字,最多9位
  pwd: 1, 2, 3, 6, 9
  """
  if len(pwd) > 9:
  raise ValueError("需要设置的密码不能超过9位!")
  keys_dict = self.get_password_location(element)
  start_point = "TouchAction(self.driver).press(x={0}, y={1}).wait(200)". \
  format(keys_dict[pwd[0]]["x"], keys_dict[pwd[0]]["y"])
  for index in range(len(pwd) - 1):  # 0,1,2,3
  follow_point = ".move_to(x={0}, y={1}).wait(200)". \
  format(keys_dict[pwd[index + 1]]["x"] - keys_dict[pwd[index]]["x"],
  keys_dict[pwd[index + 1]]["y"] - keys_dict[pwd[index]]["y"])
  start_point = start_point + follow_point
  full_point = start_point + ".release().perform()"
  return eval(full_point)
  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
  
"""
------------------------------------
  @Time : 2019/8/6 20:47
  @Auth : linux超
  @File : test.py
  @IDE  : PyCharm
  @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
  @QQ   : 28174043@qq.com
  @GROUP: 878565760
  ------------------------------------
  """
  import time
  import unittest
  from appium import webdriver
  from appium.webdriver.common.mobileby import MobileBy
  from base import Base
  class TestGesture(unittest.TestCase):
  def setUp(self):
  desired = {
  "automationName": "uiautomator1",
  "platformName": "Android",
  "platformVersion": '5.1.1',
  "deviceName": "127.0.0.1:62001",
  "appPackage": "com.xxzb.fenwoo",
  "appActivity": "com.xxzb.fenwoo.activity.addition.WelcomeActivity",
  "app": r"D:\AppAutoTest\appPackage\Future-release-2018.apk",
  "unicodeKeyboard": True,  # 屏蔽键盘
  "resetKeyboard": True
  }
  self.driver = webdriver.Remote(command_executor="http://127.0.0.1:4723/wd/hub",
  desired_capabilities=desired)
  self.base = Base(self.driver)
  def test_gesture_password(self):
  self.base.skip_welcome_page('left', 3)  # 滑动屏幕
  time.sleep(3)  # 为了看滑屏的效果
  self.driver.start_activity(app_package="com.xxzb.fenwoo",
  app_activity=".activity.user.CreateGesturePwdActivity")
  commit_btn = (MobileBy.ID, 'com.xxzb.fenwoo:id/right_btn')
  password_gesture = (MobileBy.ID, 'com.xxzb.fenwoo:id/gesturepwd_create_lockview')
  element_commit = self.base.find_element(commit_btn)
  element_commit.click()
  password_element = self.base.find_element(password_gesture)
  self.base.gesture_password(password_element, 1, 2, 3, 6, 5, 4, 7, 8, 9)
  time.sleep(5)  # 看效果
  def tearDown(self):
  self.driver.quit()
  if __name__ == '__main__':
  unittest.main()
   测试效果
  包含滑动屏幕
  总结
  最后,我们再总结一下完成所有的操作需要掌握的知识点
  1.滑动屏幕时起始位置和结束位置应该从哪里开始与结束,如何获取
  2.滑动屏幕使用的swipe()方法如何使用
  3.实现多次滑动方法的实现原理,这里用到了反射,其实使用if也可以实现一样的效果,但是总感觉if有点low
  4.9宫格起始位置与手机屏幕的关系及每个点的坐标如何计算
  5.TouchAction类中的常用方法如何使用
  6.理解绘制手势密码方法的封装原理及思路

      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号