<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超詳細(xì)干貨:Appium+Pytest實(shí)現(xiàn)App并發(fā)測試!

          共 28285字,需瀏覽 57分鐘

           ·

          2021-09-07 17:06


          開課了:重磅消息 | 2021年最新全棧測試開發(fā)技能實(shí)戰(zhàn)指南(第2期)

          1. 前言

          Appium結(jié)合Pytest開展App自動化測試時,你知道如何實(shí)現(xiàn)用例并發(fā)執(zhí)行嗎?費(fèi)話不多說,直接上代碼, 畢竟想讓每個人都能看明白也不容易,所以先放代碼,有興趣的先自行研究。

          2. 目錄結(jié)構(gòu)

          3. 文件源碼

          3.1 base/base_page.py


          """
          ------------------------------------
          @File : base_page.py
          ------------------------------------
          """

          import time
          from appium.webdriver import WebElement
          from appium.webdriver.webdriver import WebDriver
          from appium.webdriver.common.touch_action import TouchAction
          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):
                  """
                  滑動頁面跳過引導(dǎo)動畫
                  :param direction:  str 滑動方向,left, right, up, down
                  :param num: 滑動次數(shù)
                  :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])()  # 使用反射執(zhí)行不同的滑動方法
                  else:
                       raise ValueError("參數(shù){}不存在, 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):
                  """手勢密碼: 直接輸入需要鏈接的點(diǎn)對應(yīng)的數(shù)字,最多9位
                  pwd: 1, 2, 3, 6, 9
                  """

                  if len(pwd) > 9:
                      raise ValueError("需要設(shè)置的密碼不能超過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 + 1]]["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

          3.2 common/check_port.py

          """
          ------------------------------------
          @File : check_port.py
          ------------------------------------
          """

          import socket
          import os


          def check_port(host, port):
              """檢測指定的端口是否被占用"""
              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 創(chuàng)建socket對象
              try:
                  s.connect((host, port))
                  s.shutdown(2)
              except OSError:
                  print('port %s is available! ' % port)
                  return True
              else:
                  print('port %s already be in use !' % port)
                  return False


          def release_port(port):
              """釋放指定的端口"""
              cmd_find = 'netstat -aon | findstr {}'.format(port)  # 查找對應(yīng)端口的pid
              print(cmd_find)

              # 返回命令執(zhí)行后的結(jié)果
              result = os.popen(cmd_find).read()
              print(result)

              if str(port) and 'LISTENING' in result:
                  # 獲取端口對應(yīng)的pid進(jìn)程
                  i = result.index('LISTENING')
                  start = i + len('LISTENING') + 7
                  end = result.index('\n')
                  pid = result[start:end]
                  cmd_kill = 'taskkill -f -pid %s' % pid  # 關(guān)閉被占用端口的pid
                  print(cmd_kill)
                  os.popen(cmd_kill)
              else:
                  print('port %s is available !' % port)


          if __name__ == '__main__':
              host = '127.0.0.1'
              port = 4723
              if not check_port(host, port):
                  print("端口被占用")
                  release_port(port)

          3.3 common/get_main_js.py

          """
          ------------------------------------
          @File : get_main_js.py
          @IDE  : PyCharm
          ------------------------------------
          """

          import subprocess
          from config.root_config import LOG_DIR

          """
          獲取main.js的未知,使用main.js啟動appium server
          """



          class MainJs(object):
              """獲取啟動appium服務(wù)的main.js命令"""

              def __init__(self, cmd: str = "where main.js"):
                  self.cmd = cmd

              def get_cmd_result(self):
                  p = subprocess.Popen(self.cmd,
                                       stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       shell=True)
                  with open(LOG_DIR + "/" + "cmd.txt""w", encoding="utf-8") as f:
                      f.write(p.stdout.read().decode("gbk"))
                  with open(LOG_DIR + "/" + "cmd.txt""r", encoding="utf-8") as f:
                      cmd_result = f.read().strip("\n")
                  return cmd_result


          if __name__ == '__main__':
              main = MainJs("where main.js")
              print(main.get_cmd_result())

          3.4 config/desired_caps.yml

          automationName: uiautomator2
          platformVersion: 5.1.1
          platformName: Android
          appPackage: com.xxzb.fenwoo
          appActivity: .activity.addition.WelcomeActivity
          noReset: True
          ip: "127.0.0.1"

          3.5 config/root_config.py


          """
          ------------------------------------
          @File : root_config.py
          ------------------------------------
          """

          import os

          """
          project dir and path
          """

          ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
          LOG_DIR = os.path.join(ROOT_DIR, "log")
          CONFIG_DIR = os.path.join(ROOT_DIR, "config")
          CONFIG_PATH = os.path.join(CONFIG_DIR, "desired_caps.yml")

          3.6 drivers/app_driver.py

          """
          ------------------------------------
          @File : app_driver.py
          ------------------------------------
          """

          import subprocess
          from time import ctime
          from appium import webdriver
          import yaml

          from common.check_port import check_port, release_port
          from common.get_main_js import MainJs
          from config.root_config import CONFIG_PATH, LOG_DIR


          class BaseDriver(object):
              """獲取driver"""
              def __init__(self, device_info):
                  main = MainJs("where main.js")
                  with open(CONFIG_PATH, 'r') as f:
                      self.data = yaml.load(f, Loader=yaml.FullLoader)
                  self.device_info = device_info
                  js_path = main.get_cmd_result()
                  cmd = r"node {0} -a {1} -p {2} -bp {3} -U {4}:{5}".format(
                      js_path,
                      self.data["ip"],
                      self.device_info["server_port"],
                      str(int(self.device_info["server_port"]) + 1),
                      self.data["ip"],
                      self.device_info["device_port"]
                  )
                  print('%s at %s' % (cmd, ctime()))
                  if not check_port(self.data["ip"], int(self.device_info["server_port"])):
                      release_port(self.device_info["server_port"])
                  subprocess.Popen(cmd, shell=True, stdout=open(LOG_DIR + "/" + device_info["server_port"] + '.log''a'),
                                   stderr=subprocess.STDOUT)

              def get_base_driver(self):
                  desired_caps = {
                      'platformName': self.data['platformName'],
                      'platformVerion': self.data['platformVersion'],
                      'udid': self.data["ip"] + ":" + self.device_info["device_port"],
                      "deviceName": self.data["ip"] + ":" + self.device_info["device_port"],
                      'noReset': self.data['noReset'],
                      'appPackage': self.data['appPackage'],
                      'appActivity': self.data['appActivity'],
                      "unicodeKeyboard": True
                  }
                  print('appium port:%s start run %s at %s' % (
                      self.device_info["server_port"],
                      self.data["ip"] + ":" + self.device_info["device_port"],
                      ctime()
                  ))
                  driver = webdriver.Remote(
                      'http://' + self.data['ip'] + ':' + self.device_info["server_port"] + '/wd/hub',
                      desired_caps
                  )
                  return driver


          if __name__ == '__main__':
              pass

          3.7 conftest.py

          """
          ------------------------------------
          @File : conftest.py
          ------------------------------------
          """

          from drivers.app_driver import BaseDriver
          import pytest
          import time

          from common.check_port import release_port

          base_driver = None


          def pytest_addoption(parser):
              parser.addoption("--cmdopt", action="store", default="device_info"help=None)


          @pytest.fixture(scope="session")
          def cmd_opt(request):
              return request.config.getoption("--cmdopt")


          @pytest.fixture(scope="session")
          def common_driver(cmd_opt):
              cmd_opt = eval(cmd_opt)
              print("cmd_opt", cmd_opt)
              global base_driver
              base_driver = BaseDriver(cmd_opt)
              time.sleep(1)
              driver = base_driver.get_base_driver()
              yield driver
              # driver.close_app()
              driver.quit()
              release_port(cmd_opt["server_port"])

          3.8 run_case.py

          """
          ------------------------------------
          @File : run_case.py
          ------------------------------------
          """

          import pytest
          import os
          from multiprocessing import Pool


          device_infos = [
              {
                  "platform_version""5.1.1",
                  "server_port""4723",
                  "device_port""62001",
              },
              {
                  "platform_version""5.1.1",
                  "server_port""4725",
                  "device_port""62025",
              }
          ]


          def main(device_info):
              pytest.main(["--cmdopt={}".format(device_info),
                           "--alluredir""./allure-results""-vs"])
              os.system("allure generate allure-results -o allure-report --clean")


          if __name__ == "__main__":
              with Pool(2) as pool:
                  pool.map(main, device_infos)
                  pool.close()
                  pool.join()

          3.9 cases/test_concurrent.py

          """
          ------------------------------------
          @File : test_concurrent.py
          ------------------------------------
          """

          import pytest
          import time
          from appium.webdriver.common.mobileby import MobileBy

          from base.base_page import Base


          class TestGesture(object):

              def test_gesture_password(self, common_driver):
                  """這個case我只是簡單的做了一個繪制手勢密碼的過程"""
                  driver = common_driver
                  base = Base(driver)
                  base.skip_welcome_page('left', 3)  # 滑動屏幕
                  time.sleep(3)  # 為了看滑屏的效果
                  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 = base.find_element(commit_btn)
                  element_commit.click()
                  password_element = base.find_element(password_gesture)
                  base.gesture_password(password_element, 1, 2, 3, 6, 5, 4, 7, 8, 9)
                  time.sleep(5)  # 看效果


          if __name__ == '__main__':
              pytest.main()

          4. 啟動說明

          1. 我代碼中使用的是模擬器,如果你需要使用真機(jī),那么需要修改部分代碼,模擬器是帶著端口號的,而真機(jī)沒有端口號,具體怎么修改先自己研究,后面我再詳細(xì)的介紹

          2. desired_caps.yml文件中的配置需要根據(jù)自己的app配置修改

          3. 代碼中沒有包含自動連接手機(jī)的部分代碼,所以執(zhí)行項(xiàng)目前需要先手動使用adb連接上手機(jī)(有條件的,可以自己把這部分代碼寫一下,然后再運(yùn)行項(xiàng)目之前調(diào)用一下adb連接手機(jī)的方法即可)

          4. 項(xiàng)目目錄中的allure_report, allure_results目錄是系統(tǒng)自動生成的,一個存放最終的測試報告,一個是存放報告的依賴文件,如果你接觸過allure應(yīng)該知道

          5. log目錄下存放了appium server啟動之后運(yùn)行的日志

          5. 效果展示


          6. 最后

          上述只是初步實(shí)現(xiàn)了這樣一個多手機(jī)并發(fā)的需求,并沒有寫的很詳細(xì),比如,讓項(xiàng)目更加的規(guī)范還需要引入PO設(shè)計(jì)模式,這里沒寫這部分,其次base_page.py中還可以封裝更多的方法,上述代碼中也只封裝了幾個方法,如果真正的把這個并發(fā)引入到項(xiàng)目中肯定還需要完善的,但是需要添加的東西都是照葫蘆畫瓢了,有問題多思考!

          原文鏈接:https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-mult.html


          重磅消息: 由狂師老師授課主講的「全棧測試開發(fā)技能訓(xùn)練營」已經(jīng)正式開課了,課程內(nèi)容非常值得推薦!課程大綱:重磅消息 | 2021年最新全棧測試開發(fā)技能實(shí)戰(zhàn)指南(第2期)


          推薦閱讀
          官宣了,測試大神必備的"三把利劍"!

          Python全棧測試開發(fā),下周六,不見不散!

          微信小程序和公眾號H5自動化測試技巧,趕緊GET!

          重磅消息 | 2021年最新全棧測試開發(fā)技能實(shí)戰(zhàn)指南(第2期)

          月薪40K+銀行測試經(jīng)理,自動化測試實(shí)踐經(jīng)驗(yàn)分享


          END

          所有原創(chuàng)文章
          第一時間發(fā)布至此公眾號「測試開發(fā)技術(shù)」

          長按二維碼/微信掃碼  添加作者


          閱讀原文

          瀏覽 75
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本精品视频网站 | 亚洲v天堂 | 亚洲精品国产精品乱码不卡√香蕉 亚洲日韩一区二区三区四区丨高清 | 精品一线在线 | 成人操B视频 |