class _RequestsConfig: """ API配置类 """ def __init__(self): # 引用日志类 self.__log = Logger('配置接口').get_logger() # 获取公共配置 self.__pro_public = OperateConfig(constant.config_pro_api) self.__address = self.__pro_public.get_str('project', 'address') self.__timeout = self.__pro_public.get_int('project', 'timeout') self.__proxy = self.__pro_public.get_str('project', 'proxy') # 接口信息 self._api_url = None self._headers = None self._params = None self._data = None self._cookies = None self._files = None self._timeout = self.__timeout self._proxies = self._set_proxies() def _set_url(self, url: str): """ 拼接完整接口地址 :param url: URL :return: 完整URL """ self._api_url = '{}{}'.format(self.__address.rstrip('/'), url) self.__log.info('URL:{}'.format(self._api_url)) def set_headers(self, headers: dict): if not self._headers: self._headers = headers else: self._headers.update(headers) self.__log.info('headers:{}'.format(self._headers)) def _set_params(self, params: dict): self._params = params self.__log.info('params:{}'.format(self._params)) def _set_data(self, data: dict): self._data = data self.__log.info('data:{}'.format(self._data)) def set_files(self, files: dict): self._files = files self.__log.info('files:{}'.format(self._files)) def set_cookies(self, cookies: dict): self._cookies = cookies self.__log.info('cookies:{}'.format(self._cookies)) def _set_proxies(self): if self.__proxy: proxy = {self.__proxy.split(':')[0]: self.__proxy} self.__log.info('proxies:{}'.format(proxy)) return proxy
def __init__(self, report_file, if_result_file=False): # 获取邮件配置 self._email = OperateConfig(constant.config_common_path) self._s_mtp = self._email.get_str('email', 's_mtp') self._port = self._email.get_str('email', 's_mtp_port') self._from_man = self._email.get_str('email', 'from') self._to_man = self._email.get_str('email', 'to') self._authorization_code = self._email.get_str('email', 'authorization_code') self._report_name = report_file self._result_file = if_result_file
def __init__(self): # 引用日志类 self.__log = Logger('配置接口').get_logger() # 获取公共配置 self.__pro_public = OperateConfig(constant.config_pro_api) self.__address = self.__pro_public.get_str('project', 'address') self.__timeout = self.__pro_public.get_int('project', 'timeout') self.__proxy = self.__pro_public.get_str('project', 'proxy') # 接口信息 self._api_url = None self._headers = None self._params = None self._data = None self._cookies = None self._files = None self._timeout = self.__timeout self._proxies = self._set_proxies()
def __init__(self): # 引用日志类 self._log = Logger("MYSQL").get_logger() # 获取数据库配置 self._db_config = OperateConfig(config_common_path) try: self._conn = pymysql.connect( user=self._db_config.get_str('mysql', 'username'), password=self._db_config.get_str('mysql', 'password'), host=self._db_config.get_str('mysql', 'host'), port=self._db_config.get_str('mysql', 'port'), database=self._db_config.get_str('mysql', 'database')) self._log.info('成功连接数据库') except pymysql.Error as e: self._log.error('数据库连接失败:{}'.format(e))
def __init__(self, db_=0): # 引用日志类 self._log = Logger('REDIS').get_logger() # 获取redis配置信息 self._redis_conf = OperateConfig(config_common_path) try: pool = redis.ConnectionPool( host=self._redis_conf.get_str('redis', 'host'), port=self._redis_conf.get_str('redis', 'port'), password=self._redis_conf.get_str('redis', 'auth'), db=db_, decode_responses=True) self._conn = redis.StrictRedis(connection_pool=pool) self._log.info('成功连接REDIS,db({})'.format(db_)) except redis.exceptions.RedisError as e: self._log.error('REDIS连接失败:{}'.format(e))
def __init__(self): # 引用日志类 self._log = Logger("ORACLE").get_logger() # 获取数据库配置 self._db_config = OperateConfig(config_common_path) self._conn_str = '{}/{}@{}:{}/{}'.format( self._db_config.get_str('oracle', 'username'), self._db_config.get_str('oracle', 'password'), self._db_config.get_str('oracle', 'host'), self._db_config.get_str('oracle', 'port'), self._db_config.get_str('oracle', 'database')) try: self._conn = cx_Oracle.connect(self._conn_str) self._log.info('成功连接数据库') except cx_Oracle.Error as e: self._log.error('数据库连接失败:{}'.format(e))
def setUpClass(cls): cls.log.info('{}开始执行{}'.format('=' * 50, '=' * 50)) # 读取项目配置 cls.__pro_public = OperateConfig(config_pro_web) cls.homepage = cls.__pro_public.get_str('project', 'homepage') # 引用随机数据生成类 cls.faker = FakeData() # 引用关联参数读写类 cls.context = Context() # 创建生成器,返回一个迭代器,用于测试用例计数 cls._count_case = (x for x in range(1, 1000))
def __init__(self): self._log = Logger('安卓引擎').get_logger() # 读取配置 self._reader = OperateConfig(constant.config_pro_app) self._if_wifi = self._reader.get_bool('server', 'if_wifi') self._android_mode = self._reader.get_str('server', 'android_mode')
class AndroidEngine: """ 安卓引擎,自动化启动appium服务,自动连接设备,真机可自动切换WiFi连接,获取driver 1、实例化前请确保模拟器已开启,真机已连接电脑,并且打开调试模式 2、暂时只支持单机连接启动,若本机同时连接多个设备,则默认连接已连接设备列表第一个设备 """ def __init__(self): self._log = Logger('安卓引擎').get_logger() # 读取配置 self._reader = OperateConfig(constant.config_pro_app) self._if_wifi = self._reader.get_bool('server', 'if_wifi') self._android_mode = self._reader.get_str('server', 'android_mode') def get_driver(self): """ 根据配置获取driver :return: driver对象 """ self._start_server() devices = self._get_device_names() version = self._get_android_version(devices[0]) app_path = publicFunctions.get_apk_path() ports = eval(self._reader.get_str('connected', 'server_ports')) if self._android_mode == 'simulator': desired_caps = DesiredCaps.caps_android_simulator desired_caps['platformVersion'] = version desired_caps['deviceName'] = devices[0] desired_caps['app'] = app_path driver = self._get_driver(desired_caps, ports[0]) return driver elif self._android_mode == 'machine': desired_caps = DesiredCaps.caps_android_machine desired_caps['platformVersion'] = version desired_caps['deviceName'] = devices[0] desired_caps['app'] = app_path driver = self._get_driver(desired_caps, ports[0]) return driver else: self._log.error('启动模式配置有误,请确认:{}'.format(self._android_mode)) self._kill_server() sys.exit() def quit_driver(self, driver): """ 退出驱动程序,断开模拟器连接,杀掉node进程 :param driver: driver对象 :return: None """ if self._android_mode == 'simulator': self._disconnect_simulators() driver.quit() self._kill_server() sleep(3) self._log.info('已退出驱动') def _start_server(self): """ 使用命令行自动化启动appium server :return: driver对象 """ if self._android_mode == 'simulator': self._connect_simulators() devices = self._get_device_names() if self._if_wifi is True and self._android_mode == 'machine': self._switch_to_wifi() devices = [device for device in self._get_device_names() if ':' in device] commands = self._create_appium_commands(devices) for cmd in commands: cmd = r"start {}".format(cmd) os.system(cmd) sleep(3) self._log.info('appium server已启动:{}'.format(cmd)) def _get_driver(self, desired_caps: dict, port: str): """ 获取driver :param desired_caps: 连接参数 :param port: 服务端口 :return: driver对象 """ try: driver = webdriver.Remote(command_executor='http://127.0.0.1:{}/wd/hub'.format(port), desired_capabilities=desired_caps) sleep(1) self._log.info('appium server已连接') return driver except WebDriverException as e: self._log.error('appium server连接失败:{}'.format(e)) sys.exit() def _connect_simulators(self): """ 对于模拟器,在启动后可以调用此方法实现自动连接电脑 :return: None """ simulators = self._reader.get_str('server', 'simulator').split(';') for simulator in simulators: cmd = 'adb connect {}'.format(simulator) os.system(cmd) self._log.debug('模拟器({})已连接'.format(simulator)) def _disconnect_simulators(self): """ 断开全部已连接模拟器设备 :return: None """ devices = self._reader.get_str('server', 'simulator').split(';') for device in devices: cmd = 'adb disconnect {}'.format(device) os.system(cmd) self._log.debug('设备({})已断开'.format(device)) def _switch_to_wifi(self): """ 对于真机,若需要使用WiFi连接模式,在手机用USB线连接到电脑打开调试模式后,调用此方法可切换至WIFI连接 :return: None """ devices = self._get_device_names() simulators = self._reader.get_str('server', 'simulator').split(';') machines = list(set(devices) - set(simulators)) ports = self._create_useful_ports(5555, machines) for machine, port in zip(machines, ports): if str(port) in '|'.join(self._get_device_names()): cmd_1 = 'adb -s {} shell ip -f inet addr show wlan0'.format(machine) result_1 = self._execute_command(cmd_1) ip = re.search(r"inet\s(\d+\.\d+\.\d+\.\d+)", result_1).group(1) cmd_2 = 'adb -s {} tcpip {}'.format(machine, port) os.system(cmd_2) cmd_3 = 'adb connect {}:{}'.format(ip, port) result_2 = self._execute_command(cmd_3) if 'connected' in result_2: self._log.debug('设备({})成功切换至WIFI连接:{}'.format(machine, result_2.strip())) self._log.warning('请拔掉设备({})USB线!!'.format(machine)) else: self._log.error('设备({})切换至WIFI连接失败:{}'.format(machine, result_2.strip())) def _get_device_names(self): """ 获取已连接安卓设备名 :return: 安卓设备名列表 """ cmd = 'adb devices' result = self._execute_command(cmd) devices = re.findall(r"(.*[^\s])\s*device", result) devices.pop(0) if devices: self._log.debug('获取到已连接设备列表:{}'.format(devices)) return devices else: self._log.error('未检测到安卓设备。') sys.exit() def _get_android_version(self, device: str): """ 获取已连接安卓设备版本号 :param device: 设备名 :return: 版本号 """ cmd = f'adb -s {device} shell getprop ro.build.version.release' result = self._execute_command(cmd) self._log.debug('获取到设备版本号:{}'.format(result)) return result.strip() def _get_package_and_activity(self, apk_path=publicFunctions.get_apk_path()): """ 通过'aapt'命令自动获取appPackage和appActivity :param apk_path: apk路径 :return: appPackage和appActivity """ sdk_path = self._get_sdk_path() adb_disk = sdk_path.split(':')[0] build_tools_path = os.path.join(sdk_path, 'build-tools') aapt_path = os.path.join(build_tools_path, os.listdir(build_tools_path)[0]) cmd = f'{adb_disk}:&cd {aapt_path}&aapt dump badging {apk_path}' result = self._execute_command(cmd) package = re.search(r"package: name='([\w\\.]+)'", result).group(1) activity = re.search(r"launch.*activity: name='([\w\\.]+)'", result).group(1) return package, activity def _get_sdk_path(self): """ 从PATH环境变量中提取Android SDK路径 :return: Android SDK路径 """ path_env = os.environ['PATH'] sdk_search = re.search(r'(.+?)\\platform-tools', path_env) if sdk_search: sdk_path = sdk_search.group(1).split(';')[-1] if '%' in sdk_path: sdk_path = os.environ[sdk_path.strip('%')] return sdk_path else: self._log.error('Android SDK环境变量未配置!!') exit() @staticmethod def _execute_command(cmd: str): """ 执行cmd命令 :param cmd: cmd命令 :return: 命令行输出 """ with os.popen(cmd) as f: result = f.read() return result def _kill_server(self): """ 用于每次执行完毕,杀掉进程 :return: None """ cmd1 = 'tasklist | find "node.exe"' if self._execute_command(cmd1): cmd2 = 'taskkill -F -PID node.exe' self._execute_command(cmd2) self._log.info('杀掉appium server进程') def _create_appium_commands(self, devices_list: list): """ 创建Appium命令行模式启动命令 :param devices_list: 设备名列表 :return: cmd命令列表 """ p_port_list = self._create_useful_ports(4723, devices_list) bp_port_list = self._create_useful_ports(4900, devices_list) self._reader.write_data('connected', 'server_ports', str(p_port_list)) cmd_list = ['appium -a 127.0.0.1 -p {} -bp {}'.format( p_port_list[i], bp_port_list[i] ) for i in range(len(devices_list)) ] self._log.debug('已生成启动命令:{}'.format(cmd_list)) return cmd_list def _create_useful_ports(self, start_port: int, devices_list: list): """ 根据获取的已连接设备创建指定数量的可用端口 :param start_port: 起始端口 :param devices_list: 从命令行自动获取的设备列表 :return: 可用端口列表 """ port_list = [] cmd = 'netstat -ano | findstr {}'.format(start_port) while len(port_list) != len(devices_list): if not self._execute_command(cmd): port_list.append(start_port) start_port += 1 self._log.debug('已生成可用端口:{}'.format(port_list)) return port_list
paths.append(os.path.join(folder, file)) else: if file.endswith('.yaml'): paths.append(os.path.join(folder, file)) apis = [] for filename in paths: with open(filename) as fp: reader = fp.read() all_api = yaml.load(reader, Loader=yaml.FullLoader) for key, api in all_api.items(): if 'pytest' in key: apis.append((api.get('name'), api)) return apis kw = OperateConfig(constant.config_pro_api).get_str('project', 'filename_keyword') _all_api = read_pytest_apis(kw) logger = Logger('执行API测试').get_logger() @allure.title('{api_name}') @pytest.mark.parametrize('api_name,api', _all_api) # 调用全局fixture处理测试结果,每次全量测试时使用,调试时注释掉 @pytest.mark.usefixtures('bar') def test_main_api(api_name, api): """ pytest接口测试主入口 :param api_name: 接口名称 :param api: 接口信息 :return: None """
class Email: """ 发送邮件,主函数:send_email() :param report_file: 测试报告文件名,供解析,并发送带附件的HTML邮件 :param if_result_file: 是否发送API测试结果附件,默认为False,若需要发送该附件,传入True """ def __init__(self, report_file, if_result_file=False): # 获取邮件配置 self._email = OperateConfig(constant.config_common_path) self._s_mtp = self._email.get_str('email', 's_mtp') self._port = self._email.get_str('email', 's_mtp_port') self._from_man = self._email.get_str('email', 'from') self._to_man = self._email.get_str('email', 'to') self._authorization_code = self._email.get_str('email', 'authorization_code') self._report_name = report_file self._result_file = if_result_file def _login_mailbox(self): """ 登录邮箱 :return: SMTP对象 """ s = smtplib.SMTP_SSL(self._s_mtp) s.connect(self._s_mtp, self._port) s.ehlo() s.login(self._from_man, self._authorization_code) return s def _get_report_path(self): """ 生成测试报告文件路径 :return: report_path """ return join(constant.report_path, self._report_name) def _get_report_html(self): """ 解析测试报告文件,返回HTML文本 :return: HTML文本 """ with open(self._get_report_path(), encoding='utf-8', errors='ignore') as fp: return fp.read() def send_email(self, email_subject): """ 发送邮件 :param email_subject: 邮件主题 :return: """ # 引用日志类 log = Logger("发送邮件").get_logger() s_mtp = self._login_mailbox() log.info('成功登录邮箱:%s' % self._from_man) report_path_filename = self._get_report_path() log.info('成功获取测试报告路径:%s' % report_path_filename) email_content = self._get_report_html() log.info('成功解析邮件HTML正文') # 邮件带附件申明,并设置邮件头 log.info('设置邮件头...') msg_root = MIMEMultipart('related') msg_root['from'] = self._from_man msg_root['to'] = ','.join(self._to_man) msg_root['Subject'] = Header(email_subject + ' at ' + asctime(), 'utf-8') # 设置邮件HTML正文 log.info('设置HTML正文...') msg = MIMEText(email_content, 'html', 'utf-8') msg_root.attach(msg) # 设置测试报告HTML附件 log.info('设置HTML附件...') report = MIMEText( open(report_path_filename, 'rb').read(), 'base64', 'utf-8') report.add_header('content-disposition', 'attachment', filename=self._report_name) msg_root.attach(report) # 设置API测试结果EXCEL附件 if self._result_file: log.info('设置EXCEL附件...') excel = MIMEApplication( open(constant.api_result_excel_path, 'rb').read()) excel.add_header( 'Content-Disposition', 'attachment', filename=constant.api_result_excel_path.split('/')[1]) msg_root.attach(excel) # 发送 log.info('正在发送邮件至:%s' % ','.join(self._to_man)) try: s_mtp.sendmail(self._from_man, self._to_man, msg_root.as_string()) except smtplib.SMTPException as err: log.error('邮件发送出错:' + str(err)) else: log.info('成功邮件发送') finally: s_mtp.quit() log.info('成功退出邮箱')
class DBMySql: """ 操作MYSQL """ def __init__(self): # 引用日志类 self._log = Logger("MYSQL").get_logger() # 获取数据库配置 self._db_config = OperateConfig(config_common_path) try: self._conn = pymysql.connect( user=self._db_config.get_str('mysql', 'username'), password=self._db_config.get_str('mysql', 'password'), host=self._db_config.get_str('mysql', 'host'), port=self._db_config.get_str('mysql', 'port'), database=self._db_config.get_str('mysql', 'database')) self._log.info('成功连接数据库') except pymysql.Error as e: self._log.error('数据库连接失败:{}'.format(e)) @property def conn(self): """ 返回数据库连接实例,可单独PyMySql库其他方法 :return: 数据库连接实例 """ return self._conn def disconnect(self): """ 断开连接 :return: None """ self._conn.close() self._log.info('成功断开数据库') def __del__(self): self.disconnect() def select_all(self, sql_string: str): """ 执行查询sql :param sql_string: sql语句 :return: 元组列表 """ c = self._conn.cursor() self._log.info('执行查询语句:%s' % sql_string) x = c.execute(sql_string) datalist = x.fetchall() self._log.info('查询结果如下:') for data in datalist: self._log.debug('第 {} 条数据:{}'.format( datalist.index(data) + 1, data)) c.close() return datalist def select_one(self, sql_string: str): """ 执行查询sql :param sql_string: sql语句 :return: 单个查询字段值或单条记录 """ c = self._conn.cursor() self._log.info('执行查询语句:%s' % sql_string) x = c.execute(sql_string) # 获取查询的单个字段的值或单条记录 data = x.fetchone() self._log.debug('查询结果如下:{}'.format(data)) c.close() if len(data[0]) == 1: return data[0][0] else: return data def execute_sql(self, sql_string: str): """ 执行插入、更新、删除操作 :param sql_string: sql语句 :return: None """ try: c = self._conn.cursor() self._log.info('执行%s语句:%s' % (sql_string.split()[0], sql_string)) c.execute(sql_string) self._conn.commit() c.close() except pymysql.Error as e: self._log.error('执行失败:%s' % str(e)) self._conn.rollback() self._log.error('成功回滚操作')
class DBOracle: """ 操作ORACLE """ def __init__(self): # 引用日志类 self._log = Logger("ORACLE").get_logger() # 获取数据库配置 self._db_config = OperateConfig(config_common_path) self._conn_str = '{}/{}@{}:{}/{}'.format( self._db_config.get_str('oracle', 'username'), self._db_config.get_str('oracle', 'password'), self._db_config.get_str('oracle', 'host'), self._db_config.get_str('oracle', 'port'), self._db_config.get_str('oracle', 'database')) try: self._conn = cx_Oracle.connect(self._conn_str) self._log.info('成功连接数据库') except cx_Oracle.Error as e: self._log.error('数据库连接失败:{}'.format(e)) @property def conn(self): """ 返回数据库连接实例,可单独cx_Oracle库其他方法 :return: 数据库连接实例 """ return self._conn def disconnect(self): """ 断开连接 :return: None """ self._conn.close() self._log.info('成功断开数据库') def __del__(self): self.disconnect() def select_all(self, sql_string: str): """ 执行查询sql :param sql_string: sql语句 :return: 元组列表 """ c = self._conn.cursor() self._log.info('执行查询语句:%s' % sql_string) x = c.execute(sql_string) # 获取全部结果集(元组列表),可使用下标访问结果集,如datalist[0][1] datalist = x.fetchall() self._log.info('查询结果如下:') for data in datalist: self._log.debug('第 {} 条数据:{}'.format( datalist.index(data) + 1, data)) c.close() return datalist def select_one(self, sql_string: str): """ 执行查询sql :param sql_string: sql语句 :return: 单个查询字段值或单条记录 """ c = self._conn.cursor() self._log.info('执行查询语句:%s' % sql_string) x = c.execute(sql_string) # 获取查询的单个字段的值或单条记录 data = x.fetchone() self._log.debug('查询结果如下:{}'.format(data)) c.close() if len(data[0]) == 1: return data[0][0] else: return data def execute_sql(self, sql_string: str): """ 执行插入、更新、删除操作 :param sql_string: sql语句 :return: None """ try: c = self._conn.cursor() self._log.info('执行%s语句:%s' % (sql_string.split()[0], sql_string)) c.execute(sql_string) self._conn.commit() c.close() except cx_Oracle.Error as e: self._log.error('执行失败:%s' % str(e)) self._conn.rollback() self._log.error('成功回滚操作') def exec_function(self, function_name: str, *parameters, **keyword_parameters): """ 执行指定函数,可指定参数 :param function_name: 函数名 :param parameters: 元组可变参数 :param keyword_parameters: 字典可变参数 :return: None """ try: c = self._conn.cursor() self._log.info('执行函数:{}'.format(function_name)) c.callfunc(function_name, *parameters, **keyword_parameters) c.close() except cx_Oracle.Error as e: self._log.error('执行失败:%s' % str(e)) self._conn.rollback() self._log.error('成功回滚操作') def exec_process(self, process_name, *parameters, **keyword_parameters): """ 执行指定存储过程,可指定参数 :param process_name: 过程名 :param parameters: 元组可变参数 :param keyword_parameters: 字典可变参数 :return: None """ try: c = self._conn.cursor() self._log.info('执行过程:{}'.format(process_name)) c.callproc(process_name, *parameters, **keyword_parameters) c.close() except cx_Oracle.Error as e: self._log.error('执行失败:%s' % str(e)) self._conn.rollback() self._log.error('成功回滚操作')
class DBRedis: """ 连接redis执行操作 :param db_: 库,默认第一个(0) """ def __init__(self, db_=0): # 引用日志类 self._log = Logger('REDIS').get_logger() # 获取redis配置信息 self._redis_conf = OperateConfig(config_common_path) try: pool = redis.ConnectionPool( host=self._redis_conf.get_str('redis', 'host'), port=self._redis_conf.get_str('redis', 'port'), password=self._redis_conf.get_str('redis', 'auth'), db=db_, decode_responses=True) self._conn = redis.StrictRedis(connection_pool=pool) self._log.info('成功连接REDIS,db({})'.format(db_)) except redis.exceptions.RedisError as e: self._log.error('REDIS连接失败:{}'.format(e)) def conn(self): """ 返回连接实例,可单独调用redis库的其他方法 :return: 连接实例 """ return self._conn def set_kv(self, key_, value_, ex_=None, px_=None, nx_=False, xx_=False): """ 在Redis中设置值,不存在则创建,存在则修改,并返回值; 对于有些value有业务需求字符串的,加上双引号,如:'"123456"' :param key_: key :param value_: value :param ex_: 过期时间(秒) :param px_: 过期时间(毫秒) :param nx_: 如果设置为True,则只有name不存在时,当前set操作才执行 :param xx_: 如果设置为True,则只有name存在时,当前set操作才执行 :return: 设定的value值 """ self._conn.set(key_, value_, ex=ex_, px=px_, nx=nx_, xx=xx_) self._log.info('设置成功:{}={}'.format(key_, value_)) return value_ def get_v(self, key_): """ 获取指定key的value值 :param key_: key :return: value """ value = self._conn.get(key_) if value: self._log.info('获取到{}={}'.format(key_, value)) return value else: self._log.error('键{}不存在'.format(key_)) def del_kv(self, key_): """ 删除指定的key-value :param key_: key :return: """ value = self._conn.get(key_) if value: self._conn.delete(key_) self._log.info('已删除{}={}'.format(key_, value)) else: self._log.info('指定删除的键不存在')
def __init__(self): # 引用日志类 self._log = Logger('浏览器引擎').get_logger() # 获取配置 self._reader = OperateConfig(constant.config_pro_web)
class BrowserEngine: """ 浏览器引擎,主函数:open_browser() """ def __init__(self): # 引用日志类 self._log = Logger('浏览器引擎').get_logger() # 获取配置 self._reader = OperateConfig(constant.config_pro_web) def open_browser(self): """ 根据配置决定采用何种模式打开浏览器,获取driver :return: driver对象 """ headless = self._reader.get_bool('browser', 'headless') if headless: driver = self._open_browser_with_headless() else: driver = self._open_browser_without_headless() return driver def quit_browser(self, driver): """ 退出浏览器 :param driver: driver对象 :return: None """ driver.quit() # 退出浏览器后留缓冲时间,避免立马执行下个case重启浏览器引起进程冲突 sleep(1.5) self._log.info('关闭{}浏览器'.format(driver.name)) def _open_browser_without_headless(self): """ 有界面模式打开浏览器 :return: driver对象 """ driver = None browser = self._reader.get_str('browser', 'browser').lower() try: if browser == 'chrome': driver = webdriver.Chrome(constant.chrome_path) elif browser == 'firefox': driver = webdriver.Firefox( executable_path=constant.firefox_path, service_log_path=devnull) # 重定向Firefox日志文件至空文件 elif browser == 'ie': driver = webdriver.Ie(constant.ie_path) elif browser == 'edge': driver = webdriver.Edge(constant.edge_path) else: self._log.error(f'暂不支持{browser}浏览器!请自行添加!') exit() try: version = driver.capabilities['browserVersion'] except KeyError: version = driver.capabilities['version'] self._log.info(f'{browser}启动成功,版本号:{version}') sleep(1) return driver except WebDriverException as e: self._log.error('{}启动失败:{}'.format(browser, e)) exit() def _open_browser_with_headless(self): """ 无头模式打开谷歌或火狐 :return: driver对象 """ driver = None browser = self._reader.get_str('browser', 'browser').lower() try: if browser == 'chrome': chrome_options = ChromeOptions() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') driver = webdriver.Chrome(options=chrome_options, executable_path=constant.chrome_path) elif browser == 'firefox': firefox_options = FirefoxOptions() firefox_options.add_argument('--headless') firefox_options.add_argument('--disable-gpu') driver = webdriver.Firefox( options=firefox_options, executable_path=constant.firefox_path, service_log_path=devnull) else: self._log.error(f'{browser}配置有误,或{browser}不支持无头模式,请确认!!') exit() try: version = driver.capabilities['browserVersion'] except KeyError: version = driver.capabilities['version'] self._log.info(f'{browser}启动成功,版本号:{version}') sleep(1) return driver except WebDriverException as e: self._log.error('{}无头模式启动失败:{}'.format(browser, e)) exit() def remote_control_browser(self): """ 根据配置,获取远程driver 注:分布式执行用例时,需要先在服务端启动selenium server :return: driver对象 """ server_address = self._reader.get_str('remote', 'server_address') remote_browser = self._reader.get_str('remote', 'remote_browser').upper() if hasattr(DesiredCapabilities, remote_browser): browser = getattr(DesiredCapabilities, remote_browser) try: driver = Remote( command_executor='http://{}/wd/hub'.format(server_address), desired_capabilities=browser) try: version = driver.capabilities['browserVersion'] except KeyError: version = driver.capabilities['version'] self._log.info(f'{browser}启动成功,版本号:{version}') return driver except WebDriverException as e: self._log.error('远程{}启动失败,请确认:{}'.format(remote_browser, e)) exit() else: self._log.error(f'{remote_browser}配置有误!') exit()