做android自动化的时候,启动一个appium服务,只能匹配一个手机去自动化执行。有时候想同一套代码,可以在不同的手机上执行,测下app在不同手机上兼容性。 这就需要启动多个appium服务了,并且android设备和appium要一一对应才行。

一、实现需求

1.为每一台设备准备两个端口号port和bp-port

  • 每一台手机需要在PC端启动2个线程,每个线程都需要1个端口。

  • 端口号前提没被占用,建议指定1000以上端口号。

2.检测端口是否被占用

对于上面的端口号可以利用socket连接来检测是否被其他占用。如果我们的检测程序顺利建立连接,意味着该端口有系统其他程序占用;如果检测程序无法建立连接,报错,这意味着该端口无人使用,我们拿来备用

3.利用多线程去分别为每一台设备启动2个线程

  • 一个线程用于运行测试脚本,一个线程用于运行Appium Server。

  • 先启动Appium Server最后再运行脚本。由于Appium Server启动慢,所以待该线程启动一段时间后,方可启动测试脚本。

#Appium Server启动命令

appium -p {port} -bp {bp_port} --device-name {device_name}' --platform-version {platform_version} --log {log_file} --log-level info' --log-timestamp

二、代码实现

SingleThreadScript.py
from appium import webdriver

class SingleThreadScript:
    def __init__(self,deviceName, platformVersion, post):
        self.desired_caps = {
            'platformName': 'Android',  # 被测手机是安卓
            'platformVersion': platformVersion,  # 手机安卓版本
            'deviceName': deviceName,  # 设备名,安卓手机可以随意填写
            'appPackage': 'com.mobivans.onestrokecharge',  # 启动APP Package名称
            'appActivity': 'com.mobivans.onestrokecharge.activitys.MainActivity',  # 启动Activity名称
            'unicodeKeyboard': True,  # 使用自带输入法,输入中文时填True
            'resetKeyboard': True,  # 执行完程序恢复原来输入法
            'noReset': True,  # 不要重置App
            'newCommandTimeout': 60000,
            'automationName': 'UiAutomator2'
        }

        # 连接Appium Server,初始化自动化环境
        self.driver = webdriver.Remote(f'http://127.0.0.1:{post}/wd/hub', self.desired_caps)


    def business(self):
        # 设置缺省等待时间
        self.driver.implicitly_wait(5)

        #点击 加号
        self.driver.find_element_by_xpath(
            "//android.widget.LinearLayout[@resource-id='com.mobivans.onestrokecharge:id/main_write1']/android.widget.ImageView").click()

        #点击 日常 按钮
        self.driver.find_element_by_xpath("//android.support.v7.widget.RecyclerView[@resource-id='com.mobivans.onestrokecharge:id/add_rv_cateGrid']/android.widget.LinearLayout[1]/android.widget.TextView").click()

        #添加备注
        self.driver.find_element_by_id("com.mobivans.onestrokecharge:id/add_et_remark").send_keys("购买了毛衣")

        #输入 金额 45
        self.driver.find_element_by_id("com.mobivans.onestrokecharge:id/keyb_btn_4").click()
        self.driver.find_element_by_id("com.mobivans.onestrokecharge:id/keyb_btn_5").click()

        #点击 完成 按钮
        self.driver.find_element_by_id('com.mobivans.onestrokecharge:id/keyb_btn_finish').click()
CloudTestPlatform.py
import os
import socket
import threading
import time
from SingleThreadScript import SingleThreadScript

class CloudTestPlatform:
    def get_devices_info(self):
        devices = os.popen("adb devices").read().strip().split("\n")
        #从读取出来的结果列表中去除下标为0的元素
        devices.pop(0)

        port = 5000
        bp_port = 8000

        #用例保存处理后的设置信息,格式如[(设备一,版本,port, bp_port),(设备二,版本,port, bp_port)]
        device_information = []

        for device in devices:
            #取出设置名字
            device_name = device.split("\t")[0].strip()
            #根据上面取出的设备名称 取出设备版本
            platform_version = os.popen(f"adb -s {device_name} shell getprop ro.build.version.release").read().replace("\n","")

            #从当前系统查找两个没有被占用的端口 获取port bpport两个端口
            port = self.find_port(port)
            bp_port = self.find_port(bp_port)

            #将每一次设置的循环信息进行保存成一个元组
            device_information.append((device_name, platform_version, port, bp_port))

            #注意:这里要设置两个端口每次要自增加一,因为本次端口一旦使用,下次就不能在使用,那么从下一个开始查找
            port += 1
            bp_port += 1

        return device_information

    #判断当前系统中该端口是否可用
    def find_port(self, port):
        #self.check_port(port)返回的是true或者false,true代表占用,如果占用则在当前端口的基础上加一,继续判断
        while self.check_port(port):
            port += 1
        return port

    def check_port(self, port):
        #使用tcp连接, 构造一个socket连接对象,
        #参数 AF_INET 表示该socket网络层使用IP协议
        # 参数 SOCK_STREAM 表示该socket传输层使用tcp协议
        conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            #判断当前的这个端口是否能连接上,如果连接上证明端口有人在用了,连接不上会抛出异常需要使用socket.error捕获,证明此端口可用
            conn.connect(('127.0.0.1', port))
            #关闭连接 socket.SHUT_RDWR指断掉读写
            conn.shutdown(socket.SHUT_RDWR)
            return True
        except socket.error:
            return False

    #定义一个启动appium servie的方法
    # appium -p port -bp bp_port --device-name device_name --platform-version platform_version
    def start_appiumservice(self, device_name, platform_version, port, bp_port):
        log_file = os.path.join(os.getcwd(), f'report/{device_name}_appium.log')
        cmd = f'appium -p {port} -bp {bp_port} --device-name {device_name}' \
              f' --platform-version {platform_version} --log {log_file} --log-level info' \
              f' --log-timestamp'
        os.system(cmd)

    #构造测试方法
    def start_thread_test(self):
        devices = self.get_devices_info()
        # print("设备信息", devices)
        #同时取出循环次数和值
        for device_info in devices:

            #[('127.0.0.1:21503', '7.1.2', 10000, 20000)]
            # 注意这里一定不要掉错了方法,client线程要调用测试脚本的测试方法
            # server线程要调用appium server启动的方法
            server_thread = threading.Thread(target=self.start_appiumservice, args=(*device_info,))
            server_thread.start()


            ost = SingleThreadScript(*device_info[:3])
            client_thread = threading.Thread(target=ost.business)


            time.sleep(10)
            client_thread.start()


if __name__ == '__main__':
    print(CloudTestPlatform().start_thread_test())