欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

python和Appium移動端多設(shè)備自動化測試框架實現(xiàn)

 更新時間:2022年04月26日 10:24:23   作者:888米兔  
這篇文章主要介紹了python和Appium移動端多設(shè)備自動化測試框架實現(xiàn),基于pytest和Appium框架,支持Android和iOS功能自動化的測試框架的相關(guān)內(nèi)容,需要的小伙伴可以參考一下

前言:

本篇文章主要介紹基于pytest和Appium框架,支持Android和iOS功能自動化的測試框架。同時該框架支持多設(shè)備測試,并利用allure庫,生成可視化測試報告。本框架主要涉及的內(nèi)容包括:python3、pytest、appium、allure等,此處已假設(shè)你具備相應(yīng)的基礎(chǔ)知識,同時已有可以隨時運行的測試環(huán)境(iOS設(shè)備的測試只能在Mac系統(tǒng)中執(zhí)行,沒有Mac的朋友們,可以看看不執(zhí)行)

一、流程圖

本部分內(nèi)容先從自動化測試的整體流程開始介紹,目的是希望大家在開始動手去實現(xiàn)框架之前,對測試過程做到清晰明了,這樣在實現(xiàn)過程中,才能幫助我們無論何時,都不會迷茫和不知所措。才能讓我們知道從何開始,如何優(yōu)化以及拓展。

那么我們先來看下面這張流程圖: 

以上是本文所介紹框架的核心流程圖,上圖已經(jīng)展現(xiàn)了框架的核心流程,所以在接下來的講述中,大家可以參考該圖進行理解和優(yōu)化。

二、appium服務(wù)

在開始我們的測試之前,還有很多的工作需要我們?nèi)ヌ幚?,這其中最重要,也是我們開始的第一步,就是開啟appium的本地服務(wù)。關(guān)于appium的實現(xiàn)原理,本文不作過多的講解,小編會抽空進行補充,屆時也希望大家能及時關(guān)注。心急的小伙伴也可以自行百度哦~這里僅介紹啟動服務(wù)的方法。

根據(jù)appium官方的介紹,我們可以通過下面的方式來啟動appium服務(wù):

/usr/local/bin/appium -a ip -p port

也就是我們在啟動appium時,指定ip和端口,一般來說,本地ip使用127.0.0.1即可,官方默認端口為4723,我們也可以修改成自己想要的端口,只要保證使用的端口沒有被其他服務(wù)占用即可。(小技巧:如果你不知道自己appium安裝路徑,可通過which appium來幫你找到)

啟動服務(wù)之后,一般我們可以通過訪問這個連接來驗證服務(wù)是否正常:http://127.0.0.1:4723/wd/hub/status??烧TL問并返回json格式數(shù)據(jù)時,則說明服務(wù)已正常啟動。

但事實上,并不是每次啟動都可以順利進行,總會有一些意外的情況發(fā)生。比如說端口被占用。遇到這種情況我們也不必驚慌,做好應(yīng)對即可。那么今天我們就上述的過程結(jié)合python,把它實現(xiàn)出來。

上面的過程,用python來實現(xiàn),其實很簡單,我們這里選擇使用python中的subprocess庫來執(zhí)行命令,從而達到我們預(yù)期。

代碼片段如下:

import subprocess
import abc
import socket
class Driver:
	__metaclass__ = abc.ABCMeta
	self._host = '127.0.0.1'
	@abc.abstractmethod
	def connect_appium(self, port, n)
		"""
		待實現(xiàn)的連接設(shè)備方法
		"""
		return
	def start_appium(self, port):
		server = self.get_local_server_path()
        host = readConfig.ReadConfig().get_commend("host")
        log_path = root_path + '/result/log'
        cmd = "%s -a %s -p %s" % (server, host, str(port))
        if self.check_port(int(port)):
            subprocess.Popen(cmd, shell=True, stdout=open('%s/AppiumServer%s.log' % (log_path, port), 'w'))
            log.logger.info('%s/AppiumServer%s.log' % (log_path, port))
        else:
            log.logger.info("關(guān)閉被占用的端口號:%s" % str(port))
            self.kill_appium()
            log.logger.info("端口釋放完畢!啟動Appium-server,端口號:%s" % str(port))
            subprocess.Popen(cmd, shell=True, stdout=open('%s/AppiumServer%s.log' % (log_path, port), 'w'))
            log.logger.info("Appium日志信息存儲地址: %s/AppiumServer%s.log" % (log_path, port))
    def check_port(self, port):
        """
        檢查端口占用情況
        :param port:
        :return:
        """
        try:
            host = local_read_config.get_commend("host")
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            log.logger.info(s.connect((host, port)))
            s.shutdown(2)
        except OSError:
            log.logger.info("端口:%s 可用" % str(port))
            return True
        else:
            log.logger.info("端口:%s 已被占用" % str(port))
            return False

以上代碼,會在啟動appium服務(wù)之前,通過socket檢查本地端口是否被占用,若被占用,則先釋放端口,然后再啟動服務(wù),否則直接啟動服務(wù)。

至此,服務(wù)啟動完成,接下來就可以開始連接測試設(shè)備。

三、連接測試設(shè)備

當我們啟動好appium服務(wù)后,就可以開始鏈接測試設(shè)備了。因為我們要同時支持Android和iOS的設(shè)備,所以我們先來定義一個Driver類,用來封裝一些共有屬性及方法,然后讓Android和iOS分別繼承它。

appium對于設(shè)備的連接,官方給我們提供了詳細的方法事例:

# Android environment
from appium import webdriver
desired_caps = dict(
    platformName='Android',
    platformVersion='10',
    automationName='uiautomator2',
    deviceName='Android Emulator',
    app=PATH('../../../apps/selendroid-test-app.apk')
)
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
el = self.driver.find_element_by_accessibility_id('item')
el.click()
# iOS environment
from appium import webdriver
desired_caps = dict(
    platformName='iOS',
    platformVersion='13.4',
    automationName='xcuitest',
    deviceName='iPhone Simulator',
    app=PATH('../../apps/UICatalog.app.zip')
)
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
el = self.driver.find_element_by_accessibility_id('item')
el.click()

在以上兩個示例中,我們發(fā)現(xiàn),鏈接設(shè)備使用的都是同一個方法,但不同的設(shè)備需要傳入不同的參數(shù),

下面便是鏈接的關(guān)鍵: 

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

既然我們找到了共性,那么就可以對該部分內(nèi)容進行一番改造,讓它來自動完成一些它可以完成的事情。那么首先,我們來看一下,再鏈接設(shè)備的過程中,我們到底做了些什么。

從上面的代碼不難看出,每臺設(shè)備連接都可以看成兩步:第一步配置連接參數(shù)、第二步請求連接。

那么我們就可以封裝一些類和方法,來完成我們想要分端操作的想法了。其實并不困難,我們可以分別寫兩個類AndroidDriver和IOSDriver,都繼承自Driver,然后實現(xiàn)設(shè)備連接的方法。

具體實現(xiàn)可參考下面的內(nèi)容:

from Driver import Driver
class AndroidDriver(Driver):
    def __init__(self):
        self.driver = None

    def get_desired_caps(self):
        """
        實現(xiàn)繼承的抽象類方法;獲取鏈接設(shè)備的配置信息
        返回設(shè)備配置信息
        :return:desired_caps
        """
        desired_list = []
        package = local_read_config.get_value("ANDROID", "package")
        activity = local_read_config.get_value("ANDROID", "activity")
        devices_info = self.update_devices_info()
        for i in range(len(devices_info)):
            udid = devices_info[i].get("udid")
            device_name = devices_info[i].get("devices_name")
            platform_version = devices_info[i].get("version")
            system_port1 = 8200 + 2 * i
            desired_caps = {
                "platformName": "Android",
                "platformVersion": platform_version,
                "appPackage": package,
                "appActivity": activity,
                "deviceName": device_name,
                "automationName": "uiautomator2",
                "udid": udid,
                "systemPort": system_port1,
                "newCommandTimeout": 3000,
                # "adbExecTimeout": 50000
            }
            desired_list.append(desired_caps)
        return desired_list
    def connect_appium(self, port, n):
        """
        根據(jù)傳入的port,啟動appium服務(wù)
        :param port:
        :param n:
        :return:
        """
        set_adb_path()
        desired_caps = self.get_desired_caps()
        try:
            self.driver = webdriver.Remote("%s:%s/wd/hub" % (super()._remote_url, str(port)), desired_caps[n])
            return self.driver
        except WebDriverException:
            raise WebDriverException
        except ConnectionError:
            raise ConnectionError

上面的方法主要做了兩件事情,首先收集連接設(shè)備需要的desired_caps信息,然后是連接設(shè)備。需要注意的是,因為我們這個框架是支持多個測試設(shè)備同時連接的,所有這里我們把收集到的每臺測試設(shè)備的desired_caps信息放到了一個數(shù)組中,并且在連接設(shè)備的時候,我們通過appium服務(wù)的端口號和數(shù)組下標兩個值,來確定,每臺測試設(shè)備連接的appium服務(wù)。

小提示:一個appium服務(wù)無法同時連接多個手機,但是我們希望能同時連接多個測試手機,并且同時在這連接的多個手機上進行測試,所以我們這里啟動了多個appium服務(wù),并指定了每個啟動的服務(wù)端口號。因此我們只需要將端口號和設(shè)備信息對應(yīng)上即可。

至此,啟動服務(wù)和測試設(shè)備連接的實現(xiàn)就結(jié)束了,接下來就是對元素的操作了。那么我們一起來看一下,關(guān)于Element的那些事情。

四、元素封裝

眾所周知,元素的操作依賴于元素查找。

舉個常見的例子:我想百度搜索一個關(guān)鍵詞,那么我首先要找到搜索框,才能輸入關(guān)鍵詞,然后找到搜索按鈕,并點擊搜索。這就是我們要做的。

常見的定位元素的方法有:ID、XPATH、CLASSNAME、NAME、PREDICATE等,selenium提供了對應(yīng)的方法,我們這里也不做過多的封裝,大家可以直接使用,也可以像我這樣,把一些常見的定位方式封裝成一個統(tǒng)一的方法,實現(xiàn)如下:

    def get_element(self, element_id):
        """
        獲取指定頁面的元素路徑數(shù)據(jù)
        :param element_id: 元素ID
        :return: 獲取的元素對象
        """
        element_type = self.page.get(element_id).get("pathType")
        element_value = self.page.get(element_id).get("pathValue")
        element = None
        if element_type == "ID":
            element = self.driver.find_element_by_id(element_value)
        elif element_type == "CLASSNAME":
            element = self.driver.find_element_by_class_name(element_value)
        elif element_type == "XPATH":
            element = self.driver.find_element_by_xpath(element_value)
        elif element_type == "NAME":
            element = self.driver.find_element_by_name(element_value)
        elif element_type == "ACB_ID":
            element = self.driver.find_element_by_accessibility_id(element_value)
        elif element_type == "PREDICATE":
            element = self.driver.find_element_by_ios_predicate(element_value)
        return element

大家自己選擇是否進行封裝,正常調(diào)用selenium的方法也是OK噠。

同樣的道理,我們還可以封裝一些常用的操作,比如滑動屏幕,鍵盤操作等。

分端元素操作

因為我們分別接入了Android和iOS,那么它們的操作,各有不同之處,我們可以將各自的特色操作分別集中到一個單獨的AndroidElement類和iOSElement類中,這樣在后面使用的時候,我們直接繼承這兩個類就可以,并且從結(jié)構(gòu)上看,也比較清晰。

比如同樣是滑動屏幕,swipe在Android和iOS系統(tǒng)上的表現(xiàn)就不一致,因此我們就選擇了其他方法:

AndroidElement:

    def swipe_to_up(self):
        """
        向上劃,頁面滾動到最下方
        :return:
        """
        width = self.driver.get_window_size()["width"]
        height = self.driver.get_window_size()["height"]
        self.driver.swipe(width / 2, height * 3 / 5, width / 2, height / 5, duration=500)

iOSElement:

    def swipe_to_up(self):
        """
        向上滑動
        :return:
        """
        self.driver.execute_script('mobile: swipe', {'direction': 'up'})

以上只是一個小例子,只是想說明,如果有這樣的操作差異,我們可以將它們分開處理,這樣會顯得邏輯更清晰。

有了上面的實現(xiàn),我們就只需要寫測試的腳步就可以。寫腳本部分的內(nèi)容就先略過,不做詳細描述,畢竟不同的業(yè)務(wù)需求場景,都有其獨特的腳本邏輯。凡事萬變不離其宗,元素還是那個元素,操作還是那些操作,就讓大家自己去盡情發(fā)揮吧。

那么,一切準備就緒,就差讓我們的程序跑起來了。接下來就讓我們來看看,如何讓我們的測試同時在多個連接的測試設(shè)備上進行測試。

五、運行

因為我們的測試是通過pytest來執(zhí)行的,所以pytest的所有執(zhí)行參數(shù)都是可以正常使用的。而我們,也只是利用pytest的main函數(shù)來完成本次執(zhí)行。唯一不同的是,為了滿足不同設(shè)備同時進行測試,我們?yōu)槊恳慌_設(shè)備的測試,都創(chuàng)建了一個進程。每一個進程都包含了上述完整的流程。選擇進程而非線程的原因也很簡單,相信大家也都知道,進程和線程的關(guān)系吧,在同一個進程中的線程資源是共享的。而在我們看來,每一臺設(shè)備的測試都應(yīng)該是獨立的、互不干擾的,所以我們選擇進程而非線程。

具體實現(xiàn)如下:

from multiprocessing import Process
import pytest
import time
import os, re
import subprocess
from appiums.common import read_files
from appiums.driver.iOSDriver import IOSDriver
from driver.androidDriver import AndroidDriver
from driver import Driver
from elements import Element

class Run(Process):
    def __init__(self, name, args):
        super(Run, self).__init__()
        self.name = name
        self.args = args
        self.root_path = os.getcwd()
        self.device_name = re.sub('[\']', '', str(args[2].get("deviceName")).replace(" ", "_"))
    def run_test(self):
        """
        執(zhí)行測試用例
        :return:
        """
        pytest.main([
                     '--alluredir', '%s/result/data/%s' % (self.root_path, self.device_name)])
        time.sleep(2)
    def generate_report(self):
        """
        整合測試報告到項目根目錄下的result/report目錄下
        :return: none
        """
        cmd = "allure generate %s/result/data/%s -o %s/result/report/%s --clean" \
              % (self.root_path, self.device_name, self.root_path, self.device_name)
        stdout = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, text=True)
        log.logger.info("測試報告查看路徑:%s" % str(stdout.stdout.readlines()[0]).split(" ")[-1][:-1])
    def get_environment_info(self):
        """
        獲取測試環(huán)境的信息
        :return:
        """
        env = {
            "測試平臺": self.args[2].get("platformName"),
            "設(shè)備名稱": self.device_name,
            "設(shè)備系統(tǒng)版本": self.args[2].get("platformVersion"),
            "設(shè)備udid": self.args[2].get("udid"),
            "應(yīng)用名稱": self.args[2].get("bundleId") if str(self.args[2].get("platformName")).lower() == 'ios' else self.args[2].get("appPackage"),
        }
        return env
    def run(self):
        """
        執(zhí)行線程中的任務(wù)
        :return:
        """
        Driver.Driver().start_appium(self.args[0])
        time.sleep(5)
        self.set_driver()
        time.sleep(1)
        self.run_test()
        time.sleep(1)
        read_files.set_environment(self.device_name, self.get_environment_info())
        time.sleep(1)
        self.generate_report()

def main(desired_caps):
    """
    開啟測試進程執(zhí)行測試
    """
    list_p = []
    process_num = len(desired_caps)
    if process_num > 0:
        for a in range(process_num):
            port1 = 4723 + 2 * a
            p = Run('測試進程-%s' % str(port1), args=(port1, a, desired_caps[a]))
            p.start()
            log.logger.info("設(shè)備%s在進程 %s 上進行測試, 進程ID:%s" % (desired_caps[a].get("deviceName"), p.name, p.pid))
            list_p.append(p)
        for b in list_p:
            b.join()
        Driver.Driver().kill_appium()
    else:
        log.logger.error("沒有設(shè)備可進行測試,請重新連接設(shè)備后嘗試!")
        exit(-1)

def android_run():
    caps = AndroidDriver().get_desired_caps()
    main(caps)

def ios_run():
    caps = IOSDriver().get_desired_caps()
    main(caps)

到此這篇關(guān)于python和Appium移動端多設(shè)備自動化測試框架實現(xiàn)的文章就介紹到這了,更多相關(guān)python和Appium自動化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于asyncio 異步協(xié)程框架實現(xiàn)收集B站直播彈幕

    基于asyncio 異步協(xié)程框架實現(xiàn)收集B站直播彈幕

    本文給大家分享的是基于asyncio 異步協(xié)程框架實現(xiàn)收集B站直播彈幕收集系統(tǒng)的簡單設(shè)計,并附上源碼,有需要的小伙伴可以參考下
    2016-09-09
  • Win10操作系統(tǒng)中PyTorch虛擬環(huán)境配置+PyCharm配置

    Win10操作系統(tǒng)中PyTorch虛擬環(huán)境配置+PyCharm配置

    本文主要介紹了Win10操作系統(tǒng)中PyTorch虛擬環(huán)境配置+PyCharm配置,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Python while、for、生成器、列表推導(dǎo)等語句的執(zhí)行效率測試

    Python while、for、生成器、列表推導(dǎo)等語句的執(zhí)行效率測試

    這篇文章主要介紹了Python while、for、生成器、列表推導(dǎo)等語句的執(zhí)行效率測試,本文分別用兩段程序測算出了各語句的執(zhí)行效率,然后總結(jié)了什么情況下使用什么語句優(yōu)先使用的語句等,需要的朋友可以參考下
    2015-06-06
  • 日常整理python執(zhí)行系統(tǒng)命令的常見方法(全)

    日常整理python執(zhí)行系統(tǒng)命令的常見方法(全)

    本文是小編日常整理的些關(guān)于python執(zhí)行系統(tǒng)命令常見的方法,比較全面,特此通過腳本之家這個平臺把此篇文章分享給大家供大家參考
    2015-10-10
  • Python threading模塊condition原理及運行流程詳解

    Python threading模塊condition原理及運行流程詳解

    這篇文章主要介紹了Python threading模塊condition原理及運行流程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-10-10
  • 將Jupyter?Notebook(.ipynb)文件轉(zhuǎn)換為Python(.py)文件的3種方法

    將Jupyter?Notebook(.ipynb)文件轉(zhuǎn)換為Python(.py)文件的3種方法

    大多數(shù)數(shù)據(jù)科學(xué)在線課程都把Jupyter Notebook作為教學(xué)媒介,這是因為初學(xué)者在Jupyter Notebook的單元格中編寫代碼,比編寫包含類和函數(shù)的腳本更容易,這篇文章主要給大家介紹了關(guān)于將Jupyter?Notebook(.ipynb)文件轉(zhuǎn)換為Python(.py)文件的3種方法,需要的朋友可以參考下
    2023-10-10
  • 初識python的numpy模塊

    初識python的numpy模塊

    這篇文章主要介紹了初識python的numpy模塊,Numpy基于更加現(xiàn)代化的編程語言--python,python憑借著開源、免費、靈活性、簡單易學(xué)、工程特性好等特點風(fēng)靡技術(shù)圈,已經(jīng)成為機器學(xué)習(xí)、數(shù)據(jù)分析等領(lǐng)域的主流編程語言,需要的朋友可以參考下
    2022-05-05
  • 詳解appium+python 啟動一個app步驟

    詳解appium+python 啟動一個app步驟

    這篇文章主要介紹了詳解appium+python 啟動一個app步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • pandas.DataFrame中提取特定類型dtype的列

    pandas.DataFrame中提取特定類型dtype的列

    本文主要介紹了pandas.DataFrame中提取特定類型dtype的列,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • 詳解Python中的Descriptor描述符類

    詳解Python中的Descriptor描述符類

    這里我們將來詳解Python中的Descriptor描述符類,包括定義描述符并展示如何調(diào)用描述符,需要的朋友可以參考下
    2016-06-06

最新評論