appium同时运行多台设备

前不久部门老大给了一个同时运行多台手机设备的任务,运行脚本时不用手动启appium服务代码自动启。现记录下来实现过程及结果。
1、安装pyyaml及tomorrow
pip install pyyaml (我这已安装过)
在这里插入图片描述
pip install tomorrow (我这已安装过)
在这里插入图片描述
2、安装命令行版appium
2.1安装命令
npm --registry https://registry.npm.taobao.org install -g appium
2.2验证安装是否成功
cmd里输入appium
在这里插入图片描述

3、编写设备配置文件,创建devices.yaml文件

- desc: 设备_vivo,端口_4723
  port: 4723
  des:
    platformName: Android
    deviceName: 496ba836
    udid: 496ba836
    platformVersion: !!str 8.1.0
    appPackage: com.tckk.kk
    appActivity: com.tckk.kk.ui.start.SplashActivity
    noReset: !!bool True    #强制转换数据类型
    unicodeKeyboard: !!bool True
    resetKeyboard: !!bool True

- desc: 设备_leidian,端口_4725
  port: 4725
  des:
    platformName: Android
    deviceName: emulator-5554
    udid: emulator-5554
    platformVersion: !!str 5.1.1
    appPackage: com.tckk.kk
    appActivity: com.tckk.kk.ui.start.SplashActivity
    noReset: !!bool True
    unicodeKeyboard: !!bool True
    resetKeyboard: !!bool True

4、编写启动服务py文件,创建start_appium.py文件

import os,time
import subprocess

def StartAppium(host="127.0.0.1",port=4723):
    """
    启动appium服务
    :param host: 访问地址
    :param port: 端口号
    :return:
    """
    a = os.popen('netstat -ano | findstr "%s" '% port)
    time.sleep(2)
    t1 = a.read()
    if "LISTENING" in t1:
        print("appium服务已经启动:%s" % t1)

    else:
        # 启动appium服务,指定bp端口号
        bootstrap_port = str(port + 1)
        # 把在cmd弹窗输入的命令,直接写到这里
        cmd = 'start /b appium -a %s -p %s -bp %s' % (host, port, bootstrap_port)
        # 去掉 “/b”,即可以打开cmd弹窗运行
        # cmd = 'start /b appium -a %s -p %s -bp %s' % (host, port, bootstrap_port)

        # 打印输入的cmd命令,及时间
        print("%s at %s " % (cmd, time.ctime()))

        subprocess.Popen(cmd, shell=True, stdout=open('./report' + str(port) + '.log', 'a'),
                         stderr=subprocess.STDOUT)

5、封装元素定位方法,创建find_element.py文件

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class AppPage():

    def __init__(self,driver):
        self.driver = driver

    def find(self,locator):
        """
        单个元素定位方法封装
        :param locator: {"name":"xxx","by":"id","value":"xxx"}
        :return: 定位方法
        """
        print("正在通过 %s 属性 %s:定位>>%s"%(locator["by"],locator["value"],locator["name"]))
        #text属性定位
        if "text" in locator["by"]:
            t_value = "//*[@text='%s']"%locator["value"]
            t_xpath = ("xpath",t_value)
            element = WebDriverWait(self.driver,30,0.5).\
                until(EC.presence_of_element_located(t_xpath))
        #content-desc属性定位
        elif "desc" in locator["by"]:
            element = WebDriverWait(self.driver,30,0.5).\
                until(lambda x: x.find_element_by_accessibility_id(locator["value"]))
        #uiautomator方法定位,value传值如:'text("登录")'
        elif "uiautomator" in locator["by"]:
            element = WebDriverWait(self.driver,30,0.5).\
                until(lambda x: x.find_element_by_android_uiautomator(locator["value"]))
        #其它属性定位方法:id,xpath,class
        else:
            e_locator = (locator["by"],locator["value"])
            element = WebDriverWait(self.driver,30,0.5).\
                until(EC.presence_of_element_located(e_locator))
        return element

6、我的页面元素文件,创建my_page.py文件

class MyCodeLoginPage:
    """
    我的-验证码登录页面元素
    """
    my = {"name":"我的","by":"text","value":"我的"}
    phone = {"name":"我的-手机号码","by":"text","value":"请输入11位手机号"}
    code = {"name":"我的-验证码","by":"id","value":"com.tckk.kk:id/edit_identifying_code"}
    code_button ={"name":"我的-验证码按钮","by":"id","value":"com.tckk.kk:id/identifying"}
    login = {"name":"我的-登录按钮","by":"id","value":"com.tckk.kk:id/login"}
    pwd_login = {"name":"我的-密码登录","by":"id","value":"com.tckk.kk:id/pwd_login"}

class MyPwdLoginPage:
    """
    我的-密码登录页面元素
    """
    phone = {"name":"我的-手机号码","by":"text","value":"请输入11位手机号"}
    pwd = {"name":"我的-密码","by":"id","value":"com.tckk.kk:id/input_pwd"}
    login_button = {"name":"我的-登录","by":"id","value":"com.tckk.kk:id/login"}
    forget_pwd = {"name":"我的-忘记密码","by":"id","value":"com.tckk.kk:id/forget_pwd"}
    back = {"name":"我的-返回","by":"id","value":"com.tckk.kk:id/back"}

7、测试代码,创建test_login1.py文件

import unittest
from appium import webdriver
from common.find_element import AppPage
from common.read_devices import app_device_cfg
from page.my_page import MyCodeLoginPage,MyPwdLoginPage
import time

#读取手机配置参数
devices = ["leidian", "vivo"]
a = []
for i in devices:
    cfg = app_device_cfg(device=i)
    a.append(cfg)
phone = ["15700000000","19911111111"]

class MyTestCase(unittest.TestCase):
    """
    快快登录测试
    """

    def setUp(self):
        """
        初始化相关环境
        :return:
        """
        #启动app
        element_1 = a.pop()
        self.driver = webdriver.Remote("http://127.0.0.1:%s/wd/hub" % element_1["port"], element_1["des"])
        self.element = AppPage(self.driver)
        self.phone = phone.pop()
        print(element_1)
        self.driver.implicitly_wait(30)


    def tearDown(self):
        self.driver.quit()


    def test_login(self):
        """
        登录失败案例
        :return:
        """
        time.sleep(5)
        self.element.find(MyCodeLoginPage.my).click()
        self.element.find(MyCodeLoginPage.pwd_login).click()
        time.sleep(5)
        self.element.find(MyPwdLoginPage.phone).send_keys(self.phone)
        self.element.find(MyPwdLoginPage.pwd).send_keys("123456")
        self.element.find(MyPwdLoginPage.login_button).click()
        #断言登录失败,还在登录页面
        text = self.element.find(MyPwdLoginPage.forget_pwd).text
        self.assertEqual(text,"忘记密码?")



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

8、运行脚本,创建run_all_case.py文件

from tomorrow import threads
from common.start_appium import StartAppium
from case.test_login1 import MyTestCase
import unittest

#启动服务
host = "127.0.0.1"
for i in range(2):
    port = 4723 + 2*i
    StartAppium(host,port)
# #运行脚本,没有使用unittest框架
# devices = ["leidian","vivo"]
# pohe = ["15700000000","19911111111"]
# for i in range(len(devices)):
#     run_app(deviceName=devices[i],pohe=pohe[i])

#unittest用例
# pohe = ["15777936597","19960378307"]


@threads(2)    #添加线程
def run():
    """
    读取用例,运行脚本
    :return:
    """
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase("test_login"))
    runner = unittest.TextTestRunner()
    runner.run(suite)

if __name__ == "__main__":
    #循环运行
    for i in range(2):
        print(i)
        run()

<think>嗯,用户想用Python和uiautomator2实现多台设备同时运行相同的操作。首先我得回忆一下uiautomator2的基本用法,然后考虑多设备并行的问题。 用户之前可能已经知道单设备怎么操作,但现在需要扩展到多设备多设备并行通常需要同时连接多个设备,每个设备有独立的会话。这时候可能需要多线程或者多进程来处理每个设备的连接和操作。 我记得uiautomator2是通过设备的序列号来区分的,所以每个设备应该有一个唯一的serial。用户需要先获取所有连接的设备列表,然后为每个设备启动一个线程或进程,执行相同的步骤。 不过,线程和进程之间如何管理呢?比如,启动多个线程,每个线程对应一个设备,然后在这些线程中执行操作。Python的threading模块可以用于多线程,但需要注意GIL的问题,不过对于IO密集型任务,多线程应该没问题。 另外,用户提到的“相同操作”可能需要将操作步骤封装成一个函数,然后让每个设备线程调用这个函数。这样代码结构会更清晰,也便于维护。 还要考虑设备的连接和初始化。每个线程里可能需要用uiautomator2的Device对象,传入对应的serial,然后执行初始化操作,比如连接设备、安装必要的应用等。 用户可能还需要处理设备之间的同步问题,或者异常处理,比如某个设备断开连接时不影响其他设备。这时候需要在每个线程中加入异常捕获,确保一个设备的错误不会导致整个程序崩溃。 另外,参考之前用户提到的Appium多设备并行的例子,他们是通过启动多个Appium Server,每个设备对应一个Server。但uiautomator2可能不需要这样,因为它直接通过adb连接设备,可能更简单一些。不过需要确认uiautomator2是否支持同时连接多个设备而无需额外配置。 可能需要测试一下,比如同时连接两个设备,分别执行点击操作,观察是否都能成功。如果有问题,可能需要调整代码,比如使用不同的端口或者不同的serial来区分设备。 最后,代码示例部分需要展示如何遍历设备列表,启动线程,执行操作。可能还需要处理设备断开的情况,以及如何关闭连接。 总之,步骤大概是:获取设备列表,为每个设备创建线程,在每个线程中初始化设备并执行操作,处理异常,最后等待所有线程完成。</think>以下是通过Python和uiautomator2实现多设备并行执行相同操作的具体实现方法: ### 一、实现原理 1. 通过adb获取已连接设备列表,每个设备使用独立线程控制 2. 使用uiautomator2的`Device`对象绑定不同设备序列号 3. 通过线程池实现真正的并行操作(非串行) ### 二、实现步骤 #### 1. 环境准备 ```python import uiautomator2 as u2 from concurrent.futures import ThreadPoolExecutor ``` #### 2. 设备连接管理 ```python def get_devices(): """获取已连接设备列表""" import subprocess output = subprocess.check_output(["adb", "devices"]).decode() return [line.split()[0] for line in output.splitlines() if "\tdevice" in line] ``` #### 3. 核心操作封装 ```python def device_operation(serial): """单个设备操作模板""" d = u2.connect(serial) # 绑定设备序列号 # 在此处编写需要并行执行的通用操作 d.app_start("com.example.package") # 示例:启动应用 d(text="Login").click() # 示例:点击登录按钮 d.screenshot(f"{serial}_login.png") # 保存设备专属截图 ``` #### 4. 并行执行框架 ```python def parallel_execute(max_workers=5): devices = get_devices() with ThreadPoolExecutor(max_workers=max_workers) as executor: executor.map(device_operation, devices) ``` ### 三、完整实现示例 ```python import uiautomator2 as u2 from concurrent.futures import ThreadPoolExecutor import subprocess class MultiDeviceController: def __init__(self): self.devices = self._get_devices() def _get_devices(self): output = subprocess.check_output(["adb", "devices"]).decode() return [line.split()[0] for line in output.splitlines() if "\tdevice" in line] def common_operations(self, serial): try: d = u2.connect(serial) d.implicitly_wait(10) # 示例操作流程 d.app_start("com.android.settings") d(text="Wi‑Fi").click() d(text="Add network").click() # 更多自定义操作... except Exception as e: print(f"设备 {serial} 执行异常: {str(e)}") def run_parallel(self): with ThreadPoolExecutor(max_workers=len(self.devices)) as executor: executor.map(self.common_operations, self.devices) if __name__ == "__main__": controller = MultiDeviceController() controller.run_parallel() ``` ### 四、关键注意事项 1. **设备识别**:确保每台设备已开启USB调试模式并通过`adb devices`可见 2. **线程安全**:每个设备操作应保持独立上下文,避免共享资源 3. **异常处理**:单个设备异常不应影响其他设备执行 4. **性能优化**:建议同时连接的设备数不超过CPU核心数*2[^3] 5. **设备初始化**:推荐提前执行`u2.init()`初始化所有设备 ### 五、扩展优化方向 1. **动态负载均衡**:根据设备性能动态分配任务 2. **操作同步机制**:实现跨设备状态同步 3. **结果收集系统**:集中收集各设备执行日志和截图 4. **设备池管理**:实现设备的自动重连和故障转移
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值