def suite_sync_run_case(pro_name, browser_name, thread_num=2, remote=False): """ 同时执行不同用例( 通过动态修改'suite.py'文件中'TestSuite'类中的'run'方法,使得每个线程中的结果都可以记录到测试报告中 ) :param pro_name: 项目名称 :param browser_name: 浏览器名称 :param thread_num: 线程数 :param remote: 是否远程执行 【 备 注 】 1.suite 实例对象(包含了所有的测试用例实例,即继承了'unittest.TestCase'的子类的实例对象 test_instance ) 2.开启浏览器操作(每个用例执行一次):在每个'测试类'的 setUp 方法中执行 ( 继承 ParaCase 父类 ) 3.关闭浏览器操作(每个用例执行一次):在每个'测试类'的 tearDown 方法中执行 ( 继承 ParaCase 父类 ) 【 保 存 截 屏 图 片 ID 的 逻 辑 】 1.为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict' -> screen_shot_id_dict = {} 2.每个测试方法中将所有截屏ID都保存入'screen_shot_id_list' -> screen_shot_id_dict = ['aaa', 'bbb', 'ccc'] 3.实例对象'suite'在重写的'new_run'方法中 将'screen_shot_id_list'添加入'screen_shot_id_dict' 4.screen_shot_id_dict = { "测试类名.测试方法名":['aaa', 'bbb'], "测试类名.测试方法名":['cccc'] } """ if is_exist_start_case(pro_name): send_DD_for_FXC(title=pro_name, text="#### 可能在执行WEB自动化测试'定时任务'时 遇到 '" + pro_name + "' 项目存在<运行中>的用例而未执行测试") else: # 将'测试类'中的所有'测试方法'添加到 suite 对象中(每个'测试类'实例对象包含一个'测试方法') from TestBase.test_case_unit import ParaCase suite, on_line_test_method_name_list = ParaCase.get_online_case_to_suite( pro_name=pro_name, browser_name=browser_name, remote=remote) if suite != "mongo error": if on_line_test_method_name_list: # 为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict'(目的:保存截图ID) setattr(suite, "screen_shot_id_dict", {}) # 为实例对象'suite'<TestSuite>动态添加一个属性'thread_num'(目的:控制多线程数量) setattr(suite, "thread_num", thread_num) # 为实例对象'suite'<TestSuite>动态添加两个方法'run_test_custom'、'show_result_custom'( 目的:供多线程中调用 ) suite.run_test_custom = MethodType(run_test_custom, suite) suite.show_result_custom = MethodType(show_result_custom, suite) # 为实例对象'suite'<TestSuite>动态修改实例方法'run'( 目的:启用多线程来执行case ) suite.run = MethodType(new_run, suite) # 运行测试,并生成测试报告 test_result, current_report_file = generate_report( pro_name=pro_name, suite=suite, title='WEB自动化测试报告 - ' + pro_name, description='详细测试用例结果', tester="自动化测试", verbosity=2) # 测试后发送预警 # send_warning_after_test(test_result, current_report_file) else: send_DD_for_FXC(title=pro_name, text="#### 可能在执行WEB自动化测试'定时任务'时 遇到 '" + pro_name + "' 项目<没有上线>的用例而未执行测试")
def setUp(self): """ 【 每个用例对象执行前,需要进行如下配置 】 【 备 注 】 self.driver : 操作 Android 设备 self.session : 操作 APP 应用 :return: """ from Config.pro_config import get_login_accout # 通过线程名的索引 获取登录账号 self.user, self.passwd = get_login_accout( self.current_thread_name_index) # 获取'Android'驱动、设备名称 self.driver, self.device_name = get_android_driver( self.pro_name, self.current_thread_name_index, self.connected_android_device_list) # 获取APP应用信息 from Config.pro_config import get_app_package self.app_package = get_app_package(self.pro_name) # 通过'Android'驱动 启动APP应用 try: self.session = self.driver.session(self.app_package) except Exception as e: log.error(("显示异常:" + str(e))) if "SessionBrokenError" in str(e): error_msg = self.pro_name + " 项目的APP应用没有启动" elif "package not found" in str(e): error_msg = self.pro_name + " 项目的APP应用package名称有误" else: error_msg = self.pro_name + " 项目启动APP时 出现异常情况" send_DD_for_FXC(title=self.pro_name, text="#### " + error_msg + "") raise Exception(error_msg)
def run_test_custom(self, test, result, debug, index): """ :param self: 表示'suit'实例对象 :param test: 表示'测试用例'实例对象 :param result: 测试结果报告 :param debug: :param index: :return: 多线程中执行的内容 1.需要为实例对象'suite'<TestSuite>动态添加该方法 2.目的:供多线程中调用 """ # 启动测试用例:设置用例的'运行状态=running'和'开始时间' start_case_run_status(pro_name=test.pro_name, test_method_name=test.test_method) if not debug: test(result) else: test.debug() # (self)实例对象'suite'<TestSuite> 为每个执行完毕的(test)'测试用例'实例 保存'截图ID列表' try: self.screen_shot_id_dict[ test.screen_shot_id_list_name] = test.screen_shot_id_list except Exception as e: send_DD_for_FXC(title=test.pro_name, text="#### 出现异常 - 可能 远程浏览器监听未开启!!!") log.error(str(e)) if self._cleanup: self._removeTestAtIndex(index) # 停止测试用例:设置用例的'运行状态=stopping'和'运行时间' stop_case_run_status(pro_name=test.pro_name, test_method_name=test.test_method) # 返回值<'tuple'>:返回测试用例实例对象的两个属性值( 项目名称、测试方法名 )供回调函数使用 return test.pro_name, test.test_method
def get_android_driver(pro_name, current_thread_name_index, connected_android_device_list): """ 【 获取'Android'驱动、设备名称 】 :param pro_name :param current_thread_name_index: 当前线程名字的索引 :param connected_android_device_list: 已连接设备信息列表 [ { "thread_index": 1, "device_name": "小米5S", "device_udid": "192.168.31.136:5555"} } , { "thread_index": 2, "device_name": "坚果Pro", "device_udid": "192.168.31.253:4444" } } ] :return: 【 步 骤 】 1.获取 Appium 服务启动应用所需的能力参数 (指定设备,指定应用) 2.通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息和'Appium'服务 3.获取设备驱动 """ # 通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息和'Appium'服务 device_name = None device_udid = None for connected_android_devices_dict in connected_android_device_list: if current_thread_name_index == connected_android_devices_dict[ "thread_index"]: device_udid = connected_android_devices_dict["device_udid"] device_name = connected_android_devices_dict["device_name"] break log.info("\n\n") log.info("device_name -> " + device_name) log.info("device_udid -> " + device_udid) log.info("\n\n") driver = None try: # 连接设备 ADB_WIFI driver = uiautomator2.connect_adb_wifi(device_udid) # 连接设备 WIFI # driver = uiautomator2.connect('192.168.31.136') # driver = uiautomator2.connect_wifi('192.168.31.136') # 连接设备 USB # driver = uiautomator2.connect('15a6c95a') # driver = uiautomator2.connect_usb('15a6c95a') # 配置accessibility服务的最大空闲时间,超时将自动释放 driver.set_new_command_timeout(gv.NEW_COMMAND_TIMEOUT) # 全局默认的元素定位超时时间 driver.implicitly_wait(gv.IMPLICITY_WAIT) # 解锁(点亮屏幕)相当于点击了home健 driver.unlock() except Exception as e: log.error(("显示异常:" + str(e))) if "Uiautomator started failed" in str(e): error_msg = pro_name + " 项目 " + device_name + " 设备 启动 ATX 服务时 未授权" else: error_msg = pro_name + "项目 " + device_name + " 设备 启动 ATX 服务的其他异常情况" send_DD_for_FXC(title=pro_name, text="#### " + error_msg + "") raise Exception(error_msg) finally: return driver, device_name
def suite_sync_run_case(pro_name, connected_android_device_list=[]): """ 同时执行不同用例( 通过动态修改'suite.py'文件中'TestSuite'类中的'run'方法,使得每个线程中的结果都可以记录到测试报告中 ) :param pro_name: 项目名称 :param connected_android_device_list: 已连接的设备列表 ( 以 列表数量 作为 线程数量 ) (1)若 == [] :表示当前是'定时任务' (2)若 =! [] :表示当前是'页面执行',并且 已经获取到已连接的Android设备 【 备 注 】 1.suite 实例对象(包含了所有的测试用例实例,即继承了'unittest.TestCase'的子类的实例对象 test_instance ) 2.启动 Android 设备中的 APP 应用(每个用例执行一次):在每个'测试类'的 setUp 方法中执行 ( 继承 ParaCase 父类 ) 3.关闭 Android 设备中的 APP 应用 (每个用例执行一次):在每个'测试类'的 tearDown 方法中执行 ( 继承 ParaCase 父类 ) 【 保 存 截 屏 图 片 ID 的 逻 辑 】 1.为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict' -> screen_shot_id_dict = {} 2.每个测试方法中将所有截屏ID都保存入'screen_shot_id_list' -> screen_shot_id_dict = ['aaa', 'bbb', 'ccc'] 3.实例对象'suite'在重写的'new_run'方法中 将'screen_shot_id_list'添加入'screen_shot_id_dict' 4.screen_shot_id_dict = { "测试类名.测试方法名":['aaa', 'bbb'], "测试类名.测试方法名":['cccc'] } 【 并 发 线 程 数 逻 辑 】 1.通过 adb 命令 查看 Android 设备 连接情况 ( 已连接 | 未连接 | 未授权 ) 2.将'已连接'的设备列表数量 作为 并发线程数量 [ { "thread_index": 1, "device_name": "小米5S", "device_udid": "192.168.31.136:5555" } } , { "thread_index": 2, "device_name": "坚果Pro", "device_udid": "192.168.31.253:4444"} } ] 【 每 个 用 例 使 用 Android 设 备 逻 辑 】 通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息 """ if is_null(connected_android_device_list): # 表示当前是'定时任务' # (定时任务)需要判断 是否存在运行中的用例 if is_exist_start_case(pro_name): send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目存在<运行中>的用例而未执行测试(定时任务)") return "Done" # (定时任务)需要获取 已连接的 Android 设备信息列表 connected_android_device_list = get_connected_android_devices_info( pro_name) if len(connected_android_device_list) == 0: send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目 未连接任何 Android 设备") return "Done" # '已连接设备的' 列表数量 作为 线程数量 log.info("线程数量 : " + str(len(connected_android_device_list))) log.info("已连接的Android设备信息列表:" + str(connected_android_device_list) + "\n") # 将'测试类'中的所有'测试方法'添加到 suite 对象中(每个'测试类'实例对象包含一个'测试方法') from TestBase.test_case_unit import ParaCase suite, on_line_test_method_name_list = ParaCase.get_online_case_to_suite( pro_name, connected_android_device_list) if suite != "mongo error": if is_null(on_line_test_method_name_list): send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目<没有上线>的用例而未执行测试(定时任务)") else: # 为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict'(目的:保存截图ID) setattr(suite, "screen_shot_id_dict", {}) # 为实例对象'suite'<TestSuite>动态添加一个属性'android_device_name_dict'(目的:保存使用的Android设备名称) setattr(suite, "android_device_name_dict", {}) # 为实例对象'suite'<TestSuite>动态添加一个属性'thread_num'(目的:控制多线程数量) setattr(suite, "thread_num", len(connected_android_device_list)) # 为实例对象'suite'<TestSuite>动态添加两个方法'run_test_custom'、'show_result_custom'( 目的:供多线程中调用 ) suite.run_test_custom = MethodType(run_test_custom, suite) suite.show_result_custom = MethodType(show_result_custom, suite) # 为实例对象'suite'<TestSuite>动态修改实例方法'run'( 目的:启用多线程来执行case ) suite.run = MethodType(new_run, suite) # 运行测试,并生成测试报告 test_result, current_report_file = generate_report( pro_name=pro_name, suite=suite, title='Android自动化测试报告 - ' + pro_name, description='详细测试用例结果', tester="自动化测试", verbosity=2)
def suite_sync_run_case(pro_name): """ 同时执行不同用例( 通过动态修改'suite.py'文件中'TestSuite'类中的'run'方法,使得每个线程中的结果都可以记录到测试报告中 ) :param pro_name: 项目名称 【 备 注 】 1.suite 实例对象(包含了所有的测试用例实例,即继承了'unittest.TestCase'的子类的实例对象 test_instance ) 2.启动 Android 设备中的 APP 应用(每个用例执行一次):在每个'测试类'的 setUp 方法中执行 ( 继承 ParaCase 父类 ) 3.关闭 Android 设备中的 APP 应用 (每个用例执行一次):在每个'测试类'的 tearDown 方法中执行 ( 继承 ParaCase 父类 ) 【 保 存 截 屏 图 片 ID 的 逻 辑 】 1.为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict' -> screen_shot_id_dict = {} 2.每个测试方法中将所有截屏ID都保存入'screen_shot_id_list' -> screen_shot_id_dict = ['aaa', 'bbb', 'ccc'] 3.实例对象'suite'在重写的'new_run'方法中 将'screen_shot_id_list'添加入'screen_shot_id_dict' 4.screen_shot_id_dict = { "测试类名.测试方法名":['aaa', 'bbb'], "测试类名.测试方法名":['cccc'] } 【 并 发 线 程 数 逻 辑 】 前提:需要先手动检查: Appium服务是否正常启动、Android设备是否正确连接 1.获取已连接设备信息列表 [ { "thread_index": 1, "device_name": "小米5S", "platform_version": "7.0", "device_udid": "192.168.31.136:5555", "appium_server": "http://127.0.0.1:4724/wd/hub" } } , { "thread_index": 2, "device_name": "坚果Pro", "platform_version": "7.1.1", "device_udid": "192.168.31.253:4444", "appium_server": "http://127.0.0.1:4723/wd/hub" } } ] 2.返回的列表数量 作为 线程数量 【 每 个 用 例 使 用 Android 设 备 逻 辑 】 通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息和'Appium'服务 """ # (定时任务)需要判断 是否存在运行中的用例 if is_exist_start_case(pro_name): send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目存在<运行中>的用例而未执行测试(定时任务)") return "Done" # 获取 已连接的 Android 设备信息列表 connected_android_device_list = config_android_device_with_appium_server_list() # 列表数量 作为 线程数量 thread_num = len(connected_android_device_list) log.info("\n线程数量 : " + str(thread_num)) log.info("已连接的Android设备信息列表:" + str(connected_android_device_list) + "\n") if thread_num == 0: send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目 未连接任何 Android 设备") return "Done" # 将'测试类'中的所有'测试方法'添加到 suite 对象中(每个'测试类'实例对象包含一个'测试方法') from TestBase.test_case_unit import ParaCase suite, on_line_test_method_name_list = ParaCase.get_online_case_to_suite(pro_name, connected_android_device_list) if suite != "mongo error": if is_null(on_line_test_method_name_list): send_DD_for_FXC(title=pro_name, text="#### '" + pro_name + "' 项目<没有上线>的用例而未执行测试(定时任务)") else: # 为实例对象'suite'<TestSuite>动态添加一个属性'screen_shot_id_dict'(目的:保存截图ID) setattr(suite, "screen_shot_id_dict", {}) # 为实例对象'suite'<TestSuite>动态添加一个属性'android_device_name_dict'(目的:保存使用的Android设备名称) setattr(suite, "android_device_name_dict", {}) # 为实例对象'suite'<TestSuite>动态添加一个属性'thread_num'(目的:控制多线程数量) setattr(suite, "thread_num", thread_num) # 为实例对象'suite'<TestSuite>动态添加两个方法'run_test_custom'、'show_result_custom'( 目的:供多线程中调用 ) suite.run_test_custom = MethodType(run_test_custom, suite) suite.show_result_custom = MethodType(show_result_custom, suite) # 为实例对象'suite'<TestSuite>动态修改实例方法'run'( 目的:启用多线程来执行case ) suite.run = MethodType(new_run, suite) # 运行测试,并生成测试报告 test_result, current_report_file = generate_report(pro_name=pro_name, suite=suite, title='Android自动化测试报告 - ' + pro_name, description='详细测试用例结果', tester="自动化测试", verbosity=2)
def get_android_driver(pro_name, current_thread_name_index, connected_android_device_list): """ 【 获取 Android 设备驱动 】 :param pro_name :param current_thread_name_index: 当前线程名字的索引 :param connected_android_device_list: 已连接设备信息列表 [ { "thread_index": 1, "device_name": "小米5S", "platform_version": "7.0", "device_udid": "192.168.31.136:5555", "appium_server": "http://127.0.0.1:4724/wd/hub" } } , { "thread_index": 2, "device_name": "坚果Pro", "platform_version": "7.1.1", "device_udid": "192.168.31.253:4444", "appium_server": "http://127.0.0.1:4723/wd/hub" } } ] :return: 【 步 骤 】 1.获取 Appium 服务启动应用所需的能力参数 (指定设备,指定应用) 2.通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息和'Appium'服务 3.获取设备驱动 """ # 获取 Appium 服务启动应用所需的能力参数 (指定设备,指定应用) from Config.pro_config import get_app_info app_info = get_app_info(pro_name) desired_caps = dict() desired_caps["platformName"] = "Android" desired_caps["appPackage"] = app_info["appPackage"] desired_caps["appActivity"] = app_info["appActivity"] # 使用哪个自动化引擎 desired_caps["automationName"] = "UiAutomator2" # Appium 等待接收从客户端发送的新命令的超时时长,超时后Appium会终止会话 desired_caps["newCommandTimeout"] = 30 # Android 等待设备就绪的超时时长,以秒为单位 desired_caps["deviceReadyTimeout"] = 30 # Android 在启动后等待设备就绪的超时时长,以秒为单位 desired_caps["androidDeviceReadyTimeout"] = 30 # appium 启动时进程保存的原因(有待验证) # desired_caps["adbExecTimeout"] = 20000 # desired_caps["uiautomator2ServerLaunchTimeout"] = 30000 # 唤醒屏幕(效果不理想) desired_caps["unlockType"] = "pattern" desired_caps["unlockKey"] = "12589" # 通过'当前线程名索引' 获取已连接设备列表中对应的'Android'设备信息和'Appium'服务 device_name = None appium_server = None for connected_android_devices_dict in connected_android_device_list: if current_thread_name_index == connected_android_devices_dict["thread_index"]: desired_caps["platformVersion"] = connected_android_devices_dict["platform_version"] desired_caps["deviceName"] = connected_android_devices_dict["device_udid"] device_name = connected_android_devices_dict["device_name"] appium_server = connected_android_devices_dict["appium_server"] break log.info("\n\n") log.info("device_name -> " + device_name) log.info("appium_server -> " + appium_server) log.info("\n\n") # 获取设备驱动 try: driver = webdriver.Remote(appium_server, desired_caps) except Exception as e: log.error(("显示异常:" + str(e))) if "Failed to establish a new connection" in str(e): error_msg = "Appium 服务(" + appium_server + ")未启动" elif "Could not find a connected Android device" in str(e): error_msg = "Android 设备(" + device_name + ")未连接" elif "Failed to launch Appium Settings app" in str(e): error_msg = "Appium Setting 应用启动超时" else: error_msg = "启动 Appium 服务的其他异常情况" send_DD_for_FXC(title=pro_name, text="#### " + error_msg + "") raise Exception(error_msg) return driver, device_name