UI自动化-多手机并发及交互

多手机并发

通常UI自动化都是单机执行,当需要测试兼容性时,则需要多机执行(多机跑同一套脚本)。这时候如何处理呢?

方案一:for 循环-多机型依次执行,即A手机执行完后B手机再执行

def run_parallel(user_info):
    #pytest 执行入口
    report = f"report-{user_info['device']['deviceName']}"
    try:
        os.system(f"del /s /q " + projectPath + f"\\{report}")
        time.sleep(1)
        os.system(f"rd /s /q " + projectPath + f"\\{report}")
        time.sleep(1)
        print(f"{report} report has deleted")
    except:
        print("error occur")
    else:
        pytest.main(["../TestCases/", f"--cmdopt={user_info}",
                     "--alluredir", f"../{report}/xml"])
        os.system(f"allure generate ../{report}/xml -o ../{report}/html")
if __name__ == '__main__':
    for i in user_info:
        run_parallel(i)

以上方案虽然也实现了,但明显的执行时长随着设备的增多而增多。理想状态应是多机同时执行,那如何实现呢?这就要用到多进程。

方案二:多进程-多机型并发执行,即A,B同时执行

if __name__ == '__main__':
    pool = Pool()
    for i in user_info:
        #注意要真正实现多进程,需使用非阻塞式,即pool.apply_async;若使用阻塞式,即pool.apply,则效果同方案1
        pool.apply_async(run_parallel, (i, )) 
    pool.close()
    pool.join()

【踩坑】当使用了以上多进程方案后,若发现并没有起到想象中的作用,是同一套脚本在同一台手机多次执行,则是配置不当。

2.1 配置

1.driver配置一定要加udid和systemPort,否则脚本会默认在第一台手机多次执行
desired_caps["udid"] = self.device_info["udid"]
desired_caps["systemPort"] = self.device_info["server_port"]
2.启动appium_server配置:加udid
cmd = "start appium -p {0} -bp {1} -U {2}".format(self.device_info["server_port"], self.device_info["server_port"] + 1, self.device_info["udid"])
        os.system(cmd)

多手机交互

上面我们处理了多机并发的问题,那如果需要多机交互的场景,该如何处理呢?比如,直播间交互,需要一台手机做主播,另一台做观众。

方案一: 定义多diver

# conftest.py
@pytest.fixture(scope='function')
def driver1_reset():
    global action1s
    if action1s is None:
        global different
        different = 1.5
        with allure.step('Start and Reset APP'):
            action1s = BaseDriver.base_driver(0,noReset=False)
        yield action1s
        with allure.step('Close_APP'):
            time.sleep(3)
            action1s.close_app()
            different=None
            action1s=None

@pytest.fixture(scope='function')
def driver2_noreset():
    global action2
    if action2 is None:
        global different
        different =2
        with allure.step('Start second phone and noReset APP'):
            action2 = BaseDriver.base_driver(1,noReset=True)
        yield action2
        with allure.step('Close_APP2'):
            time.sleep(3)
            action2.close_app()
            different=None
            action2=None
#test_live.py
def test_comment(self, page, page2):
    page.loginpage.if_not_login('94724068', '123456')
    page2.loginpage.if_not_login('61042217', '123456')
    # 主播开播
    page.livePage1.click_liveBtn()
    page.livePage1.click_liveBtn()
    page.livePage1.clear_liveTitle()
    live_title = Tools.generate_five_randomStr()
    page.livePage1.input_liveTitle(live_title)
    page.livePage1.click_permissions()
    page.livePage1.click_configureBtn()
    page.livePage1.click_liveTagBtn()
    page.livePage2.select_a_liveTag(1)
    page.livePage2.click_confirm_TagBtn()
    page.livePage1.start_live()
    # 观众进入直播间
    page2.livePage2.click_live_class('颜值')
    page2.livePage1.swipe_down()
    page2.livePage1.enter_liveroom(live_title)

以上也可实现多机交互,但可以明显的看到conftest.py中代码冗余,手机的执行顺序也是每一步都按顺序执行。即A登录后,B登录。A开播后,B当观众端。由此考虑,仍使用多进程,需要按序执行的步骤,加锁即可。

方案二:多进程 And 加锁

# run.py
def run_parallel(user_info, lock):
    report = f"report-{user_info['device']['deviceName']}"
    try:
        os.system(f"del /s /q " + projectPath + f"\\{report}")
        time.sleep(1)
        os.system(f"rd /s /q " + projectPath + f"\\{report}")
        time.sleep(1)
        print(f"{report} report has deleted")
    except:
        print("error occur")

    else:
        pytest.main(["../TestCases/", f"--cmdopt={user_info}", f"--input={id(lock)}",
                     "--alluredir", f"../{report}/xml"])
        os.system(f"allure generate ../{report}/xml -o ../{report}/html")

if __name__ == '__main__':
    manager = Manager()
    lock = manager.Lock()
    pool = Pool()
    for i in user_info:
        pool.apply_async(run_parallel, (i, lock))
    pool.close()
    pool.join()
# conftest.py
def pytest_addoption(parser):  # 定义命令行传参参数
    parser.addoption("--cmdopt", action="store", default="device", help="None")
    parser.addoption("--input", default="default")

@pytest.fixture(scope="session")  # 命令行参数传递给pytest
def cmdopt(request):
    return request.config.getoption("--cmdopt")

@pytest.fixture(scope="session")
def input(request):
    lock = int(request.config.getoption("--input"))
    l = _ctypes.PyObj_FromPtr(lock)
    return l
# test_live.py
def live_contact(self):
    self.page.livePage2.click_live_class('颜值')
    self.page.livePage1.swipe_down()
    flag = self.page.livePage1.the_element_exist((By.XPATH, "//*[@text='{}' and @index='2']".format(live_title)))
    if not flag:
        print('start live')
        print('pid is {}'.format(os.getpid()))
        self.page.livePage1.click_liveBtn()
        self.page.livePage1.click_liveBtn()
        self.page.livePage1.clear_liveTitle()
        self.page.livePage1.input_liveTitle(live_title)
        self.page.livePage1.click_permissions()
        self.page.livePage1.click_configureBtn()
        self.page.livePage1.click_liveTagBtn()
        self.page.livePage2.select_a_liveTag(1)
        self.page.livePage2.click_confirm_TagBtn()
        self.page.livePage1.start_live()
    else:
        print('join live')
        print('pid is {}'.format(os.getpid()))
        self.page.livePage1.enter_liveroom(live_title)
        # 等待10秒观众进入直播间
        time.sleep(10)

def test_comment(self, get_info,input):
    info = eval(get_info)
    user =info['user']
    pwd = info['pwd']
    device = info['device']
    lock = input
    self.login(user, pwd, device) 
    lock.acquire()
    self.live_contact()
    lock.release()
    self.message_contact()
    self.verify_message()

执行以上代码,可看到执行顺序为:两手机同时登录,一台手机开播后,另一台手机进入当观众端。即不加锁时,多机同步并发,加锁时,仅一部手机执行操作后,后续手机才可依次执行该操作。利用以上逻辑,可最大化提升手机执行效率,且实现了交互的目的。

【踩坑】pytest 框架不同于Unitest框架,其传参虽有fixture可灵活使用,但其测试类中不可有init.故而要传特殊类型的参数时,需做特殊处理。因此以上难点即在如何传lock对象。

2.1 传lock对象

manage对象只可在主进程定义。以上代码使用manager.Lock()来控制进程操作,如需进程共用同一份数据,可使用manager.List(),manager.Dict()等。

pytest通过命令行传参数,默认参数类型都为字符串。所以在test_live.py拿到的参数也为字符串,并不能发挥作用。故在取参时需做处理,将内存地址转为对象。

 lock = int(request.config.getoption("--input"))
 l = _ctypes.PyObj_FromPtr(lock)
讨论数量: 0

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!