├── .DS_Store ├── .idea ├── Aff_service.iml ├── dictionaries │ └── wangjuan.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── README.md ├── bin ├── .DS_Store ├── __init__.py ├── config │ ├── .DS_Store │ ├── __init__.py │ ├── confManage.py │ ├── confRead.py │ └── config.ini ├── randomly │ ├── .DS_Store │ ├── __init__.py │ ├── randomly_data.py │ ├── randomly_float.py │ ├── randomly_hash.py │ ├── randomly_int.py │ ├── randomly_string.py │ └── randomly_time.py ├── script │ ├── .DS_Store │ ├── __init__.py │ ├── logs.py │ ├── mkDir.py │ ├── writeCase.py │ └── writeCaseYml.py └── unit │ ├── .DS_Store │ ├── __init__.py │ ├── apiMethod.py │ ├── apiSend.py │ ├── apiSendCheck.py │ ├── checkResult.py │ ├── initializeCase.py │ ├── initializeCookie.py │ ├── initializePremise.py │ ├── initializeRelevance.py │ ├── readExpectedResult.py │ ├── readParameter.py │ ├── readResultRelevance.py │ ├── replaceRandomly.py │ └── replaceRelevance.py ├── content ├── .DS_Store ├── data │ ├── .DS_Store │ ├── __init__.py │ ├── cookie.txt │ └── registerFromThirdPart.chlsj ├── page │ ├── .DS_Store │ ├── __init__.py │ └── api │ │ ├── registerFromThirdPart.json │ │ ├── registerFromThirdPart.yml │ │ └── result_registerFromThirdPart.json ├── report │ ├── .DS_Store │ ├── __init__.py │ ├── html │ │ ├── app.js │ │ ├── data │ │ │ ├── attachments │ │ │ │ ├── 304af0ca57799642.txt │ │ │ │ ├── 45bd124d04ccae4a.attach │ │ │ │ ├── 57970c65b56bc98.attach │ │ │ │ ├── 7be0ae4e1a985894.attach │ │ │ │ ├── 8a82ba420cae5564.attach │ │ │ │ ├── 903757b7fff46258.attach │ │ │ │ ├── a42bc2ee2ac1e349.attach │ │ │ │ ├── b575e64856904e.attach │ │ │ │ ├── ba67fab1f091dd23.attach │ │ │ │ ├── c65d9577bc4b56af.attach │ │ │ │ ├── ce8359096e29e5b7.attach │ │ │ │ ├── e02dc41aa35a35d4.txt │ │ │ │ ├── e4d2dd8b170229b0.attach │ │ │ │ └── f8c4f43090afb738.attach │ │ │ ├── behaviors.csv │ │ │ ├── behaviors.json │ │ │ ├── categories.csv │ │ │ ├── categories.json │ │ │ ├── packages.json │ │ │ ├── suites.csv │ │ │ ├── suites.json │ │ │ ├── test-cases │ │ │ │ ├── 7a5644b324f85d27.json │ │ │ │ └── 90e76208292b8b5b.json │ │ │ └── timeline.json │ │ ├── export │ │ │ ├── influxDbData.txt │ │ │ ├── mail.html │ │ │ └── prometheusData.txt │ │ ├── favicon.ico │ │ ├── history │ │ │ ├── categories-trend.json │ │ │ ├── duration-trend.json │ │ │ ├── history-trend.json │ │ │ ├── history.json │ │ │ └── retry-trend.json │ │ ├── index.html │ │ ├── plugins │ │ │ ├── behaviors │ │ │ │ └── index.js │ │ │ ├── packages │ │ │ │ └── index.js │ │ │ └── screen-diff │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ ├── styles.css │ │ └── widgets │ │ │ ├── behaviors.json │ │ │ ├── categories-trend.json │ │ │ ├── categories.json │ │ │ ├── duration-trend.json │ │ │ ├── duration.json │ │ │ ├── environment.json │ │ │ ├── executors.json │ │ │ ├── history-trend.json │ │ │ ├── launch.json │ │ │ ├── retry-trend.json │ │ │ ├── severity.json │ │ │ ├── status-chart.json │ │ │ ├── suites.json │ │ │ └── summary.json │ └── xml │ │ └── .DS_Store ├── resource │ └── __init__.py └── testcase │ ├── .DS_Store │ ├── Template.py │ ├── __init__.py │ └── api │ ├── __pycache__ │ ├── test_registerFromThirdPart.cpython-35-PYTEST.pyc │ ├── test_registerFromThirdPart.cpython-35-pytest-5.2.0.pyc │ └── test_registerFromThirdPart.cpython-38-pytest-5.4.3.pyc │ └── test_registerFromThirdPart.py ├── doc ├── Operation.md └── api_service.xmind ├── log └── .DS_Store ├── requirements.txt └── setupMain.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/.DS_Store -------------------------------------------------------------------------------- /.idea/Aff_service.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/dictionaries/wangjuan.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | chlsj 5 | configparser 6 | pytest 7 | rele 8 | rerunfailures 9 | ruamel 10 | toolbelt 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ApexVCS 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # api_service 2 | #### 简介: 3 | 4 | 基于Pytest+request+Allure的接口自动化框架; 5 | 主要应用于Affiliate接口测试,其他项目也可看情况应用。 6 | 7 | ---- 8 | #### 接口文件: 9 | 10 | 备注:Charles导出接口应选择文件类型为`JSON Session File(.chlsj)` 11 | 12 | ---- 13 | #### 模块类的设计: 14 | 备注:Charles导出接口应选择文件类型为`JSON Session File(.chlsj)`\ 15 | 重要模块介绍: 16 | >1、writeCase.py :自动读取新的Charles文件,并自动生成测试用例 \ 17 | 2、apiMethod.py:封装request方法,可以支持多协议扩展(get\post\put\delete)\ 18 | 3、checkResult.py:封装验证response方法\ 19 | 4、setupMain.py: 核心代码,定义并执行用例集,生成报告 20 | 21 | ---- 22 | 23 | 详细介绍见原文: https://www.jianshu.com/p/6f5bfc1182ae 24 | 25 | -------------------------------------------------------------------------------- /bin/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/bin/.DS_Store -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : __init__.py 6 | -------------------------------------------------------------------------------- /bin/config/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/bin/config/.DS_Store -------------------------------------------------------------------------------- /bin/config/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /bin/config/confManage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | 6 | import re 7 | from bin.config.confRead import Config 8 | 9 | 10 | def host_manage(hos): 11 | """ 12 | host关联配置 13 | :param hos: 14 | :return: 15 | """ 16 | try: 17 | relevance_list = re.findall(r'\${(.*?)}\$', hos) 18 | for n in relevance_list: 19 | pattern = re.compile(r'\${' + n + r'}\$') 20 | host_cf = Config() 21 | host_relevance = host_cf.read_host() 22 | hos = re.sub(pattern, host_relevance[n], hos, count=1) 23 | except TypeError: 24 | pass 25 | return hos 26 | 27 | 28 | def mail_manage(ml): 29 | """ 30 | email关联配置 31 | :param ml: 32 | :return: 33 | """ 34 | try: 35 | relevance_list = re.findall(r"\${(.*?)}\$", ml) 36 | for n in relevance_list: 37 | pattern = re.compile(r'\${' + n + r'}\$') 38 | email_cf = Config() 39 | email_relevance = email_cf.read_email() 40 | ml = re.sub(pattern, email_relevance[n], ml, count=1) 41 | except TypeError: 42 | pass 43 | return ml 44 | 45 | 46 | def dir_manage(directory): 47 | """ 48 | directory关联配置 49 | :param directory: 50 | :return: 51 | """ 52 | try: 53 | relevance_list = re.findall(r"\${(.*?)}\$", directory) 54 | for n in relevance_list: 55 | pattern = re.compile(r'\${' + n + r'}\$') 56 | dir_cf = Config() 57 | dir_relevance = dir_cf.read_dir() 58 | directory = re.sub(pattern, dir_relevance[n], directory, count=1) 59 | except TypeError: 60 | pass 61 | return directory 62 | 63 | 64 | if __name__ == '__main__': 65 | host = host_manage(hos='${pre}$') 66 | dirs = dir_manage(directory='${case_dir}$') 67 | 68 | print(host) 69 | print(dirs) 70 | -------------------------------------------------------------------------------- /bin/config/confRead.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | 6 | from configparser import ConfigParser 7 | import os 8 | 9 | 10 | class Config: 11 | 12 | def __init__(self): 13 | """ 14 | 初始化 15 | """ 16 | self.config = ConfigParser() 17 | self.conf_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini') 18 | if not os.path.exists(self.conf_path): 19 | raise FileNotFoundError("请确保配置文件存在!") 20 | 21 | def set_conf(self, title, value, text): 22 | """ 23 | 配置文件修改 24 | :param title: 25 | :param value: 26 | :param text: 27 | :return: 28 | """ 29 | self.config.set(title, value, text) 30 | with open(self.conf_path, "w+", encoding='utf-8') as f: 31 | return self.config.write(f) 32 | 33 | def add_conf(self, title): 34 | """ 35 | 配置文件添加 36 | :param title: 37 | :return: 38 | """ 39 | self.config.add_section(title) 40 | with open(self.conf_path, "w+", encoding='utf-8') as f: 41 | return self.config.write(f) 42 | 43 | def read_host(self): 44 | """ 45 | 读取配置文件中host相关信息 46 | :return: 47 | """ 48 | self.config.read(self.conf_path, encoding='utf-8') 49 | host = self.config['host'] 50 | return host 51 | 52 | def read_email(self): 53 | """ 54 | 读取配置文件中host相关信息 55 | :return: 56 | """ 57 | self.config.read(self.conf_path, encoding='utf-8') 58 | email = self.config['email'] 59 | return email 60 | 61 | def read_dir(self): 62 | """ 63 | 读取配置文件中directory相关信息 64 | :return: 65 | """ 66 | self.config.read(self.conf_path, encoding='utf-8') 67 | directory = self.config['directory'] 68 | return directory 69 | 70 | 71 | if __name__ == '__main__': 72 | cf = Config() 73 | 74 | -------------------------------------------------------------------------------- /bin/config/config.ini: -------------------------------------------------------------------------------- 1 | [directory] 2 | data_dir=/crm/data/ 3 | page_dir=/crm/page/ 4 | cookie_dir=/crm/data/cookie.txt 5 | report_xml_dir=/crm/report/xml/ 6 | report_html_dir=/crm/report/html/ 7 | case_dir=/crm/testcase/ 8 | 9 | [host] 10 | host=service.papaya.fm 11 | 12 | -------------------------------------------------------------------------------- /bin/randomly/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/bin/randomly/.DS_Store -------------------------------------------------------------------------------- /bin/randomly/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /bin/randomly/randomly_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : choice.py 6 | 7 | import random 8 | 9 | 10 | def choice_data(data): 11 | """ 12 | 获取随机整型数据 13 | :param data: 数组 14 | :return: 15 | """ 16 | _list = data.split(",") 17 | num = random.choice(_list) 18 | return num 19 | 20 | 21 | if __name__ == "__main__": 22 | print(choice_data("400,100,2")) 23 | -------------------------------------------------------------------------------- /bin/randomly/randomly_float.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : random_float.py 6 | 7 | import random 8 | 9 | 10 | def random_float(data): 11 | """ 12 | 获取随机整型数据 13 | :param data: 数组 14 | :return: 15 | """ 16 | try: 17 | start_num, end_num, accuracy = data.split(",") 18 | start_num = int(start_num) 19 | end_num = int(end_num) 20 | accuracy = int(accuracy) 21 | except ValueError: 22 | raise Exception("调用随机整数失败,范围参数或精度有误!\n小数范围精度 %s" % data) 23 | 24 | if start_num <= end_num: 25 | num = random.uniform(start_num, end_num) 26 | else: 27 | num = random.uniform(end_num, start_num) 28 | num = round(num, accuracy) 29 | return num 30 | 31 | 32 | if __name__ == '__main__': 33 | print(random_float("200,100,5")) 34 | -------------------------------------------------------------------------------- /bin/randomly/randomly_hash.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : md5.py 6 | 7 | import hashlib 8 | import re 9 | 10 | 11 | def md5(data): 12 | """ 13 | md5加密 14 | :param data:想要加密的字符 15 | :return: 16 | """ 17 | m1 = hashlib.md5() 18 | m1.update(data.encode("utf-8")) 19 | data = m1.hexdigest() 20 | return data 21 | 22 | 23 | if __name__ == '__main__': 24 | print(md5("ssss")) 25 | -------------------------------------------------------------------------------- /bin/randomly/randomly_int.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : random_int.py 6 | 7 | import random 8 | 9 | 10 | def random_int(scope): 11 | """ 12 | 获取随机整型数据 13 | :param scope: 数据范围 14 | :return: 15 | """ 16 | try: 17 | start_num, end_num = scope.split(",") 18 | start_num = int(start_num) 19 | end_num = int(end_num) 20 | except ValueError: 21 | raise Exception("调用随机整数失败,范围参数有误!\n %s" % str(scope)) 22 | if start_num <= end_num: 23 | num = random.randint(start_num, end_num) 24 | else: 25 | num = random.randint(end_num, start_num) 26 | 27 | return num 28 | 29 | 30 | if __name__ == '__main__': 31 | print(random_int("100,200")) 32 | -------------------------------------------------------------------------------- /bin/randomly/randomly_string.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : random_string.py 6 | 7 | import random 8 | import string 9 | 10 | 11 | def random_string(num_len): 12 | """ 13 | 从a-zA-Z0-9生成制定数量的随机字符 14 | :param num_len: 字符串长度 15 | :return: 16 | """ 17 | try: 18 | num_len = int(num_len) 19 | except ValueError: 20 | raise Exception("从a-zA-Z0-9生成指定数量的随机字符失败!长度参数有误 %s" % num_len) 21 | strings = ''.join(random.sample(string.hexdigits, +num_len)) 22 | return strings 23 | 24 | 25 | if __name__ == '__main__': 26 | print(random_string(16)) 27 | -------------------------------------------------------------------------------- /bin/randomly/randomly_time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : get_time.py 6 | 7 | import datetime 8 | import time 9 | 10 | """ 11 | python中时间日期格式化符号: 12 | ------------------------------------ 13 | %y 两位数的年份表示(00-99) 14 | %Y 四位数的年份表示(000-9999) 15 | %m 月份(01-12) 16 | %d 月内中的一天(0-31) 17 | %H 24小时制小时数(0-23) 18 | %I 12小时制小时数(01-12) 19 | %M 分钟数(00=59) 20 | %S 秒(00-59) 21 | %a 本地简化星期名称 22 | %A 本地完整星期名称 23 | %b 本地简化的月份名称 24 | %B 本地完整的月份名称 25 | %c 本地相应的日期表示和时间表示 26 | %j 年内的一天(001-366) 27 | %p 本地A.M.或P.M.的等价符 28 | %U 一年中的星期数(00-53)星期天为星期的开始 29 | %w 星期(0-6),星期天为星期的开始 30 | %W 一年中的星期数(00-53)星期一为星期的开始 31 | %x 本地相应的日期表示 32 | %X 本地相应的时间表示 33 | %Z 当前时区的名称 # 乱码 34 | %% %号本身 35 | 36 | # datetime.timedelta 代表两个时间之间的时间差 37 | # time.strftime(fmt[,tupletime]) 接收以时间元组,并返回以可读字符串表示的当地时间,格式由fmt决定 38 | # time.strptime(str,fmt='%a %b %d %H:%M:%S %Y') 根据fmt的格式把一个时间字符串解析为时间元组 39 | # time.mktime(tupletime) 接受时间元组并返回时间戳(1970纪元后经过的浮点秒数) 40 | 41 | """ 42 | 43 | 44 | def get_time(time_type, layout, unit="0,0,0,0,0"): 45 | """ 46 | 获取时间 47 | :param time_type: 现在的时间now, 其他时间else_time 48 | :param layout: 10timestamp,13timestamp, else 时间类型 49 | :param unit: 时间单位:[seconds, minutes, hours, days, weeks] 秒,分,时,天,周,所有参数都是可选的,并且默认都是0 50 | :return: 51 | """ 52 | ti = datetime.datetime.now() 53 | if time_type != "now": 54 | resolution = unit.split(",") 55 | try: 56 | ti = ti + datetime.timedelta(seconds=int(resolution[0]), minutes=int(resolution[1]), 57 | hours=int(resolution[2]), days=int(resolution[3]), weeks=int(resolution[4])) 58 | except ValueError: 59 | raise Exception("获取时间错误,时间单位%s" % unit) 60 | if layout == "10timestamp": 61 | ti = ti.strftime('%Y-%m-%d %H:%M:%S') 62 | ti = int(time.mktime(time.strptime(ti, "%Y-%m-%d %H:%M:%S"))) 63 | return ti 64 | elif layout == "13timestamp": 65 | ti = ti.strftime('%Y-%m-%d %H:%M:%S') 66 | ti = int(time.mktime(time.strptime(ti, '%Y-%m-%d %H:%M:%S'))) 67 | # round()是四舍五入 68 | ti = int(round(ti * 1000)) 69 | return ti 70 | else: 71 | ti = ti.strftime(layout) 72 | return ti 73 | 74 | 75 | if __name__ == '__main__': 76 | print(get_time("now", "13timestamp", "12,12,12,12,12")) 77 | print(get_time("else", '%Y-%m-%d %H:%M:%S', '0,0,0,5,0')) 78 | -------------------------------------------------------------------------------- /bin/script/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/bin/script/.DS_Store -------------------------------------------------------------------------------- /bin/script/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /bin/script/logs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : logs.py 6 | 7 | import logging 8 | import time 9 | import sys 10 | 11 | from bin.script.mkDir import mk_dir 12 | 13 | 14 | class LogConfig: 15 | 16 | def __init__(self, path): 17 | """ 18 | 日志配置 19 | :param path: 路径 20 | """ 21 | 22 | runtime = time.strftime('%Y-%m-%d', time.localtime(time.time())) 23 | 24 | mk_dir(path + "/log") 25 | logfile = path + "/log/" + runtime + '.log' 26 | logfile_err = path + "/log/" + runtime + '_error.log' 27 | 28 | logger = logging.getLogger() 29 | logger.setLevel(logging.DEBUG) 30 | logger.handlers = [] 31 | 32 | # 第二步,创建一个handler,用于写入全部info日志文件 33 | 34 | fh = logging.FileHandler(logfile, mode='a+') 35 | fh.setLevel(logging.DEBUG) 36 | 37 | # 第三步,创建一个handler,用于写入错误日志文件 38 | 39 | fh_err = logging.FileHandler(logfile_err, mode='a+') 40 | fh_err.setLevel(logging.ERROR) 41 | 42 | # 第四步,再创建一个handler,用于输出到控制台 43 | ch = logging.StreamHandler(sys.stdout) 44 | ch.setLevel(logging.INFO) 45 | 46 | # 第五步,定义handler的输出格式 47 | formatter = logging.Formatter("%(asctime)s - %(filename)s - %(levelname)s: %(message)s") 48 | fh.setFormatter(formatter) 49 | fh_err.setFormatter(formatter) 50 | ch.setFormatter(formatter) 51 | 52 | # 第六步,将logger添加到handler里面 53 | logger.addHandler(fh) 54 | logger.addHandler(fh_err) 55 | logger.addHandler(ch) 56 | -------------------------------------------------------------------------------- /bin/script/mkDir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : Mkdir.py 6 | 7 | import os 8 | import logging 9 | 10 | 11 | def mk_dir(path): 12 | # 去除首位空格 13 | path = path.strip() 14 | path = path.rstrip("\\") 15 | path = path.rstrip("/") 16 | 17 | # 判断路径是否存在 18 | is_exists = os.path.exists(path) 19 | 20 | if not is_exists: 21 | try: 22 | os.makedirs(path) 23 | except Exception as e: 24 | logging.error("logs目录创建失败:%s" % e) 25 | else: 26 | # 如果目录存在则不创建,并提示目录已存在 27 | logging.debug("logs目录已存在:%s" % str(path)) 28 | pass 29 | -------------------------------------------------------------------------------- /bin/script/writeCase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : writeCase.py 6 | 7 | import os 8 | import shutil 9 | from bin.script.mkDir import mk_dir 10 | from bin.config.confManage import dir_manage 11 | from bin.script.writeCaseYml import write_case_yml 12 | from setupMain import project_path 13 | 14 | 15 | def write_case(_path): 16 | yml_list = write_case_yml(_path) 17 | test_path = project_path+dir_manage('${case_dir}$') 18 | src = test_path+'Template.py' 19 | 20 | for case in yml_list: 21 | yml_path = case.split('/')[0] 22 | yml_name = case.split('/')[1] 23 | case_name = 'test_' + yml_name + '.py' 24 | new_case = test_path + yml_path + '/' + case_name 25 | mk_dir(test_path + yml_path) 26 | if case_name in os.listdir(test_path + yml_path): 27 | pass 28 | else: 29 | shutil.copyfile(src, new_case) 30 | with open(new_case, 'r', encoding='utf-8') as fw: 31 | source = fw.readlines() 32 | n = 0 33 | with open(new_case, 'w', encoding='utf-8') as f: 34 | for line in source: 35 | if 'PATH = project_path' in line: 36 | line = line.replace("offer", "%s" % yml_path) 37 | f.write(line) 38 | n = n+1 39 | elif 'case_dict = ini_case' in line: 40 | line = line.replace("Template", yml_name) 41 | f.write(line) 42 | n = n + 1 43 | elif 'class TestTemplate' in line: 44 | line = line.replace("TestTemplate", "Test%s" % yml_name.title().replace("_", "")) 45 | f.write(line) 46 | n = n + 1 47 | elif '@allure.story' in line: 48 | line = line.replace("Template", yml_name) 49 | f.write(line) 50 | n = n + 1 51 | elif 'def test_template' in line: 52 | line = line.replace("template", yml_name.lower()) 53 | f.write(line) 54 | n = n + 1 55 | 56 | else: 57 | f.write(line) 58 | n += 1 59 | for i in range(n, len(source)): 60 | f.write(source[i]) 61 | 62 | 63 | if __name__ == '__main__': 64 | ym_path = 'D:/api_service/crm/data' 65 | write_case(ym_path) 66 | 67 | -------------------------------------------------------------------------------- /bin/script/writeCaseYml.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : writeCaseYml.py 6 | 7 | import json 8 | import os 9 | import urllib.parse 10 | import logging 11 | from ruamel import yaml 12 | from bin.script.mkDir import mk_dir 13 | from setupMain import project_path 14 | from bin.config.confManage import dir_manage 15 | 16 | 17 | def write_case_yml(har_path): 18 | """ 19 | 循环读取导出文件 20 | :param har_path: Charles导出文件路径 21 | :return: 22 | """ 23 | har_list = os.listdir(har_path) 24 | case_file_list = [] 25 | 26 | for i in har_list: 27 | if 'chlsj' in i: 28 | with open(har_path+'/'+str(i), 'r', encoding='utf-8') as f: 29 | logging.debug("从%s目录下,读取文件%s" % (har_path, i)) 30 | 31 | har_cts = json.loads(f.read()) 32 | har_ct = har_cts[0] 33 | case_list = dict() 34 | scheme = har_ct["scheme"] 35 | method = har_ct["method"] 36 | path = har_ct["path"] 37 | title = path.split("/")[-1] 38 | info_id = "test_" + title + "_01" 39 | parameter_type = har_ct["request"]["mimeType"] 40 | parameter = dict() 41 | try: 42 | if method in 'POST': 43 | parameter_list = urllib.parse.unquote(har_ct["request"]["body"]["text"]) 44 | elif method in 'PUT': 45 | parameter_list = har_ct["request"]["body"]["text"] 46 | elif method in 'DELETE': 47 | parameter_list = urllib.parse.unquote(har_ct["request"]["body"]["text"]) 48 | else: 49 | parameter_list = har_ct["query"] 50 | 51 | if "&" in parameter_list: 52 | 53 | for key in parameter_list.split("&"): 54 | val = key.split("=") 55 | parameter[val[0]] = val[1] 56 | else: 57 | parameter = json.loads(parameter_list) 58 | except Exception as e: 59 | logging.error("未找到parameter: %s" % e) 60 | raise e 61 | 62 | case_path = project_path + dir_manage('${page_dir}$') + path.split("/")[-2] 63 | mk_dir(case_path) 64 | 65 | response_code = har_ct["response"]["status"] 66 | response_body = har_ct["response"]["body"]["text"] 67 | test_info = dict() 68 | test_info["id"] = info_id 69 | test_info["title"] = path.split("/")[-2] 70 | test_info["host"] = '${host}$' 71 | test_info["address"] = path 72 | 73 | # 定义checkout 74 | check = dict() 75 | check["check_type"] = 'json' 76 | check["expected_code"] = response_code 77 | expected_request = json.loads(response_body) 78 | 79 | result_file = 'result_' + title + '.json' 80 | # result参数大于4时,写入result.json中 81 | if len(expected_request) >= 2: 82 | if result_file in os.listdir(case_path): 83 | pass 84 | else: 85 | result_list = [] 86 | result_dicts = dict() 87 | with open(case_path + '/' + result_file, "w", encoding='utf-8') as ff: 88 | result_dicts["test_name"] = title 89 | result_dicts["json"] = expected_request 90 | result_list.append(result_dicts) 91 | 92 | json.dump(result_list, ff, ensure_ascii=False, indent=4) 93 | check["expected_request"] = result_file 94 | 95 | else: 96 | check["expected_request"] = expected_request 97 | 98 | param_file = case_path + '/' + title + '.json' 99 | test_case_list = [] 100 | test_case = dict() 101 | test_case_list.append(test_case) 102 | # para参数大于等于4时,参数文件单独写入json中 103 | if len(parameter) >= 4: 104 | if title + '.json' in os.listdir(case_path): 105 | pass 106 | else: 107 | new_dicts = dict() 108 | new_list = [] 109 | with open(param_file, "w", encoding='utf-8') as fs: 110 | new_dicts["test_name"] = title 111 | new_dicts["parameter"] = parameter 112 | new_list.append(new_dicts) 113 | 114 | json.dump(new_list, fs, ensure_ascii=False, indent=4) 115 | 116 | test_case["test_name"] = title 117 | test_case["info"] = title 118 | test_case["http_type"] = scheme 119 | test_case["request_type"] = method 120 | test_case["parameter_type"] = parameter_type 121 | test_case["address"] = path 122 | test_case["headers"] = {"X-Requested-With": "XMLHttpRequest"} 123 | test_case["cookies"] = True 124 | test_case["timeout"] = 20 125 | test_case["parameter"] = title + '.json' 126 | test_case["file"] = False 127 | test_case["check"] = check 128 | test_case["relevance"] = None 129 | 130 | else: 131 | test_case["test_name"] = title 132 | test_case["info"] = title 133 | test_case["http_type"] = scheme 134 | test_case["request_type"] = method 135 | test_case["parameter_type"] = parameter_type 136 | test_case["address"] = path 137 | test_case["headers"] = {"X-Requested-With": "XMLHttpRequest"} 138 | test_case["cookies"] = True 139 | test_case["timeout"] = 20 140 | test_case["parameter"] = parameter 141 | test_case["file"] = False 142 | test_case["check"] = check 143 | test_case["relevance"] = None 144 | 145 | case_list["test_info"] = test_info 146 | case_list["premise"] = None 147 | case_list["test_case"] = test_case_list 148 | 149 | case_file = case_path + '/' + title + '.yml' 150 | if title + '.yml' in os.listdir(case_path): 151 | pass 152 | else: 153 | with open(case_path + '/' + title + '.yml', 'w+', encoding='utf-8') as ff: 154 | case_file_list.append(path.split("/")[-2]+'/'+title) 155 | logging.debug("从%s目录下,写入测试文件%s" % (case_path, case_file)) 156 | yaml.dump(case_list, ff, Dumper=yaml.RoundTripDumper) 157 | return case_file_list 158 | 159 | 160 | if __name__ == '__main__': 161 | har = '/Users/wangjuan/workpace/api_service/crm/data' 162 | s = write_case_yml(har) 163 | -------------------------------------------------------------------------------- /bin/unit/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/bin/unit/.DS_Store -------------------------------------------------------------------------------- /bin/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /bin/unit/apiMethod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : https.py 6 | 7 | import json 8 | import os 9 | import requests 10 | import random 11 | import logging 12 | import simplejson 13 | from setupMain import project_path 14 | from bin.config.confManage import dir_manage 15 | from requests_toolbelt import MultipartEncoder 16 | 17 | 18 | def post(header, address, request_parameter_type, timeout=8, data=None, files=None): 19 | """ 20 | post请求 21 | :param header: 请求头 22 | :param address: 请求地址 23 | :param request_parameter_type: 请求参数格式(form_data,raw) 24 | :param timeout: 超时时间 25 | :param data: 请求参数 26 | :param files: 文件路径 27 | :return: 28 | """ 29 | if 'form-data' in request_parameter_type: 30 | for i in files: 31 | value = files[i] 32 | if isinstance(value, int): 33 | files[i] = str(value) 34 | pass 35 | elif '/' in value: 36 | file_parm = i 37 | files[file_parm] = (os.path.basename(value), open(value, 'rb'), 'application/octet-stream') 38 | else: 39 | pass 40 | multipart = MultipartEncoder( 41 | fields=files, 42 | boundary='-----------------------------' + str(random.randint(1e28, 1e29 - 1)) 43 | ) 44 | header['Content-Type'] = multipart.content_type 45 | 46 | response = requests.post(url=address, data=multipart, headers=header, timeout=timeout) 47 | else: 48 | data = json.dumps(data) 49 | response = requests.post(url=address, data=data, headers=header, timeout=timeout) 50 | try: 51 | if response.status_code != 200: 52 | return response.status_code, response.text 53 | else: 54 | return response.status_code, response.json() 55 | except json.decoder.JSONDecodeError: 56 | return response.status_code, '' 57 | except simplejson.errors.JSONDecodeError: 58 | return response.status_code, '' 59 | except Exception as e: 60 | logging.exception('ERROR') 61 | logging.error(e) 62 | raise 63 | 64 | 65 | def get(header, address, data, timeout=8): 66 | """ 67 | get请求 68 | :param header: 请求头 69 | :param address: 请求地址 70 | :param data: 请求参数 71 | :param timeout: 超时时间 72 | :return: 73 | """ 74 | response = requests.get(url=address, params=data, headers=header, timeout=timeout) 75 | if response.status_code == 301: 76 | response = requests.get(url=response.headers["location"]) 77 | try: 78 | return response.status_code, response.json() 79 | except json.decoder.JSONDecodeError: 80 | return response.status_code, '' 81 | except simplejson.errors.JSONDecodeError: 82 | return response.status_code, '' 83 | except Exception as e: 84 | logging.exception('ERROR') 85 | logging.error(e) 86 | raise 87 | 88 | 89 | def put(header, address, request_parameter_type, timeout=8, data=None, files=None): 90 | """ 91 | put请求 92 | :param header: 请求头 93 | :param address: 请求地址 94 | :param request_parameter_type: 请求参数格式(form_data,raw) 95 | :param timeout: 超时时间 96 | :param data: 请求参数 97 | :param files: 文件路径 98 | :return: 99 | """ 100 | if request_parameter_type == 'raw': 101 | data = json.dumps(data) 102 | elif request_parameter_type == 'application/json': 103 | data = json.dumps(data) 104 | response = requests.put(url=address, data=data, headers=header, timeout=timeout, files=files) 105 | try: 106 | return response.status_code, response.json() 107 | except json.decoder.JSONDecodeError: 108 | return response.status_code, '' 109 | except simplejson.errors.JSONDecodeError: 110 | return response.status_code, '' 111 | except Exception as e: 112 | logging.exception('ERROR') 113 | logging.error(e) 114 | raise 115 | 116 | 117 | def delete(header, address, data, timeout=8): 118 | """ 119 | get请求 120 | :param header: 请求头 121 | :param address: 请求地址 122 | :param data: 请求参数 123 | :param timeout: 超时时间 124 | :return: 125 | """ 126 | response = requests.delete(url=address, params=data, headers=header, timeout=timeout) 127 | 128 | try: 129 | return response.status_code, response.json() 130 | except json.decoder.JSONDecodeError: 131 | return response.status_code, '' 132 | except simplejson.errors.JSONDecodeError: 133 | return response.status_code, '' 134 | except Exception as e: 135 | logging.exception('ERROR') 136 | logging.error(e) 137 | raise 138 | 139 | 140 | def save_cookie(header, address, timeout=8, data=None, files=None): 141 | """ 142 | 保存cookie信息 143 | :param header: 请求头 144 | :param address: 请求地址 145 | :param timeout: 超时时间 146 | :param data: 请求参数 147 | :param files: 文件路径 148 | :return: 149 | """ 150 | cookie_path = project_path + dir_manage('${cookie_dir}$') 151 | response = requests.post(url=address, data=data, headers=header, timeout=timeout, files=files) 152 | try: 153 | cookie = response.cookies.get_dict() 154 | for i in cookie: 155 | values = cookie[i] 156 | with open(cookie_path, 'w+', encoding='utf-8')as f: 157 | f.write(i+"="+values) 158 | logging.debug("cookies已保存,结果为:%s" % (i+"="+values)) 159 | except json.decoder.JSONDecodeError: 160 | return response.status_code, '' 161 | except simplejson.errors.JSONDecodeError: 162 | return response.status_code, '' 163 | except Exception as e: 164 | logging.exception('ERROR') 165 | logging.error(e) 166 | raise 167 | -------------------------------------------------------------------------------- /bin/unit/apiSend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : send_request.py 6 | 7 | import logging 8 | import allure 9 | 10 | from bin.unit import apiMethod, replaceRelevance 11 | from bin.unit import initializeCookie 12 | from bin.config import confManage 13 | from bin.unit import readParameter 14 | 15 | 16 | def send_request(data, host, address, _path, relevance=None): 17 | """ 18 | 封装请求 19 | :param data: 测试用例 20 | :param host: 测试host 21 | :param address: 接口地址 22 | :param relevance: 关联对象 23 | :param _path: case路径 24 | :return: 25 | """ 26 | logging.info("="*100) 27 | header = readParameter.read_param(data["test_name"], data["headers"], _path, relevance) 28 | if data["cookies"] is True: 29 | header["Cookie"] = initializeCookie.ini_cookie() 30 | logging.debug("请求头处理结果:%s" % header) 31 | parameter = readParameter.read_param(data["test_name"], data["parameter"], _path, relevance) 32 | logging.debug("请求参数处理结果:%s" % parameter) 33 | try: 34 | host = data["host"] 35 | except KeyError: 36 | pass 37 | try: 38 | address = data["address"] 39 | except KeyError: 40 | pass 41 | host = confManage.host_manage(host) 42 | address = replaceRelevance.replace(address, relevance) 43 | logging.debug("host处理结果: %s" % host) 44 | if not host: 45 | raise Exception("接口请求地址为空 %s" % data["headers"]) 46 | logging.info("请求接口:%s" % str(data["test_name"])) 47 | logging.info("请求地址:%s" % data["http_type"] + "://" + host + address) 48 | logging.info("请求头: %s" % str(header)) 49 | logging.info("请求参数: %s" % str(parameter)) 50 | if data["test_name"] == 'password正确': 51 | with allure.step("保存cookie信息"): 52 | allure.attach(name="请求接口", body=str(data["test_name"])) 53 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 54 | allure.attach(name="请求头", body=str(header)) 55 | allure.attach(name="请求参数", body=str(parameter)) 56 | apiMethod.save_cookie(header=header, address=data["http_type"] + "://" + host + address, data=parameter) 57 | 58 | if data["request_type"].lower() == 'post': 59 | logging.info("请求方法: POST") 60 | if data["file"]: 61 | with allure.step("POST上传文件"): 62 | allure.attach(name="请求接口", body=str(data["test_name"])) 63 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 64 | allure.attach(name="请求头", body=str(header)) 65 | allure.attach(name="请求参数", body=str(parameter)) 66 | result = apiMethod.post(header=header, 67 | address=data["http_type"] + "://" + host + address, 68 | request_parameter_type=data["parameter_type"], 69 | files=parameter, 70 | timeout=data["timeout"]) 71 | else: 72 | with allure.step("POST请求接口"): 73 | allure.attach(name="请求接口", body=str(data["test_name"])) 74 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 75 | allure.attach(name="请求头", body=str(header)) 76 | allure.attach(name="请求参数", body=str(parameter)) 77 | result = apiMethod.post(header=header, 78 | address=data["http_type"] + "://" + host + address, 79 | request_parameter_type=data["parameter_type"], 80 | data=parameter, 81 | timeout=data["timeout"]) 82 | elif data["request_type"].lower() == 'get': 83 | with allure.step("GET请求接口"): 84 | allure.attach(name="请求接口", body=str(data["test_name"])) 85 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 86 | allure.attach(name="请求头", body=str(header)) 87 | allure.attach(name="请求参数", body=str(parameter)) 88 | logging.info("请求方法: GET") 89 | result = apiMethod.get(header=header, 90 | address=data["http_type"] + "://" + host + address, 91 | data=parameter, 92 | timeout=data["timeout"]) 93 | elif data["request_type"].lower() == 'put': 94 | logging.info("请求方法: PUT") 95 | if data["file"]: 96 | with allure.step("PUT上传文件"): 97 | allure.attach(name="请求接口", body=str(data["test_name"])) 98 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 99 | allure.attach(name="请求头", body=str(header)) 100 | allure.attach(name="请求参数", body=str(parameter)) 101 | result = apiMethod.put(header=header, 102 | address=data["http_type"] + "://" + host + address, 103 | request_parameter_type=data["parameter_type"], 104 | files=parameter, 105 | timeout=data["timeout"]) 106 | else: 107 | with allure.step("PUT请求接口"): 108 | allure.attach(name="请求接口", body=str(data["test_name"])) 109 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 110 | allure.attach(name="请求头", body=str(header)) 111 | allure.attach(name="请求参数", body=str(parameter)) 112 | result = apiMethod.put(header=header, 113 | address=data["http_type"] + "://" + host + address, 114 | request_parameter_type=data["parameter_type"], 115 | data=parameter, 116 | timeout=data["timeout"]) 117 | elif data["request_type"].lower() == 'delete': 118 | with allure.step("DELETE请求接口"): 119 | allure.attach(name="请求接口", body=str(data["test_name"])) 120 | allure.attach(name="请求地址", body=data["http_type"] + "://" + host + address) 121 | allure.attach(name="请求头", body=str(header)) 122 | allure.attach(name="请求参数", body=str(parameter)) 123 | logging.info("请求方法: DELETE") 124 | result = apiMethod.delete(header=header, 125 | address=data["http_type"] + "://" + host + address, 126 | data=parameter, 127 | timeout=data["timeout"]) 128 | else: 129 | result = {"code": False, "data": False} 130 | logging.info("请求接口结果:\n %s" % str(result)) 131 | return result 132 | -------------------------------------------------------------------------------- /bin/unit/apiSendCheck.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : apiSendCheck.py 6 | 7 | 8 | from bin.unit import checkResult, apiSend 9 | from bin.unit.readResultRelevance import get_relevance 10 | 11 | 12 | def api_send_check(case, project_dict, relevance, _path, rel=None): 13 | """ 14 | 接口请求并校验结果 15 | :param case: 单条用例 16 | :param project_dict: 用例文件对象 17 | :param relevance: 关键值实例对象 18 | :param rel: 关联值类对象 19 | :param _path: case目录 20 | :return: 21 | """ 22 | code, data = apiSend.send_request(case, project_dict["test_info"].get("host"), 23 | project_dict["test_info"].get("address"), _path, relevance) 24 | if isinstance(case["check"], list): 25 | for i in case["check"]: 26 | checkResult.check_result(case["test_name"], i, code, data, _path, relevance) 27 | else: 28 | checkResult.check_result(case["test_name"], case["check"], code, data, _path, relevance) 29 | 30 | get_relevance(data, case["relevance"], rel) 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /bin/unit/checkResult.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : check_result.py 6 | 7 | import operator 8 | import re 9 | import allure 10 | 11 | from bin.unit import readExpectedResult 12 | 13 | 14 | def check_json(src_data, dst_data): 15 | """ 16 | 校验的json 17 | :param src_data: 检验内容 18 | :param dst_data: 接口返回的数据 19 | :return: 20 | """ 21 | if isinstance(src_data, dict): 22 | for key in src_data: 23 | if key not in dst_data: 24 | raise Exception("JSON格式校验,关键字%s不在返回结果%s中" % (key, dst_data)) 25 | else: 26 | this_key = key 27 | if isinstance(src_data[this_key], dict) and isinstance(dst_data[this_key], dict): 28 | check_json(src_data[this_key], dst_data[this_key]) 29 | elif isinstance(type(src_data[this_key]), type(dst_data[this_key])): 30 | raise Exception("JSON格式校验,关键字 %s 与 %s 类型不符" % (src_data[this_key], dst_data[this_key])) 31 | else: 32 | pass 33 | else: 34 | raise Exception("JSON校验内容非dict格式") 35 | 36 | 37 | def check_result(test_name, case, code, data, _path, relevance=None): 38 | """ 39 | 校验测试结果 40 | :param test_name: 测试名称 41 | :param case: 测试用例 42 | :param code: HTTP状态 43 | :param data: 返回的接口json数据 44 | :param relevance: 关联值对象 45 | :param _path: case路径 46 | :return: 47 | """ 48 | 49 | if case["check_type"] == 'no_check': 50 | with allure.step("不校验结果"): 51 | pass 52 | elif case["check_type"] == 'json': 53 | expected_request = case["expected_request"] 54 | if isinstance(case["expected_request"], str): 55 | expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance) 56 | with allure.step("JSON格式校验"): 57 | allure.attach(name="期望code", body=str(case["expected_code"])) 58 | allure.attach(name='期望data', body=str(expected_request)) 59 | allure.attach(name="实际code", body=str(code)) 60 | allure.attach(name='实际data', body=str(data)) 61 | if int(code) == case["expected_code"]: 62 | if not data: 63 | data = "{}" 64 | check_json(expected_request, data) 65 | else: 66 | raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) 67 | 68 | elif case["check_type"] == 'only_check_status': 69 | with allure.step("校验HTTP状态"): 70 | allure.attach(name="期望code", body=str(case["expected_code"])) 71 | allure.attach(name="实际code", body=str(code)) 72 | allure.attach(name='实际data', body=str(data)) 73 | if int(code) == case["expected_code"]: 74 | pass 75 | else: 76 | raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) 77 | 78 | elif case["check_type"] == 'entirely_check': 79 | expected_request = case["expected_request"] 80 | if isinstance(case["expected_request"], str): 81 | expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance) 82 | with allure.step("完全校验"): 83 | allure.attach(name="期望code", body=str(case["expected_code"])) 84 | allure.attach(name='期望data', body=str(expected_request)) 85 | allure.attach(name="实际code", body=str(code)) 86 | allure.attach(name='实际data', body=str(data)) 87 | if int(code) == case["expected_code"]: 88 | result = operator.eq(expected_request, data) 89 | if result: 90 | pass 91 | else: 92 | raise Exception("完全校验失败! %s ! = %s" % (expected_request, data)) 93 | else: 94 | raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) 95 | 96 | elif case["check_type"] == 'Regular_check': 97 | if int(code) == case["expected_code"]: 98 | try: 99 | result = "" 100 | if isinstance(case["expected_request"], list): 101 | for i in case[""]: 102 | result = re.findall(i.replace("\"", "\""), str(data)) 103 | allure.attach('校验完成结果\n', str(result)) 104 | else: 105 | result = re.findall(case["expected_request"].replace("\"", "\'"), str(data)) 106 | with allure.step("正则校验"): 107 | allure.attach(name="期望code", body=str(case["expected_code"])) 108 | allure.attach(name='正则表达式', body=str(case["expected_request"]).replace("\'", "\"")) 109 | allure.attach(name="实际code", body=str(code)) 110 | allure.attach(name='实际data', body=str(data)) 111 | allure.attach(name=case["expected_request"].replace("\"", "\'") + '校验完成结果', 112 | body=str(result).replace("\'", "\"")) 113 | if not result: 114 | raise Exception("正则未校验到内容! %s" % case["expected_request"]) 115 | except KeyError: 116 | raise Exception("正则校验执行失败! %s\n正则表达式为空时" % case["expected_request"]) 117 | else: 118 | raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) 119 | 120 | else: 121 | raise Exception("无该校验方式%s" % case["check_type"]) 122 | -------------------------------------------------------------------------------- /bin/unit/initializeCase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : ini_case.py 6 | 7 | import yaml 8 | 9 | 10 | def ini_case(_path, case_file): 11 | """ 12 | case初始化.yml测试用例 13 | :param _path: case路径 14 | :param case_file: case名称 15 | :return: 16 | """ 17 | try: 18 | with open(_path + '/' + case_file + '.yml', 'r', encoding="utf-8") as f: 19 | project_dict = yaml.load(f) 20 | except FileNotFoundError: 21 | with open(_path + '/' + case_file + '.yaml', 'r', encoding="utf-8") as f: 22 | project_dict = yaml.load(f) 23 | return project_dict 24 | -------------------------------------------------------------------------------- /bin/unit/initializeCookie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : ini_cookie.py 6 | 7 | import os 8 | from setupMain import project_path 9 | from bin.config.confManage import dir_manage 10 | 11 | 12 | def ini_cookie(): 13 | """ 14 | 读取cookie文件 15 | :return: 16 | """ 17 | file = project_path + dir_manage('${cookie_dir}$') 18 | with open(file, 'rb') as f: 19 | cookie = f.read().decode() 20 | 21 | return cookie.strip("\n") 22 | -------------------------------------------------------------------------------- /bin/unit/initializePremise.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : ini_request.py 6 | 7 | import logging 8 | import time 9 | import allure 10 | 11 | from bin.unit.readResultRelevance import get_relevance 12 | from bin.unit.apiSend import send_request 13 | 14 | 15 | def ini_request(case_dict, _path, relevance=None): 16 | """ 17 | 用例前提条件执行,提取关键值 18 | :param case_dict: 用例对象 19 | :param relevance: 关联对象 20 | :param _path: case路径 21 | :return: 22 | """ 23 | if isinstance(case_dict["premise"], list): 24 | logging.info("执行测试用例前置接口") 25 | with allure.step("接口关联请求"): 26 | for i in case_dict["premise"]: 27 | relevance_list = {} 28 | for j in range(0, 3): 29 | code, data = send_request(i, case_dict["test_info"].get("host"), 30 | i["address"], relevance_list, _path) 31 | if not data: 32 | with allure.step("接口请求失败!等待三秒后重试!"): 33 | pass 34 | if i["relevance"]: 35 | if len(i["relevance"]): 36 | relevance = get_relevance(data, i["relevance"], relevance) 37 | print(relevance) 38 | if isinstance(relevance, bool): 39 | with allure.step("从结果中提取关联键的值失败!等待3秒后重试!"): 40 | pass 41 | logging.info("从结果中提取关联键的值失败!等待3秒后重试!") 42 | time.sleep(3) 43 | continue 44 | else: 45 | break 46 | else: 47 | break 48 | else: 49 | break 50 | if isinstance(relevance, bool): 51 | logging.info("从结果中提取关联键的值失败!重试三次失败") 52 | raise Exception("获取前置接口关联数据失败") 53 | else: 54 | pass 55 | return relevance 56 | -------------------------------------------------------------------------------- /bin/unit/initializeRelevance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : ini_relevance.py 6 | 7 | import configparser 8 | import logging 9 | 10 | from bin.unit.replaceRandomly import replace_random 11 | 12 | 13 | def get_relevance_conf(_path): 14 | """ 15 | 读取关联文件配置 16 | :param _path: 17 | :return: 18 | """ 19 | logging.info("初始化关联文件") 20 | config = configparser.ConfigParser() 21 | config.read(_path, encoding="utf-8") 22 | relevance = config["relevance"] 23 | relevance_conf = dict() 24 | logging.debug("读取关联文件内容: %s" % relevance.items()) 25 | for key, value in relevance.items(): 26 | relevance_conf[key] = replace_random(value) 27 | logging.debug("初始化关联的数据:%s" % relevance_conf) 28 | return relevance_conf 29 | 30 | 31 | def ini_relevance(_path, file): 32 | """ 33 | 初始化关联文件 34 | :param _path: 文件路径 35 | :param file: 文件名称 36 | :return: 37 | """ 38 | relevance = get_relevance_conf(_path + '/' + file + '.ini') 39 | return relevance 40 | -------------------------------------------------------------------------------- /bin/unit/readExpectedResult.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : read_json.py 6 | 7 | import json 8 | from json import JSONDecodeError 9 | 10 | from bin.unit.replaceRelevance import replace 11 | 12 | 13 | def read_json(test_names, code_json, _path, relevance=None): 14 | """ 15 | 校验内容读取 16 | :param test_names: 用例名称 17 | :param code_json: 文件路径 18 | :param relevance: 关联对象 19 | :param _path: case路径 20 | :return: 21 | """ 22 | if isinstance(code_json, dict): 23 | code_json = replace(code_json, relevance) 24 | else: 25 | try: 26 | with open(_path+'/'+code_json, "r", encoding="utf-8") as file: 27 | data = json.load(file) 28 | for i in data: 29 | if i['test_name'] == test_names: 30 | code_json = i['json'] 31 | break 32 | if not code_json: 33 | raise Exception("未找到用例关联的期望结果\n文件路径: %s\n索引: %s" % (code_json, test_names)) 34 | else: 35 | code_json = replace(code_json, relevance) 36 | except FileNotFoundError: 37 | raise Exception("用例关联文件不存在\n文件路径: %s" % code_json) 38 | except JSONDecodeError: 39 | raise Exception("用例关联的期望文件有误\n文件路径: %s" % code_json) 40 | 41 | return code_json 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /bin/unit/readParameter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : read_param.py 6 | 7 | import json 8 | 9 | from json import JSONDecodeError 10 | from bin.unit.replaceRelevance import replace 11 | 12 | 13 | def read_param(test_name, param, _path, relevance=None): 14 | """ 15 | 读取用例中参数parameter 16 | :param test_name: 用例名称 17 | :param param: parameter 18 | :param relevance: 关联对象 19 | :param _path: case路径 20 | :param result: 全局结果 21 | :return: 22 | """ 23 | 24 | if isinstance(param, dict): 25 | param = replace(param, relevance) 26 | elif isinstance(param, list): 27 | param = replace(param, relevance) 28 | elif param is None: 29 | pass 30 | else: 31 | try: 32 | with open(_path + "/" + param, "r", encoding="utf-8") as f: 33 | data = json.load(f) 34 | for i in data: 35 | if i["test_name"] == test_name: 36 | param = i["parameter"] 37 | break 38 | if not isinstance(param, dict): 39 | raise Exception("未能找到用例关联的参数\n文件路径:%s\n索引:%s" % (param, _path)) 40 | else: 41 | param = replace(param, relevance) 42 | except FileNotFoundError: 43 | raise Exception("用例关联文件不存在\n文件路径: %s" % param) 44 | except JSONDecodeError: 45 | raise Exception("用例关联的参数文件有误\n文件路径: %s" % param) 46 | return param 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /bin/unit/readResultRelevance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : get_relevance.py 6 | 7 | import logging 8 | 9 | from bin.unit.replaceRelevance import get_value 10 | 11 | 12 | def get_relevance(data, relevance_list, relevance): 13 | """ 14 | 从返回结果中获取关联值 15 | :param data: 16 | :param relevance_list: 17 | :param relevance: 18 | :return: 19 | """ 20 | # 关联键是否时list 21 | if not relevance_list: 22 | return relevance 23 | logging.debug("从返回结果中根据关联键%s提取值" % relevance_list) 24 | if isinstance(relevance_list, list): 25 | # 遍历关联键 26 | for j in relevance_list: 27 | # 从结果中提取关联键的值 28 | relevance_value = get_value(data, j) 29 | print(relevance_value) 30 | if relevance_value: 31 | # 考虑到一个关联键,多个值 32 | if j in relevance: 33 | if isinstance(relevance[j], list): 34 | a = relevance[j] 35 | a.append(relevance_value) 36 | relevance[j] = a 37 | else: 38 | a = relevance[j] 39 | b = list() 40 | b.append(a) 41 | b.append(relevance_value) 42 | relevance[j] = b 43 | else: 44 | relevance[j] = relevance_value 45 | else: 46 | relevance = relevance_value 47 | else: 48 | relevance_value = get_value(data, relevance_list) 49 | if relevance_value: 50 | # 考虑到一个关联键,多个值 51 | if relevance_list in relevance: 52 | if isinstance(relevance_list, list): 53 | a = relevance[relevance_list] 54 | a.append(relevance_value) 55 | relevance[relevance_list] = a 56 | else: 57 | a = relevance[relevance_list] 58 | b = list() 59 | b.append(a) 60 | b.append(relevance_value) 61 | relevance[relevance_list] = a 62 | else: 63 | relevance[relevance_list] = relevance_value 64 | logging.debug("提取后,关联键对象\n%s" % relevance) 65 | return relevance 66 | -------------------------------------------------------------------------------- /bin/unit/replaceRandomly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : replace_random.py 6 | 7 | import re 8 | 9 | from bin.randomly import randomly_float, randomly_int, randomly_string, randomly_time, randomly_data 10 | 11 | 12 | def replace_random(value): 13 | """ 14 | 调用定义方法替换字符串 15 | :param value: 16 | :return: 17 | """ 18 | int_list = re.findall(r'\$RandomInt\(([0-9]*,[0-9]*?)\)\$', value) 19 | string_list = re.findall(r'\$RandomString\(([0-9]*?)\)\$', value) 20 | float_list = re.findall(r'\$RandomFloat\(([0-9]*,[0-9]*,[0-9]*)\)\$', value) 21 | time_list = re.findall(r"\$GetTime\(time_type=(.*?),layout=(.*?),unit=([0-9],[0-9],[0-9],[0-9],[0-9])\)\$", value) 22 | choice_list = re.findall(r"\$Choice\((.*?)\)\$", value) 23 | 24 | if len(int_list): 25 | # 获取整型数据替换 26 | for i in int_list: 27 | pattern = re.compile(r'\$RandomInt\(' + i + r'\)\$') 28 | key = str(randomly_int.random_int(i)) 29 | value = re.sub(pattern, key, value, count=1) 30 | value = replace_random(value) 31 | 32 | elif len(string_list): 33 | # 获取字符串数据替换 34 | for j in string_list: 35 | pattern = re.compile(r'\$RandomString\(' + j + r'\)\$') 36 | key = str(randomly_string.random_string(j)) 37 | value = re.sub(pattern, key, value, count=1) 38 | value = replace_random(value) 39 | 40 | elif len(float_list): 41 | # 获取浮点数数据替换 42 | for n in float_list: 43 | pattern = re.compile(r'\$RandomFloat\(' + n + r'\)\$') 44 | key = str(randomly_float.random_float(n)) 45 | value = re.sub(pattern, key, value, count=1) 46 | value = replace_random(value) 47 | 48 | elif len(time_list): 49 | # 获取时间替换 50 | for m in time_list: 51 | if len(m[0]) and len(m[1]): 52 | pattern = re.compile(r'\$GetTime\(time_type=' + m[0] + ',layout=' + m[1] + ',unit=' + m[2] + '\)\$') 53 | key = str(randomly_time.get_time(m[0], m[1], m[2])) 54 | value = re.sub(pattern, key, value, count=1) 55 | else: 56 | print("$GetTime$参数错误,time_type, layout为必填") 57 | value = replace_random(value) 58 | 59 | elif len(choice_list): 60 | # 调用choice方法 61 | for i in choice_list: 62 | pattern = re.compile(r'\$Choice\(' + i + r'\)\$') 63 | key = str(randomly_data.choice_data(i)) 64 | value = re.sub(pattern, key, value, count=1) 65 | value = replace_random(value) 66 | 67 | else: 68 | pass 69 | return value 70 | 71 | 72 | if __name__ == '__main__': 73 | int_num = '$RandomInt($RandomInt(2,13)$,$RandomInt(2,13)$)$' 74 | str_num = '$RandomString($RandomInt(2,23)$)$' 75 | float_num = '$RandomFloat($RandomInt(2,13)$,$RandomInt(2,13)$,$RandomInt(2,13)$)$' 76 | time_num = '$GetTime(time_type=else,layout=13timestamp,unit=0,0,0,0,6)$' 77 | choice_num = '$Choice($RandomInt(2,13)$,$RandomInt(2,13)$)$' 78 | print(replace_random(int_num)) 79 | print(replace_random(str_num)) 80 | print(replace_random(float_num)) 81 | print(replace_random(time_num)) 82 | print(replace_random(choice_num)) 83 | -------------------------------------------------------------------------------- /bin/unit/replaceRelevance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : replace_relevance.py 6 | 7 | 8 | import re 9 | # from bin.unit.initializeCase import ini_case 10 | 11 | 12 | def replace(param, relevance=None): 13 | """ 14 | 替换关联数据 15 | :param param: 请求参数 16 | :param relevance: 关联对象 17 | :return: 18 | """ 19 | if isinstance(param, dict): 20 | for key, value in param.items(): 21 | if isinstance(value, dict): 22 | param[key] = replace(value, relevance) 23 | elif isinstance(value, list): 24 | for k, i in enumerate(value): 25 | param[key][k] = replace(i, relevance) 26 | else: 27 | try: 28 | relevance_list = re.findall(r"\${(.*?)}\$", value) 29 | 30 | relevance_index = 0 31 | for n in relevance_list: 32 | pattern = re.compile(r'\${' + n + r'}\$') 33 | n = n.lower() 34 | try: 35 | if isinstance(relevance[n], list): 36 | try: 37 | param[key] = re.sub(pattern, relevance[n][relevance_index], param[key], count=1) 38 | relevance_index += 1 39 | except IndexError: 40 | relevance_index = 0 41 | param[key] = re.sub(pattern, relevance[n][relevance_index], param[key], count=1) 42 | relevance_index += 1 43 | else: 44 | param[key] = re.sub(pattern, relevance[n], param[key], count=1) 45 | except KeyError: 46 | pass 47 | except TypeError: 48 | pass 49 | try: 50 | param[key] = param[key] 51 | except TypeError: 52 | pass 53 | 54 | elif isinstance(param, list): 55 | for k, i in enumerate(param): 56 | param[k] = replace(i, relevance) 57 | else: 58 | try: 59 | relevance_list = re.findall(r"\${(.*?)}\$", param) 60 | relevance_index = 0 61 | for n in relevance_list: 62 | pattern = re.compile(r'\${' + n + r'}\$') 63 | try: 64 | if isinstance(relevance[n], list): 65 | try: 66 | param = re.sub(pattern, relevance[n][relevance_index], param, count=1) 67 | relevance_index += 1 68 | except IndexError: 69 | relevance_index = 0 70 | param = re.sub(pattern, relevance[n][relevance_index], param, count=1) 71 | relevance_index += 1 72 | else: 73 | param = re.sub(pattern, relevance[n], param) 74 | except KeyError: 75 | pass 76 | except TypeError: 77 | pass 78 | 79 | return param 80 | 81 | 82 | __relevance = "" 83 | 84 | 85 | def get_value(data, value): 86 | """ 87 | 获取json中的值 88 | :param data: json数据 89 | :param value: 值 90 | :return: 91 | """ 92 | global __relevance 93 | if isinstance(data, dict): 94 | if value in data: 95 | __relevance = data[value] 96 | else: 97 | for key in data: 98 | __relevance = get_value(data[key], value) 99 | elif isinstance(data, list): 100 | for key in data: 101 | if isinstance(key, dict): 102 | __relevance = get_value(key, value) 103 | break 104 | return __relevance 105 | -------------------------------------------------------------------------------- /content/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/.DS_Store -------------------------------------------------------------------------------- /content/data/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/data/.DS_Store -------------------------------------------------------------------------------- /content/data/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/05 3 | # @Author : XiaoXi 4 | # @PROJECT : api_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /content/data/cookie.txt: -------------------------------------------------------------------------------- 1 | crm-frontend-pre1=http; _ga=GA1.2.549558715.1590144919; _gid=GA1.2.211509637.1590372044; _gat=1; token=eyJleHAiOjE1OTAzOTg4MTYsImFsZyI6IkhTMjU2IiwiaWF0IjoxNTkwMzc3MjE2fQ.eyJpZCI6MjIyfQ.Hkcdt2UDsl6k9cZ1k6k1VTtL48m7cnhbgqkPKxg2TPU; AWSALB=wLIyMl0Othii928BwlCVyGeIJ2PMFI+hz8l6LTge1WyO6x7Jv9Nr0V4EmqNlaDT8TkxE2AvLOjOjZ2NNPlKmXqtOeLUILV8YsTotvb/MuroPRQncm+d6t7KMD8u2; AWSALBCORS=wLIyMl0Othii928BwlCVyGeIJ2PMFI+hz8l6LTge1WyO6x7Jv9Nr0V4EmqNlaDT8TkxE2AvLOjOjZ2NNPlKmXqtOeLUILV8YsTotvb/MuroPRQncm+d6t7KMD8u2 -------------------------------------------------------------------------------- /content/data/registerFromThirdPart.chlsj: -------------------------------------------------------------------------------- 1 | [{"status":"COMPLETE","method":"POST","protocolVersion":"HTTP/1.1","scheme":"http","host":"service-pre1.fm-eks.kiwisns.com","actualPort":80,"path":"/api/registerFromThirdPart","query":null,"tunnel":false,"keptAlive":false,"webSocket":false,"remoteAddress":"service-pre1.fm-eks.kiwisns.com/34.218.53.171","clientAddress":"/192.168.8.196","clientPort":59916,"times":{"start":"2020-05-26T16:01:15.704+08:00","requestBegin":"2020-05-26T16:01:15.996+08:00","requestComplete":"2020-05-26T16:01:15.997+08:00","responseBegin":"2020-05-26T16:01:16.535+08:00","end":"2020-05-26T16:01:16.537+08:00"},"durations":{"total":831,"dns":46,"connect":244,"ssl":null,"request":1,"response":2,"latency":538},"speeds":{"overall":1955,"request":680000,"response":472500},"totalSize":1625,"request":{"sizes":{"headers":498,"body":182},"mimeType":"application/x-www-form-urlencoded","charset":null,"contentEncoding":null,"header":{"firstLine":"POST /api/registerFromThirdPart HTTP/1.1","headers":[{"name":"Host","value":"service-pre1.fm-eks.kiwisns.com"},{"name":"User-Agent","value":"PapayaFM%20Pre/595 CFNetwork/1121.2.2 Darwin/19.3.0"},{"name":"Accept","value":"*/*"},{"name":"channel","value":"AppStore"},{"name":"appVersion","value":"3.3.8"},{"name":"Accept-Language","value":"en-us"},{"name":"Accept-Encoding","value":"gzip, deflate"},{"name":"Content-Type","value":"application/x-www-form-urlencoded"},{"name":"deviceId","value":"cc66c802-019b-73a2-4473-e775935cbe9e"},{"name":"operator","value":""},{"name":"language","value":"en"},{"name":"timezone","value":"Asia/Shanghai"},{"name":"Content-Length","value":"182"},{"name":"device","value":"iPhone XR"},{"name":"osVersion","value":"13.3.1"},{"name":"build","value":"595"},{"name":"Connection","value":"keep-alive"}]},"body":{"text":"{ \"loginEmail\" : \"222@222.com\", \"loginOptions\" : \"papaya_login\", \"deviceId\" : \"D28BD97C-B94D-455A-AD08-8B8FF6AFB57E\", \"timeZone\" : \"Asia\\/Shanghai\", \"password\" : \"123456\" }","charset":null}},"response":{"status":200,"sizes":{"headers":436,"body":509},"mimeType":"application/json","charset":null,"contentEncoding":null,"header":{"firstLine":"HTTP/1.1 200 OK","headers":[{"name":"Date","value":"Tue, 26 May 2020 08:01:15 GMT"},{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"509"},{"name":"Expires","value":"Mon, 26 Jul 1997 05:00:00 GMT"},{"name":"Last-Modified","value":"26 May 2020 08:01:15 GMT"},{"name":"Cache-Control","value":"no-store, no-cache, must-revalidate, post-check=0, pre-check=0"},{"name":"Pragma","value":"no-cache"},{"name":"X-Robots-Tag","value":"noindex, nofollow"},{"name":"x-request-id","value":"69248177903184395186109097467529134080"},{"name":"Proxy-Connection","value":"keep-alive"}]},"body":{"text":"{\"resultCode\":1,\"userId\":\"4b571740-c93d-11e9-b06d-0a0d602cefa2\",\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGI1NzE3NDAtYzkzZC0xMWU5LWIwNmQtMGEwZDYwMmNlZmEyIiwiZXhwIjoxNTkyODk5Mjc1LjM1MzUzMzcsImd1ZXN0IjpmYWxzZSwibm93IjoxNTkwNDgwMDc1LjM1MzUzMzcsImJpbmRfZGV2aWNlX2lkIjoiIiwidXNlcl9zYW5kYm94Ijp0cnVlLCJzY29wZXMiOlsidXNlciJdfQ.9bF9jBW-2-xbIgn4zKvHj_POxdErR0Rl4rQoGDsLLWU\",\"refresh_token\":\"cc13eb2f42070edc596df1c9982ee88f543f3d890c6cb4d4\",\"isNew\":false,\"email\":\"222@222.com\",\"isEmailVerified\":1}","charset":null}}}] -------------------------------------------------------------------------------- /content/page/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/page/.DS_Store -------------------------------------------------------------------------------- /content/page/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/05 3 | # @Author : XiaoXi 4 | # @PROJECT : api_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /content/page/api/registerFromThirdPart.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "parameter": { 4 | "deviceInfoList": [ 5 | "019F9E50-9561-4FFB-87F9-5FDD8A579B67", 6 | "3145728000", 7 | "414", 8 | "736" 9 | ], 10 | "loginOptions": "device_login", 11 | "deviceId": "bf38b545-b766-8c2a-8ec7-e80ccf22a3b4", 12 | "timeZone": "Asia/Shanghai" 13 | }, 14 | "test_name": "registerFromThirdPart" 15 | } 16 | ] -------------------------------------------------------------------------------- /content/page/api/registerFromThirdPart.yml: -------------------------------------------------------------------------------- 1 | test_case: 2 | - file: false 3 | check: 4 | check_type: json 5 | expected_code: 200 6 | expected_request: result_registerFromThirdPart.json 7 | headers: 8 | user-agent: PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0 9 | deviceid: bf38b545-b766-8c2a-8ec7-e80ccf22a3b4 10 | appversion: 3.4.0 11 | cookies: false 12 | timeout: 60 13 | parameter: registerFromThirdPart.json 14 | parameter_type: application/x-www-form-urlencoded 15 | test_name: registerFromThirdPart 16 | info: registerFromThirdPart 17 | address: /api/registerFromThirdPart 18 | request_type: POST 19 | relevance: 20 | http_type: https 21 | test_info: 22 | host: ${host}$ 23 | address: /api/registerFromThirdPart 24 | title: api 25 | id: test_registerFromThirdPart_01 26 | premise: 27 | -------------------------------------------------------------------------------- /content/page/api/result_registerFromThirdPart.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "json": { 4 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNjVjMDc4MGUtOWZlZi0xMWVhLTg2ZWYtMDI1YWUwZWUzNjYwIiwiZXhwIjo0NDkzNjA2MTE0LjE0NTI1NiwiZ3Vlc3QiOmZhbHNlLCJub3ciOjE1OTA1NjYxMTQuMTQ1MjU1NiwiYmluZF9kZXZpY2VfaWQiOiJiZjM4YjU0NS1iNzY2LThjMmEtOGVjNy1lODBjY2YyMmEzYjQiLCJ1c2VyX3NhbmRib3giOmZhbHNlLCJzY29wZXMiOlsidXNlciJdfQ.hEIw5xDCvQv5dcF_UIrJbhE6-yuYJlwx566upGoGCyE", 5 | "userId": "65c0780e-9fef-11ea-86ef-025ae0ee3660", 6 | "isNew": true, 7 | "resultCode": 1, 8 | "isEmailVerified": 1, 9 | "email": "", 10 | "refresh_token": "7224a861493a76b86700857d1ebaf998c6658f71832c321e" 11 | }, 12 | "test_name": "registerFromThirdPart" 13 | } 14 | ] -------------------------------------------------------------------------------- /content/report/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/report/.DS_Store -------------------------------------------------------------------------------- /content/report/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/05 3 | # @Author : XiaoXi 4 | # @PROJECT : api_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /content/report/html/data/attachments/304af0ca57799642.txt: -------------------------------------------------------------------------------- 1 | INFO root:apiSend.py:26 ==================================================================================================== 2 | DEBUG root:apiSend.py:30 请求头处理结果:{'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'} 3 | DEBUG root:apiSend.py:32 请求参数处理结果:{'loginOptions': 'device_login', 'timeZone': 'Asia/Shanghai', 'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4'} 4 | DEBUG root:apiSend.py:43 host处理结果: service.papaya.fm 5 | INFO root:apiSend.py:46 请求接口:registerFromThirdPart 6 | INFO root:apiSend.py:47 请求地址:https://service.papaya.fm/api/registerFromThirdPart 7 | INFO root:apiSend.py:48 请求头: {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'} 8 | INFO root:apiSend.py:49 请求参数: {'loginOptions': 'device_login', 'timeZone': 'Asia/Shanghai', 'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4'} 9 | INFO root:apiSend.py:59 请求方法: POST 10 | DEBUG urllib3.connectionpool:connectionpool.py:824 Starting new HTTPS connection (1): service.papaya.fm -------------------------------------------------------------------------------- /content/report/html/data/attachments/45bd124d04ccae4a.attach: -------------------------------------------------------------------------------- 1 | {'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'loginOptions': 'device_login', 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'timeZone': 'Asia/Shanghai'} -------------------------------------------------------------------------------- /content/report/html/data/attachments/57970c65b56bc98.attach: -------------------------------------------------------------------------------- 1 | 200 -------------------------------------------------------------------------------- /content/report/html/data/attachments/7be0ae4e1a985894.attach: -------------------------------------------------------------------------------- 1 | registerFromThirdPart -------------------------------------------------------------------------------- /content/report/html/data/attachments/8a82ba420cae5564.attach: -------------------------------------------------------------------------------- 1 | {'resultCode': 1, 'userId': '65c0780e-9fef-11ea-86ef-025ae0ee3660', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNjVjMDc4MGUtOWZlZi0xMWVhLTg2ZWYtMDI1YWUwZWUzNjYwIiwiZXhwIjo0NDk3MDU0Njc1LjA3NDI3NCwiZ3Vlc3QiOmZhbHNlLCJub3ciOjE1OTQwMTQ2NzUuMDc0Mjc0LCJiaW5kX2RldmljZV9pZCI6ImJmMzhiNTQ1LWI3NjYtOGMyYS04ZWM3LWU4MGNjZjIyYTNiNCIsInVzZXJfc2FuZGJveCI6ZmFsc2UsInNjb3BlcyI6WyJ1c2VyIl19.AhmHfguT5sQvq6VRujew-IxtHY82EivoFvtdXQcpSzU', 'refresh_token': '1050f1af51e420f7dbe18ed0ba00272ace392fb60f7fe361', 'isNew': True, 'email': '', 'isEmailVerified': 1} -------------------------------------------------------------------------------- /content/report/html/data/attachments/903757b7fff46258.attach: -------------------------------------------------------------------------------- 1 | registerFromThirdPart -------------------------------------------------------------------------------- /content/report/html/data/attachments/a42bc2ee2ac1e349.attach: -------------------------------------------------------------------------------- 1 | {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'} -------------------------------------------------------------------------------- /content/report/html/data/attachments/b575e64856904e.attach: -------------------------------------------------------------------------------- 1 | https://service.papaya.fm/api/registerFromThirdPart -------------------------------------------------------------------------------- /content/report/html/data/attachments/ba67fab1f091dd23.attach: -------------------------------------------------------------------------------- 1 | {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'} -------------------------------------------------------------------------------- /content/report/html/data/attachments/c65d9577bc4b56af.attach: -------------------------------------------------------------------------------- 1 | 200 -------------------------------------------------------------------------------- /content/report/html/data/attachments/ce8359096e29e5b7.attach: -------------------------------------------------------------------------------- 1 | https://service.papaya.fm/api/registerFromThirdPart -------------------------------------------------------------------------------- /content/report/html/data/attachments/e02dc41aa35a35d4.txt: -------------------------------------------------------------------------------- 1 | INFO root:apiSend.py:26 ==================================================================================================== 2 | DEBUG root:apiSend.py:30 请求头处理结果:{'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'} 3 | DEBUG root:apiSend.py:32 请求参数处理结果:{'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'loginOptions': 'device_login', 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'timeZone': 'Asia/Shanghai'} 4 | DEBUG root:apiSend.py:43 host处理结果: service.papaya.fm 5 | INFO root:apiSend.py:46 请求接口:registerFromThirdPart 6 | INFO root:apiSend.py:47 请求地址:https://service.papaya.fm/api/registerFromThirdPart 7 | INFO root:apiSend.py:48 请求头: {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'} 8 | INFO root:apiSend.py:49 请求参数: {'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'loginOptions': 'device_login', 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'timeZone': 'Asia/Shanghai'} 9 | INFO root:apiSend.py:59 请求方法: POST 10 | DEBUG urllib3.connectionpool:connectionpool.py:937 Starting new HTTPS connection (1): service.papaya.fm:443 11 | DEBUG urllib3.connectionpool:connectionpool.py:433 https://service.papaya.fm:443 "POST /api/registerFromThirdPart HTTP/1.1" 200 543 12 | INFO root:apiSend.py:130 请求接口结果: 13 | (200, {'resultCode': 1, 'userId': '65c0780e-9fef-11ea-86ef-025ae0ee3660', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNjVjMDc4MGUtOWZlZi0xMWVhLTg2ZWYtMDI1YWUwZWUzNjYwIiwiZXhwIjo0NDk3MDU0Njc1LjA3NDI3NCwiZ3Vlc3QiOmZhbHNlLCJub3ciOjE1OTQwMTQ2NzUuMDc0Mjc0LCJiaW5kX2RldmljZV9pZCI6ImJmMzhiNTQ1LWI3NjYtOGMyYS04ZWM3LWU4MGNjZjIyYTNiNCIsInVzZXJfc2FuZGJveCI6ZmFsc2UsInNjb3BlcyI6WyJ1c2VyIl19.AhmHfguT5sQvq6VRujew-IxtHY82EivoFvtdXQcpSzU', 'refresh_token': '1050f1af51e420f7dbe18ed0ba00272ace392fb60f7fe361', 'isNew': True, 'email': '', 'isEmailVerified': 1}) -------------------------------------------------------------------------------- /content/report/html/data/attachments/e4d2dd8b170229b0.attach: -------------------------------------------------------------------------------- 1 | {'loginOptions': 'device_login', 'timeZone': 'Asia/Shanghai', 'deviceInfoList': ['019F9E50-9561-4FFB-87F9-5FDD8A579B67', '3145728000', '414', '736'], 'deviceId': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4'} -------------------------------------------------------------------------------- /content/report/html/data/attachments/f8c4f43090afb738.attach: -------------------------------------------------------------------------------- 1 | {'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNjVjMDc4MGUtOWZlZi0xMWVhLTg2ZWYtMDI1YWUwZWUzNjYwIiwiZXhwIjo0NDkzNjA2MTE0LjE0NTI1NiwiZ3Vlc3QiOmZhbHNlLCJub3ciOjE1OTA1NjYxMTQuMTQ1MjU1NiwiYmluZF9kZXZpY2VfaWQiOiJiZjM4YjU0NS1iNzY2LThjMmEtOGVjNy1lODBjY2YyMmEzYjQiLCJ1c2VyX3NhbmRib3giOmZhbHNlLCJzY29wZXMiOlsidXNlciJdfQ.hEIw5xDCvQv5dcF_UIrJbhE6-yuYJlwx566upGoGCyE', 'userId': '65c0780e-9fef-11ea-86ef-025ae0ee3660', 'isNew': True, 'resultCode': 1, 'isEmailVerified': 1, 'email': '', 'refresh_token': '7224a861493a76b86700857d1ebaf998c6658f71832c321e'} -------------------------------------------------------------------------------- /content/report/html/data/behaviors.csv: -------------------------------------------------------------------------------- 1 | "Epic","Feature","Story","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN" 2 | "","api","registerFromThirdPart","0","1","1","0","0" 3 | -------------------------------------------------------------------------------- /content/report/html/data/behaviors.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "b1a8273437954620fa374b796ffaacdd", 3 | "name" : "behaviors", 4 | "children" : [ { 5 | "name" : "api", 6 | "children" : [ { 7 | "name" : "registerFromThirdPart", 8 | "children" : [ { 9 | "name" : "test_registerfromthirdpart[case_data0]", 10 | "uid" : "90e76208292b8b5b", 11 | "parentUid" : "ad50a78e6c6413e54d4008907fbc4622", 12 | "status" : "broken", 13 | "time" : { 14 | "start" : 1590999247287, 15 | "stop" : 1590999248398, 16 | "duration" : 1111 17 | }, 18 | "flaky" : false, 19 | "newFailed" : false, 20 | "parameters" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 21 | }, { 22 | "name" : "test_registerfromthirdpart[case_data0]", 23 | "uid" : "7a5644b324f85d27", 24 | "parentUid" : "ad50a78e6c6413e54d4008907fbc4622", 25 | "status" : "passed", 26 | "time" : { 27 | "start" : 1594014671665, 28 | "stop" : 1594014672562, 29 | "duration" : 897 30 | }, 31 | "flaky" : false, 32 | "newFailed" : false, 33 | "parameters" : [ "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" ] 34 | } ], 35 | "uid" : "ad50a78e6c6413e54d4008907fbc4622" 36 | } ], 37 | "uid" : "8f7c46175c4d7b5ea277e197185d81f1" 38 | } ] 39 | } -------------------------------------------------------------------------------- /content/report/html/data/categories.csv: -------------------------------------------------------------------------------- 1 | "Category","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN" 2 | "Test defects","0","1","0","0","0" 3 | -------------------------------------------------------------------------------- /content/report/html/data/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "4b4757e66a1912dae1a509f688f20b0f", 3 | "name" : "categories", 4 | "children" : [ { 5 | "name" : "Test defects", 6 | "children" : [ { 7 | "name" : "requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))", 8 | "children" : [ { 9 | "name" : "test_registerfromthirdpart[case_data0]", 10 | "uid" : "90e76208292b8b5b", 11 | "parentUid" : "7a4fdc4be1ee1c2b6b8240c88a062fb1", 12 | "status" : "broken", 13 | "time" : { 14 | "start" : 1590999247287, 15 | "stop" : 1590999248398, 16 | "duration" : 1111 17 | }, 18 | "flaky" : false, 19 | "newFailed" : false, 20 | "parameters" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 21 | } ], 22 | "uid" : "7a4fdc4be1ee1c2b6b8240c88a062fb1" 23 | } ], 24 | "uid" : "bdbf199525818fae7a8651db9eafe741" 25 | } ] 26 | } -------------------------------------------------------------------------------- /content/report/html/data/packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "83edc06c07f9ae9e47eb6dd1b683e4e2", 3 | "name" : "packages", 4 | "children" : [ { 5 | "name" : "crm.testcase.api.test_registerFromThirdPart", 6 | "children" : [ { 7 | "name" : "test_registerfromthirdpart[case_data0]", 8 | "uid" : "90e76208292b8b5b", 9 | "parentUid" : "fd7025a620042ebaf62e85e060519688", 10 | "status" : "broken", 11 | "time" : { 12 | "start" : 1590999247287, 13 | "stop" : 1590999248398, 14 | "duration" : 1111 15 | }, 16 | "flaky" : false, 17 | "newFailed" : false, 18 | "parameters" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 19 | }, { 20 | "name" : "test_registerfromthirdpart[case_data0]", 21 | "uid" : "7a5644b324f85d27", 22 | "parentUid" : "fd7025a620042ebaf62e85e060519688", 23 | "status" : "passed", 24 | "time" : { 25 | "start" : 1594014671665, 26 | "stop" : 1594014672562, 27 | "duration" : 897 28 | }, 29 | "flaky" : false, 30 | "newFailed" : false, 31 | "parameters" : [ "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" ] 32 | } ], 33 | "uid" : "crm.testcase.api.test_registerFromThirdPart" 34 | } ] 35 | } -------------------------------------------------------------------------------- /content/report/html/data/suites.csv: -------------------------------------------------------------------------------- 1 | "Status","Start Time","Stop Time","Duration in ms","Parent Suite","Suite","Sub Suite","Test Class","Test Method","Name","Description" 2 | "broken","Mon Jun 01 16:14:07 CST 2020","Mon Jun 01 16:14:08 CST 2020","1111","crm.testcase.api","test_registerFromThirdPart","TestRegisterfromthirdpart","","","test_registerfromthirdpart[case_data0]"," 3 | 4 | :param case_data: 测试用例 5 | :return: 6 | " 7 | "passed","Mon Jul 06 13:51:11 CST 2020","Mon Jul 06 13:51:12 CST 2020","897","crm.testcase.api","test_registerFromThirdPart","TestRegisterfromthirdpart","","","test_registerfromthirdpart[case_data0]"," 8 | 9 | :param case_data: 测试用例 10 | :return: 11 | " 12 | -------------------------------------------------------------------------------- /content/report/html/data/suites.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "98d3104e051c652961429bf95fa0b5d6", 3 | "name" : "suites", 4 | "children" : [ { 5 | "name" : "crm.testcase.api", 6 | "children" : [ { 7 | "name" : "test_registerFromThirdPart", 8 | "children" : [ { 9 | "name" : "TestRegisterfromthirdpart", 10 | "children" : [ { 11 | "name" : "test_registerfromthirdpart[case_data0]", 12 | "uid" : "90e76208292b8b5b", 13 | "parentUid" : "d80cfbdfd028b5d8c17009f7ad02f50f", 14 | "status" : "broken", 15 | "time" : { 16 | "start" : 1590999247287, 17 | "stop" : 1590999248398, 18 | "duration" : 1111 19 | }, 20 | "flaky" : false, 21 | "newFailed" : false, 22 | "parameters" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 23 | }, { 24 | "name" : "test_registerfromthirdpart[case_data0]", 25 | "uid" : "7a5644b324f85d27", 26 | "parentUid" : "d80cfbdfd028b5d8c17009f7ad02f50f", 27 | "status" : "passed", 28 | "time" : { 29 | "start" : 1594014671665, 30 | "stop" : 1594014672562, 31 | "duration" : 897 32 | }, 33 | "flaky" : false, 34 | "newFailed" : false, 35 | "parameters" : [ "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" ] 36 | } ], 37 | "uid" : "d80cfbdfd028b5d8c17009f7ad02f50f" 38 | } ], 39 | "uid" : "4fa6648a1afaa519c8aab2191ed01ae6" 40 | } ], 41 | "uid" : "f04ab4cab6a039f79a05b60ec90c8240" 42 | } ] 43 | } -------------------------------------------------------------------------------- /content/report/html/data/test-cases/7a5644b324f85d27.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "7a5644b324f85d27", 3 | "name" : "test_registerfromthirdpart[case_data0]", 4 | "fullName" : "crm.testcase.api.test_registerFromThirdPart.TestRegisterfromthirdpart#test_registerfromthirdpart", 5 | "historyId" : "5d7c8a95eea77353216e45474fe0ca72", 6 | "time" : { 7 | "start" : 1594014671665, 8 | "stop" : 1594014672562, 9 | "duration" : 897 10 | }, 11 | "description" : "\n\n :param case_data: 测试用例\n :return:\n ", 12 | "descriptionHtml" : "
    :param case_data: 测试用例\n    :return:\n
\n", 13 | "status" : "passed", 14 | "flaky" : false, 15 | "newFailed" : false, 16 | "beforeStages" : [ ], 17 | "testStage" : { 18 | "description" : "\n\n :param case_data: 测试用例\n :return:\n ", 19 | "status" : "passed", 20 | "steps" : [ { 21 | "name" : "POST请求接口", 22 | "time" : { 23 | "start" : 1594014671668, 24 | "stop" : 1594014671669, 25 | "duration" : 1 26 | }, 27 | "status" : "passed", 28 | "steps" : [ ], 29 | "attachments" : [ { 30 | "uid" : "903757b7fff46258", 31 | "name" : "请求接口", 32 | "source" : "903757b7fff46258.attach", 33 | "type" : "text/plain", 34 | "size" : 21 35 | }, { 36 | "uid" : "b575e64856904e", 37 | "name" : "请求地址", 38 | "source" : "b575e64856904e.attach", 39 | "type" : "text/plain", 40 | "size" : 51 41 | }, { 42 | "uid" : "ba67fab1f091dd23", 43 | "name" : "请求头", 44 | "source" : "ba67fab1f091dd23.attach", 45 | "type" : "text/plain", 46 | "size" : 139 47 | }, { 48 | "uid" : "45bd124d04ccae4a", 49 | "name" : "请求参数", 50 | "source" : "45bd124d04ccae4a.attach", 51 | "type" : "text/plain", 52 | "size" : 201 53 | } ], 54 | "parameters" : [ ], 55 | "stepsCount" : 0, 56 | "attachmentsCount" : 4, 57 | "shouldDisplayMessage" : false, 58 | "hasContent" : true 59 | }, { 60 | "name" : "JSON格式校验", 61 | "time" : { 62 | "start" : 1594014672561, 63 | "stop" : 1594014672562, 64 | "duration" : 1 65 | }, 66 | "status" : "passed", 67 | "steps" : [ ], 68 | "attachments" : [ { 69 | "uid" : "57970c65b56bc98", 70 | "name" : "期望code", 71 | "source" : "57970c65b56bc98.attach", 72 | "type" : "text/plain", 73 | "size" : 3 74 | }, { 75 | "uid" : "f8c4f43090afb738", 76 | "name" : "期望data", 77 | "source" : "f8c4f43090afb738.attach", 78 | "type" : "text/plain", 79 | "size" : 558 80 | }, { 81 | "uid" : "c65d9577bc4b56af", 82 | "name" : "实际code", 83 | "source" : "c65d9577bc4b56af.attach", 84 | "type" : "text/plain", 85 | "size" : 3 86 | }, { 87 | "uid" : "8a82ba420cae5564", 88 | "name" : "实际data", 89 | "source" : "8a82ba420cae5564.attach", 90 | "type" : "text/plain", 91 | "size" : 556 92 | } ], 93 | "parameters" : [ ], 94 | "stepsCount" : 0, 95 | "attachmentsCount" : 4, 96 | "shouldDisplayMessage" : false, 97 | "hasContent" : true 98 | } ], 99 | "attachments" : [ { 100 | "uid" : "e02dc41aa35a35d4", 101 | "name" : "log", 102 | "source" : "e02dc41aa35a35d4.txt", 103 | "type" : "text/plain", 104 | "size" : 2135 105 | } ], 106 | "parameters" : [ ], 107 | "stepsCount" : 2, 108 | "attachmentsCount" : 9, 109 | "shouldDisplayMessage" : false, 110 | "hasContent" : true 111 | }, 112 | "afterStages" : [ ], 113 | "labels" : [ { 114 | "name" : "story", 115 | "value" : "registerFromThirdPart" 116 | }, { 117 | "name" : "feature", 118 | "value" : "api" 119 | }, { 120 | "name" : "parentSuite", 121 | "value" : "crm.testcase.api" 122 | }, { 123 | "name" : "suite", 124 | "value" : "test_registerFromThirdPart" 125 | }, { 126 | "name" : "subSuite", 127 | "value" : "TestRegisterfromthirdpart" 128 | }, { 129 | "name" : "host", 130 | "value" : "xiaoxideMacBook.lan" 131 | }, { 132 | "name" : "thread", 133 | "value" : "26484-MainThread" 134 | }, { 135 | "name" : "framework", 136 | "value" : "pytest" 137 | }, { 138 | "name" : "language", 139 | "value" : "cpython3" 140 | }, { 141 | "name" : "package", 142 | "value" : "crm.testcase.api.test_registerFromThirdPart" 143 | }, { 144 | "name" : "resultFormat", 145 | "value" : "allure2" 146 | } ], 147 | "parameters" : [ { 148 | "name" : "case_data", 149 | "value" : "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" 150 | } ], 151 | "links" : [ ], 152 | "hidden" : false, 153 | "retry" : false, 154 | "extra" : { 155 | "severity" : "normal", 156 | "retries" : [ ], 157 | "categories" : [ ], 158 | "tags" : [ ] 159 | }, 160 | "source" : "7a5644b324f85d27.json", 161 | "parameterValues" : [ "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" ] 162 | } -------------------------------------------------------------------------------- /content/report/html/data/test-cases/90e76208292b8b5b.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "90e76208292b8b5b", 3 | "name" : "test_registerfromthirdpart[case_data0]", 4 | "fullName" : "crm.testcase.api.test_registerFromThirdPart.TestRegisterfromthirdpart#test_registerfromthirdpart", 5 | "historyId" : "c726df5b4b0e58b446c43e76a1c2c73b", 6 | "time" : { 7 | "start" : 1590999247287, 8 | "stop" : 1590999248398, 9 | "duration" : 1111 10 | }, 11 | "description" : "\n\n :param case_data: 测试用例\n :return:\n ", 12 | "descriptionHtml" : "
    :param case_data: 测试用例\n    :return:\n
\n", 13 | "status" : "broken", 14 | "statusMessage" : "requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))", 15 | "statusTrace" : "self = \nsock = \nserver_side = False, do_handshake_on_connect = True, suppress_ragged_eofs = True\nserver_hostname = b'service.papaya.fm'\n\n def wrap_socket(self, sock, server_side=False,\n do_handshake_on_connect=True, suppress_ragged_eofs=True,\n server_hostname=None):\n cnx = OpenSSL.SSL.Connection(self._ctx, sock)\n \n if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3\n server_hostname = server_hostname.encode('utf-8')\n \n if server_hostname is not None:\n cnx.set_tlsext_host_name(server_hostname)\n \n cnx.set_connect_state()\n \n while True:\n try:\n> cnx.do_handshake()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/contrib/pyopenssl.py:441: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \n\n def do_handshake(self):\n \"\"\"\n Perform an SSL handshake (usually called after renegotiate() or one of\n set_*_state()). This can raise the same exceptions as send and recv.\n \n :return: None.\n \"\"\"\n result = _lib.SSL_do_handshake(self._ssl)\n> self._raise_ssl_error(self._ssl, result)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/SSL.py:1806: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nssl = , result = -1\n\n def _raise_ssl_error(self, ssl, result):\n if self._context._verify_helper is not None:\n self._context._verify_helper.raise_if_problem()\n if self._context._npn_advertise_helper is not None:\n self._context._npn_advertise_helper.raise_if_problem()\n if self._context._npn_select_helper is not None:\n self._context._npn_select_helper.raise_if_problem()\n if self._context._alpn_select_helper is not None:\n self._context._alpn_select_helper.raise_if_problem()\n if self._context._ocsp_helper is not None:\n self._context._ocsp_helper.raise_if_problem()\n \n error = _lib.SSL_get_error(ssl, result)\n if error == _lib.SSL_ERROR_WANT_READ:\n raise WantReadError()\n elif error == _lib.SSL_ERROR_WANT_WRITE:\n raise WantWriteError()\n elif error == _lib.SSL_ERROR_ZERO_RETURN:\n raise ZeroReturnError()\n elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP:\n # TODO: This is untested.\n raise WantX509LookupError()\n elif error == _lib.SSL_ERROR_SYSCALL:\n if _lib.ERR_peek_error() == 0:\n if result < 0:\n if platform == \"win32\":\n errno = _ffi.getwinerror()[0]\n else:\n errno = _ffi.errno\n \n if errno != 0:\n raise SysCallError(errno, errorcode.get(errno))\n raise SysCallError(-1, \"Unexpected EOF\")\n else:\n # TODO: This is untested.\n _raise_current_error()\n elif error == _lib.SSL_ERROR_NONE:\n pass\n else:\n> _raise_current_error()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/SSL.py:1546: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nexception_type = \n\n def exception_from_error_queue(exception_type):\n \"\"\"\n Convert an OpenSSL library failure into a Python exception.\n \n When a call to the native OpenSSL library fails, this is usually signalled\n by the return value, and an error code is stored in an error queue\n associated with the current thread. The err library provides functions to\n obtain these error codes and textual error messages.\n \"\"\"\n errors = []\n \n while True:\n error = lib.ERR_get_error()\n if error == 0:\n break\n errors.append((\n text(lib.ERR_lib_error_string(error)),\n text(lib.ERR_func_error_string(error)),\n text(lib.ERR_reason_error_string(error))))\n \n> raise exception_type(errors)\nE OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/_util.py:54: Error\n\nDuring handling of the above exception, another exception occurred:\n\nself = \nmethod = 'POST', url = '/api/registerFromThirdPart'\nbody = '{\"loginOptions\": \"device_login\", \"timeZone\": \"Asia/Shanghai\", \"deviceInfoList\": [\"019F9E50-9561-4FFB-87F9-5FDD8A579B67\", \"3145728000\", \"414\", \"736\"], \"deviceId\": \"bf38b545-b766-8c2a-8ec7-e80ccf22a3b4\"}'\nheaders = {'Connection': 'keep-alive', 'Accept': '*/*', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'Accept-...': 'gzip, deflate', 'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'Content-Length': '201'}\nretries = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nredirect = False, assert_same_host = False\ntimeout = \npool_timeout = None, release_conn = False, chunked = False, body_pos = None\nresponse_kw = {'decode_content': False, 'preload_content': False}, conn = None\nrelease_this_conn = True, err = None, clean_exit = False\ntimeout_obj = \nis_new_proxy_conn = True\n\n def urlopen(self, method, url, body=None, headers=None, retries=None,\n redirect=True, assert_same_host=True, timeout=_Default,\n pool_timeout=None, release_conn=None, chunked=False,\n body_pos=None, **response_kw):\n \"\"\"\n Get a connection from the pool and perform an HTTP request. This is the\n lowest level call for making a request, so you'll need to specify all\n the raw details.\n \n .. note::\n \n More commonly, it's appropriate to use a convenience method provided\n by :class:`.RequestMethods`, such as :meth:`request`.\n \n .. note::\n \n `release_conn` will only behave as expected if\n `preload_content=False` because we want to make\n `preload_content=False` the default behaviour someday soon without\n breaking backwards compatibility.\n \n :param method:\n HTTP request method (such as GET, POST, PUT, etc.)\n \n :param body:\n Data to send in the request body (useful for creating\n POST requests, see HTTPConnectionPool.post_url for\n more convenience).\n \n :param headers:\n Dictionary of custom headers to send, such as User-Agent,\n If-None-Match, etc. If None, pool headers are used. If provided,\n these headers completely replace any pool-specific headers.\n \n :param retries:\n Configure the number of retries to allow before raising a\n :class:`~urllib3.exceptions.MaxRetryError` exception.\n \n Pass ``None`` to retry until you receive a response. Pass a\n :class:`~urllib3.util.retry.Retry` object for fine-grained control\n over different types of retries.\n Pass an integer number to retry connection errors that many times,\n but no other types of errors. Pass zero to never retry.\n \n If ``False``, then retries are disabled and any exception is raised\n immediately. Also, instead of raising a MaxRetryError on redirects,\n the redirect response will be returned.\n \n :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.\n \n :param redirect:\n If True, automatically handle redirects (status codes 301, 302,\n 303, 307, 308). Each redirect counts as a retry. Disabling retries\n will disable redirect, too.\n \n :param assert_same_host:\n If ``True``, will make sure that the host of the pool requests is\n consistent else will raise HostChangedError. When False, you can\n use the pool on an HTTP proxy and request foreign hosts.\n \n :param timeout:\n If specified, overrides the default timeout for this one\n request. It may be a float (in seconds) or an instance of\n :class:`urllib3.util.Timeout`.\n \n :param pool_timeout:\n If set and the pool is set to block=True, then this method will\n block for ``pool_timeout`` seconds and raise EmptyPoolError if no\n connection is available within the time period.\n \n :param release_conn:\n If False, then the urlopen call will not release the connection\n back into the pool once a response is received (but will release if\n you read the entire contents of the response such as when\n `preload_content=True`). This is useful if you're not preloading\n the response's content immediately. You will need to call\n ``r.release_conn()`` on the response ``r`` to return the connection\n back into the pool. If None, it takes the value of\n ``response_kw.get('preload_content', True)``.\n \n :param chunked:\n If True, urllib3 will send the body using chunked transfer\n encoding. Otherwise, urllib3 will send the body using the standard\n content-length form. Defaults to False.\n \n :param int body_pos:\n Position to seek to in file-like body in the event of a retry or\n redirect. Typically this won't need to be set because urllib3 will\n auto-populate the value when needed.\n \n :param \\\\**response_kw:\n Additional parameters are passed to\n :meth:`urllib3.response.HTTPResponse.from_httplib`\n \"\"\"\n if headers is None:\n headers = self.headers\n \n if not isinstance(retries, Retry):\n retries = Retry.from_int(retries, redirect=redirect, default=self.retries)\n \n if release_conn is None:\n release_conn = response_kw.get('preload_content', True)\n \n # Check host\n if assert_same_host and not self.is_same_host(url):\n raise HostChangedError(self, url, retries)\n \n conn = None\n \n # Track whether `conn` needs to be released before\n # returning/raising/recursing. Update this variable if necessary, and\n # leave `release_conn` constant throughout the function. That way, if\n # the function recurses, the original value of `release_conn` will be\n # passed down into the recursive call, and its value will be respected.\n #\n # See issue #651 [1] for details.\n #\n # [1] \n release_this_conn = release_conn\n \n # Merge the proxy headers. Only do this in HTTP. We have to copy the\n # headers dict so we can safely change it without those changes being\n # reflected in anyone else's copy.\n if self.scheme == 'http':\n headers = headers.copy()\n headers.update(self.proxy_headers)\n \n # Must keep the exception bound to a separate variable or else Python 3\n # complains about UnboundLocalError.\n err = None\n \n # Keep track of whether we cleanly exited the except block. This\n # ensures we do proper cleanup in finally.\n clean_exit = False\n \n # Rewind body position, if needed. Record current position\n # for future rewinds in the event of a redirect/retry.\n body_pos = set_file_position(body, body_pos)\n \n try:\n # Request a connection from the queue.\n timeout_obj = self._get_timeout(timeout)\n conn = self._get_conn(timeout=pool_timeout)\n \n conn.timeout = timeout_obj.connect_timeout\n \n is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)\n if is_new_proxy_conn:\n> self._prepare_proxy(conn)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:595: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nconn = \n\n def _prepare_proxy(self, conn):\n \"\"\"\n Establish tunnel connection early, because otherwise httplib\n would improperly set Host: header to proxy's IP:port.\n \"\"\"\n # Python 2.7+\n try:\n set_tunnel = conn.set_tunnel\n except AttributeError: # Platform-specific: Python 2.6\n set_tunnel = conn._set_tunnel\n \n if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older\n set_tunnel(self._proxy_host, self.port)\n else:\n set_tunnel(self._proxy_host, self.port, self.proxy_headers)\n \n> conn.connect()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:816: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \n\n def connect(self):\n # Add certificate verification\n conn = self._new_conn()\n \n hostname = self.host\n if getattr(self, '_tunnel_host', None):\n # _tunnel_host was added in Python 2.6.3\n # (See: http://hg.python.org/cpython/rev/0f57b30a152f)\n \n self.sock = conn\n # Calls self._set_hostport(), so self.host is\n # self._tunnel_host below.\n self._tunnel()\n # Mark this connection as not reusable\n self.auto_open = 0\n \n # Override the host with the one we're requesting data from.\n hostname = self._tunnel_host\n \n is_time_off = datetime.date.today() < RECENT_DATE\n if is_time_off:\n warnings.warn((\n 'System time is way off (before {0}). This will probably '\n 'lead to SSL verification errors').format(RECENT_DATE),\n SystemTimeWarning\n )\n \n # Wrap socket using verification with the root certs in\n # trusted_root_certs\n if self.ssl_context is None:\n self.ssl_context = create_urllib3_context(\n ssl_version=resolve_ssl_version(self.ssl_version),\n cert_reqs=resolve_cert_reqs(self.cert_reqs),\n )\n \n context = self.ssl_context\n context.verify_mode = resolve_cert_reqs(self.cert_reqs)\n self.sock = ssl_wrap_socket(\n sock=conn,\n keyfile=self.key_file,\n certfile=self.cert_file,\n ca_certs=self.ca_certs,\n ca_cert_dir=self.ca_cert_dir,\n server_hostname=hostname,\n> ssl_context=context)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connection.py:326: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nsock = \nkeyfile = None, certfile = None, cert_reqs = None\nca_certs = '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/certifi/cacert.pem'\nserver_hostname = 'service.papaya.fm', ssl_version = None, ciphers = None\nssl_context = \nca_cert_dir = None\n\n def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,\n ca_certs=None, server_hostname=None,\n ssl_version=None, ciphers=None, ssl_context=None,\n ca_cert_dir=None):\n \"\"\"\n All arguments except for server_hostname, ssl_context, and ca_cert_dir have\n the same meaning as they do when using :func:`ssl.wrap_socket`.\n \n :param server_hostname:\n When SNI is supported, the expected hostname of the certificate\n :param ssl_context:\n A pre-made :class:`SSLContext` object. If none is provided, one will\n be created using :func:`create_urllib3_context`.\n :param ciphers:\n A string of ciphers we wish the client to support. This is not\n supported on Python 2.6 as the ssl module does not support it.\n :param ca_cert_dir:\n A directory containing CA certificates in multiple separate files, as\n supported by OpenSSL's -CApath flag or the capath argument to\n SSLContext.load_verify_locations().\n \"\"\"\n context = ssl_context\n if context is None:\n # Note: This branch of code and all the variables in it are no longer\n # used by urllib3 itself. We should consider deprecating and removing\n # this code.\n context = create_urllib3_context(ssl_version, cert_reqs,\n ciphers=ciphers)\n \n if ca_certs or ca_cert_dir:\n try:\n context.load_verify_locations(ca_certs, ca_cert_dir)\n except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2\n raise SSLError(e)\n # Py33 raises FileNotFoundError which subclasses OSError\n # These are not equivalent unless we check the errno attribute\n except OSError as e: # Platform-specific: Python 3.3 and beyond\n if e.errno == errno.ENOENT:\n raise SSLError(e)\n raise\n elif getattr(context, 'load_default_certs', None) is not None:\n # try to load OS default certs; works well on Windows (require Python3.4+)\n context.load_default_certs()\n \n if certfile:\n context.load_cert_chain(certfile, keyfile)\n if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI\n> return context.wrap_socket(sock, server_hostname=server_hostname)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/ssl_.py:329: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nsock = \nserver_side = False, do_handshake_on_connect = True, suppress_ragged_eofs = True\nserver_hostname = b'service.papaya.fm'\n\n def wrap_socket(self, sock, server_side=False,\n do_handshake_on_connect=True, suppress_ragged_eofs=True,\n server_hostname=None):\n cnx = OpenSSL.SSL.Connection(self._ctx, sock)\n \n if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3\n server_hostname = server_hostname.encode('utf-8')\n \n if server_hostname is not None:\n cnx.set_tlsext_host_name(server_hostname)\n \n cnx.set_connect_state()\n \n while True:\n try:\n cnx.do_handshake()\n except OpenSSL.SSL.WantReadError:\n rd = util.wait_for_read(sock, sock.gettimeout())\n if not rd:\n raise timeout('select timed out')\n continue\n except OpenSSL.SSL.Error as e:\n> raise ssl.SSLError('bad handshake: %r' % e)\nE ssl.SSLError: (\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/contrib/pyopenssl.py:448: SSLError\n\nDuring handling of the above exception, another exception occurred:\n\nself = \nrequest = , stream = False\ntimeout = , verify = True\ncert = None\nproxies = OrderedDict([('http', 'http://127.0.0.1:8888'), ('https', 'http://127.0.0.1:8888')])\n\n def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):\n \"\"\"Sends PreparedRequest object. Returns Response object.\n \n :param request: The :class:`PreparedRequest ` being sent.\n :param stream: (optional) Whether to stream the request content.\n :param timeout: (optional) How long to wait for the server to send\n data before giving up, as a float, or a :ref:`(connect timeout,\n read timeout) ` tuple.\n :type timeout: float or tuple or urllib3 Timeout object\n :param verify: (optional) Either a boolean, in which case it controls whether\n we verify the server's TLS certificate, or a string, in which case it\n must be a path to a CA bundle to use\n :param cert: (optional) Any user-provided SSL certificate to be trusted.\n :param proxies: (optional) The proxies dictionary to apply to the request.\n :rtype: requests.Response\n \"\"\"\n \n conn = self.get_connection(request.url, proxies)\n \n self.cert_verify(conn, request.url, verify, cert)\n url = self.request_url(request, proxies)\n self.add_headers(request)\n \n chunked = not (request.body is None or 'Content-Length' in request.headers)\n \n if isinstance(timeout, tuple):\n try:\n connect, read = timeout\n timeout = TimeoutSauce(connect=connect, read=read)\n except ValueError as e:\n # this may raise a string formatting error.\n err = (\"Invalid timeout {0}. Pass a (connect, read) \"\n \"timeout tuple, or a single float to set \"\n \"both timeouts to the same value\".format(timeout))\n raise ValueError(err)\n elif isinstance(timeout, TimeoutSauce):\n pass\n else:\n timeout = TimeoutSauce(connect=timeout, read=timeout)\n \n try:\n if not chunked:\n resp = conn.urlopen(\n method=request.method,\n url=url,\n body=request.body,\n headers=request.headers,\n redirect=False,\n assert_same_host=False,\n preload_content=False,\n decode_content=False,\n retries=self.max_retries,\n> timeout=timeout\n )\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py:440: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nmethod = 'POST', url = '/api/registerFromThirdPart'\nbody = '{\"loginOptions\": \"device_login\", \"timeZone\": \"Asia/Shanghai\", \"deviceInfoList\": [\"019F9E50-9561-4FFB-87F9-5FDD8A579B67\", \"3145728000\", \"414\", \"736\"], \"deviceId\": \"bf38b545-b766-8c2a-8ec7-e80ccf22a3b4\"}'\nheaders = {'Connection': 'keep-alive', 'Accept': '*/*', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'Accept-...': 'gzip, deflate', 'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'Content-Length': '201'}\nretries = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nredirect = False, assert_same_host = False\ntimeout = \npool_timeout = None, release_conn = False, chunked = False, body_pos = None\nresponse_kw = {'decode_content': False, 'preload_content': False}, conn = None\nrelease_this_conn = True, err = None, clean_exit = False\ntimeout_obj = \nis_new_proxy_conn = True\n\n def urlopen(self, method, url, body=None, headers=None, retries=None,\n redirect=True, assert_same_host=True, timeout=_Default,\n pool_timeout=None, release_conn=None, chunked=False,\n body_pos=None, **response_kw):\n \"\"\"\n Get a connection from the pool and perform an HTTP request. This is the\n lowest level call for making a request, so you'll need to specify all\n the raw details.\n \n .. note::\n \n More commonly, it's appropriate to use a convenience method provided\n by :class:`.RequestMethods`, such as :meth:`request`.\n \n .. note::\n \n `release_conn` will only behave as expected if\n `preload_content=False` because we want to make\n `preload_content=False` the default behaviour someday soon without\n breaking backwards compatibility.\n \n :param method:\n HTTP request method (such as GET, POST, PUT, etc.)\n \n :param body:\n Data to send in the request body (useful for creating\n POST requests, see HTTPConnectionPool.post_url for\n more convenience).\n \n :param headers:\n Dictionary of custom headers to send, such as User-Agent,\n If-None-Match, etc. If None, pool headers are used. If provided,\n these headers completely replace any pool-specific headers.\n \n :param retries:\n Configure the number of retries to allow before raising a\n :class:`~urllib3.exceptions.MaxRetryError` exception.\n \n Pass ``None`` to retry until you receive a response. Pass a\n :class:`~urllib3.util.retry.Retry` object for fine-grained control\n over different types of retries.\n Pass an integer number to retry connection errors that many times,\n but no other types of errors. Pass zero to never retry.\n \n If ``False``, then retries are disabled and any exception is raised\n immediately. Also, instead of raising a MaxRetryError on redirects,\n the redirect response will be returned.\n \n :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.\n \n :param redirect:\n If True, automatically handle redirects (status codes 301, 302,\n 303, 307, 308). Each redirect counts as a retry. Disabling retries\n will disable redirect, too.\n \n :param assert_same_host:\n If ``True``, will make sure that the host of the pool requests is\n consistent else will raise HostChangedError. When False, you can\n use the pool on an HTTP proxy and request foreign hosts.\n \n :param timeout:\n If specified, overrides the default timeout for this one\n request. It may be a float (in seconds) or an instance of\n :class:`urllib3.util.Timeout`.\n \n :param pool_timeout:\n If set and the pool is set to block=True, then this method will\n block for ``pool_timeout`` seconds and raise EmptyPoolError if no\n connection is available within the time period.\n \n :param release_conn:\n If False, then the urlopen call will not release the connection\n back into the pool once a response is received (but will release if\n you read the entire contents of the response such as when\n `preload_content=True`). This is useful if you're not preloading\n the response's content immediately. You will need to call\n ``r.release_conn()`` on the response ``r`` to return the connection\n back into the pool. If None, it takes the value of\n ``response_kw.get('preload_content', True)``.\n \n :param chunked:\n If True, urllib3 will send the body using chunked transfer\n encoding. Otherwise, urllib3 will send the body using the standard\n content-length form. Defaults to False.\n \n :param int body_pos:\n Position to seek to in file-like body in the event of a retry or\n redirect. Typically this won't need to be set because urllib3 will\n auto-populate the value when needed.\n \n :param \\\\**response_kw:\n Additional parameters are passed to\n :meth:`urllib3.response.HTTPResponse.from_httplib`\n \"\"\"\n if headers is None:\n headers = self.headers\n \n if not isinstance(retries, Retry):\n retries = Retry.from_int(retries, redirect=redirect, default=self.retries)\n \n if release_conn is None:\n release_conn = response_kw.get('preload_content', True)\n \n # Check host\n if assert_same_host and not self.is_same_host(url):\n raise HostChangedError(self, url, retries)\n \n conn = None\n \n # Track whether `conn` needs to be released before\n # returning/raising/recursing. Update this variable if necessary, and\n # leave `release_conn` constant throughout the function. That way, if\n # the function recurses, the original value of `release_conn` will be\n # passed down into the recursive call, and its value will be respected.\n #\n # See issue #651 [1] for details.\n #\n # [1] \n release_this_conn = release_conn\n \n # Merge the proxy headers. Only do this in HTTP. We have to copy the\n # headers dict so we can safely change it without those changes being\n # reflected in anyone else's copy.\n if self.scheme == 'http':\n headers = headers.copy()\n headers.update(self.proxy_headers)\n \n # Must keep the exception bound to a separate variable or else Python 3\n # complains about UnboundLocalError.\n err = None\n \n # Keep track of whether we cleanly exited the except block. This\n # ensures we do proper cleanup in finally.\n clean_exit = False\n \n # Rewind body position, if needed. Record current position\n # for future rewinds in the event of a redirect/retry.\n body_pos = set_file_position(body, body_pos)\n \n try:\n # Request a connection from the queue.\n timeout_obj = self._get_timeout(timeout)\n conn = self._get_conn(timeout=pool_timeout)\n \n conn.timeout = timeout_obj.connect_timeout\n \n is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)\n if is_new_proxy_conn:\n self._prepare_proxy(conn)\n \n # Make the request on the httplib connection object.\n httplib_response = self._make_request(conn, method, url,\n timeout=timeout_obj,\n body=body, headers=headers,\n chunked=chunked)\n \n # If we're going to release the connection in ``finally:``, then\n # the response doesn't need to know about the connection. Otherwise\n # it will also try to release it and we'll have a double-release\n # mess.\n response_conn = conn if not release_conn else None\n \n # Pass method to Response for length checking\n response_kw['request_method'] = method\n \n # Import httplib's response into our own wrapper object\n response = self.ResponseCls.from_httplib(httplib_response,\n pool=self,\n connection=response_conn,\n retries=retries,\n **response_kw)\n \n # Everything went great!\n clean_exit = True\n \n except queue.Empty:\n # Timed out by queue.\n raise EmptyPoolError(self, \"No pool connections are available.\")\n \n except (TimeoutError, HTTPException, SocketError, ProtocolError,\n BaseSSLError, SSLError, CertificateError) as e:\n # Discard the connection for these exceptions. It will be\n # replaced during the next _get_conn() call.\n clean_exit = False\n if isinstance(e, (BaseSSLError, CertificateError)):\n e = SSLError(e)\n elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy:\n e = ProxyError('Cannot connect to proxy.', e)\n elif isinstance(e, (SocketError, HTTPException)):\n e = ProtocolError('Connection aborted.', e)\n \n retries = retries.increment(method, url, error=e, _pool=self,\n> _stacktrace=sys.exc_info()[2])\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:639: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nmethod = 'POST', url = '/api/registerFromThirdPart', response = None\nerror = SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),)\n_pool = \n_stacktrace = \n\n def increment(self, method=None, url=None, response=None, error=None,\n _pool=None, _stacktrace=None):\n \"\"\" Return a new Retry object with incremented retry counters.\n \n :param response: A response object, or None, if the server did not\n return a response.\n :type response: :class:`~urllib3.response.HTTPResponse`\n :param Exception error: An error encountered during the request, or\n None if the response was received successfully.\n \n :return: A new ``Retry`` object.\n \"\"\"\n if self.total is False and error:\n # Disabled, indicate to re-raise the error.\n raise six.reraise(type(error), error, _stacktrace)\n \n total = self.total\n if total is not None:\n total -= 1\n \n connect = self.connect\n read = self.read\n redirect = self.redirect\n status_count = self.status\n cause = 'unknown'\n status = None\n redirect_location = None\n \n if error and self._is_connection_error(error):\n # Connect retry?\n if connect is False:\n raise six.reraise(type(error), error, _stacktrace)\n elif connect is not None:\n connect -= 1\n \n elif error and self._is_read_error(error):\n # Read retry?\n if read is False or not self._is_method_retryable(method):\n raise six.reraise(type(error), error, _stacktrace)\n elif read is not None:\n read -= 1\n \n elif response and response.get_redirect_location():\n # Redirect retry?\n if redirect is not None:\n redirect -= 1\n cause = 'too many redirects'\n redirect_location = response.get_redirect_location()\n status = response.status\n \n else:\n # Incrementing because of a server error like a 500 in\n # status_forcelist and a the given method is in the whitelist\n cause = ResponseError.GENERIC_ERROR\n if response and response.status:\n if status_count is not None:\n status_count -= 1\n cause = ResponseError.SPECIFIC_ERROR.format(\n status_code=response.status)\n status = response.status\n \n history = self.history + (RequestHistory(method, url, error, status, redirect_location),)\n \n new_retry = self.new(\n total=total,\n connect=connect, read=read, redirect=redirect, status=status_count,\n history=history)\n \n if new_retry.is_exhausted():\n> raise MaxRetryError(_pool, url, error or ResponseError(cause))\nE urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/retry.py:388: MaxRetryError\n\nDuring handling of the above exception, another exception occurred:\n\nself = \ncase_data = {'address': '/api/registerFromThirdPart', 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'cookies': False, 'file': False, ...}\n\n @pytest.mark.parametrize(\"case_data\", case_dict[\"test_case\"], ids=[])\n @allure.story(\"registerFromThirdPart\")\n # @pytest.mark.flaky(reruns=3, reruns_delay=3)\n def test_registerfromthirdpart(self, case_data):\n \"\"\"\n \n :param case_data: 测试用例\n :return:\n \"\"\"\n self.init_relevance = ini_request(case_dict, PATH)\n # 发送测试请求\n> api_send_check(case_data, case_dict, self.init_relevance, PATH)\n\ncrm/testcase/api/test_registerFromThirdPart.py:33: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nbin/unit/apiSendCheck.py:23: in api_send_check\n project_dict[\"test_info\"].get(\"address\"), _path, relevance)\nbin/unit/apiSend.py:81: in send_request\n timeout=data[\"timeout\"])\nbin/unit/apiMethod.py:49: in post\n response = requests.post(url=address, data=data, headers=header, timeout=timeout)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/api.py:112: in post\n return request('post', url, data=data, json=json, **kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/api.py:58: in request\n return session.request(method=method, url=url, **kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py:508: in request\n resp = self.send(prep, **send_kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py:618: in send\n r = adapter.send(request, **kwargs)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nrequest = , stream = False\ntimeout = , verify = True\ncert = None\nproxies = OrderedDict([('http', 'http://127.0.0.1:8888'), ('https', 'http://127.0.0.1:8888')])\n\n def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):\n \"\"\"Sends PreparedRequest object. Returns Response object.\n \n :param request: The :class:`PreparedRequest ` being sent.\n :param stream: (optional) Whether to stream the request content.\n :param timeout: (optional) How long to wait for the server to send\n data before giving up, as a float, or a :ref:`(connect timeout,\n read timeout) ` tuple.\n :type timeout: float or tuple or urllib3 Timeout object\n :param verify: (optional) Either a boolean, in which case it controls whether\n we verify the server's TLS certificate, or a string, in which case it\n must be a path to a CA bundle to use\n :param cert: (optional) Any user-provided SSL certificate to be trusted.\n :param proxies: (optional) The proxies dictionary to apply to the request.\n :rtype: requests.Response\n \"\"\"\n \n conn = self.get_connection(request.url, proxies)\n \n self.cert_verify(conn, request.url, verify, cert)\n url = self.request_url(request, proxies)\n self.add_headers(request)\n \n chunked = not (request.body is None or 'Content-Length' in request.headers)\n \n if isinstance(timeout, tuple):\n try:\n connect, read = timeout\n timeout = TimeoutSauce(connect=connect, read=read)\n except ValueError as e:\n # this may raise a string formatting error.\n err = (\"Invalid timeout {0}. Pass a (connect, read) \"\n \"timeout tuple, or a single float to set \"\n \"both timeouts to the same value\".format(timeout))\n raise ValueError(err)\n elif isinstance(timeout, TimeoutSauce):\n pass\n else:\n timeout = TimeoutSauce(connect=timeout, read=timeout)\n \n try:\n if not chunked:\n resp = conn.urlopen(\n method=request.method,\n url=url,\n body=request.body,\n headers=request.headers,\n redirect=False,\n assert_same_host=False,\n preload_content=False,\n decode_content=False,\n retries=self.max_retries,\n timeout=timeout\n )\n \n # Send the request.\n else:\n if hasattr(conn, 'proxy_pool'):\n conn = conn.proxy_pool\n \n low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)\n \n try:\n low_conn.putrequest(request.method,\n url,\n skip_accept_encoding=True)\n \n for header, value in request.headers.items():\n low_conn.putheader(header, value)\n \n low_conn.endheaders()\n \n for i in request.body:\n low_conn.send(hex(len(i))[2:].encode('utf-8'))\n low_conn.send(b'\\r\\n')\n low_conn.send(i)\n low_conn.send(b'\\r\\n')\n low_conn.send(b'0\\r\\n\\r\\n')\n \n # Receive the response from the server\n try:\n # For Python 2.7+ versions, use buffering of HTTP\n # responses\n r = low_conn.getresponse(buffering=True)\n except TypeError:\n # For compatibility with Python 2.6 versions and back\n r = low_conn.getresponse()\n \n resp = HTTPResponse.from_httplib(\n r,\n pool=conn,\n connection=low_conn,\n preload_content=False,\n decode_content=False\n )\n except:\n # If we hit any problems here, clean up the connection.\n # Then, reraise so that we can handle the actual exception.\n low_conn.close()\n raise\n \n except (ProtocolError, socket.error) as err:\n raise ConnectionError(err, request=request)\n \n except MaxRetryError as e:\n if isinstance(e.reason, ConnectTimeoutError):\n # TODO: Remove this in 3.0.0: see #2811\n if not isinstance(e.reason, NewConnectionError):\n raise ConnectTimeout(e, request=request)\n \n if isinstance(e.reason, ResponseError):\n raise RetryError(e, request=request)\n \n if isinstance(e.reason, _ProxyError):\n raise ProxyError(e, request=request)\n \n if isinstance(e.reason, _SSLError):\n # This branch is for urllib3 v1.22 and later.\n> raise SSLError(e, request=request)\nE requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py:506: SSLError", 16 | "flaky" : false, 17 | "newFailed" : false, 18 | "beforeStages" : [ ], 19 | "testStage" : { 20 | "description" : "\n\n :param case_data: 测试用例\n :return:\n ", 21 | "status" : "broken", 22 | "statusMessage" : "requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))", 23 | "statusTrace" : "self = \nsock = \nserver_side = False, do_handshake_on_connect = True, suppress_ragged_eofs = True\nserver_hostname = b'service.papaya.fm'\n\n def wrap_socket(self, sock, server_side=False,\n do_handshake_on_connect=True, suppress_ragged_eofs=True,\n server_hostname=None):\n cnx = OpenSSL.SSL.Connection(self._ctx, sock)\n \n if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3\n server_hostname = server_hostname.encode('utf-8')\n \n if server_hostname is not None:\n cnx.set_tlsext_host_name(server_hostname)\n \n cnx.set_connect_state()\n \n while True:\n try:\n> cnx.do_handshake()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/contrib/pyopenssl.py:441: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \n\n def do_handshake(self):\n \"\"\"\n Perform an SSL handshake (usually called after renegotiate() or one of\n set_*_state()). This can raise the same exceptions as send and recv.\n \n :return: None.\n \"\"\"\n result = _lib.SSL_do_handshake(self._ssl)\n> self._raise_ssl_error(self._ssl, result)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/SSL.py:1806: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nssl = , result = -1\n\n def _raise_ssl_error(self, ssl, result):\n if self._context._verify_helper is not None:\n self._context._verify_helper.raise_if_problem()\n if self._context._npn_advertise_helper is not None:\n self._context._npn_advertise_helper.raise_if_problem()\n if self._context._npn_select_helper is not None:\n self._context._npn_select_helper.raise_if_problem()\n if self._context._alpn_select_helper is not None:\n self._context._alpn_select_helper.raise_if_problem()\n if self._context._ocsp_helper is not None:\n self._context._ocsp_helper.raise_if_problem()\n \n error = _lib.SSL_get_error(ssl, result)\n if error == _lib.SSL_ERROR_WANT_READ:\n raise WantReadError()\n elif error == _lib.SSL_ERROR_WANT_WRITE:\n raise WantWriteError()\n elif error == _lib.SSL_ERROR_ZERO_RETURN:\n raise ZeroReturnError()\n elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP:\n # TODO: This is untested.\n raise WantX509LookupError()\n elif error == _lib.SSL_ERROR_SYSCALL:\n if _lib.ERR_peek_error() == 0:\n if result < 0:\n if platform == \"win32\":\n errno = _ffi.getwinerror()[0]\n else:\n errno = _ffi.errno\n \n if errno != 0:\n raise SysCallError(errno, errorcode.get(errno))\n raise SysCallError(-1, \"Unexpected EOF\")\n else:\n # TODO: This is untested.\n _raise_current_error()\n elif error == _lib.SSL_ERROR_NONE:\n pass\n else:\n> _raise_current_error()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/SSL.py:1546: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nexception_type = \n\n def exception_from_error_queue(exception_type):\n \"\"\"\n Convert an OpenSSL library failure into a Python exception.\n \n When a call to the native OpenSSL library fails, this is usually signalled\n by the return value, and an error code is stored in an error queue\n associated with the current thread. The err library provides functions to\n obtain these error codes and textual error messages.\n \"\"\"\n errors = []\n \n while True:\n error = lib.ERR_get_error()\n if error == 0:\n break\n errors.append((\n text(lib.ERR_lib_error_string(error)),\n text(lib.ERR_func_error_string(error)),\n text(lib.ERR_reason_error_string(error))))\n \n> raise exception_type(errors)\nE OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/OpenSSL/_util.py:54: Error\n\nDuring handling of the above exception, another exception occurred:\n\nself = \nmethod = 'POST', url = '/api/registerFromThirdPart'\nbody = '{\"loginOptions\": \"device_login\", \"timeZone\": \"Asia/Shanghai\", \"deviceInfoList\": [\"019F9E50-9561-4FFB-87F9-5FDD8A579B67\", \"3145728000\", \"414\", \"736\"], \"deviceId\": \"bf38b545-b766-8c2a-8ec7-e80ccf22a3b4\"}'\nheaders = {'Connection': 'keep-alive', 'Accept': '*/*', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'Accept-...': 'gzip, deflate', 'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'Content-Length': '201'}\nretries = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nredirect = False, assert_same_host = False\ntimeout = \npool_timeout = None, release_conn = False, chunked = False, body_pos = None\nresponse_kw = {'decode_content': False, 'preload_content': False}, conn = None\nrelease_this_conn = True, err = None, clean_exit = False\ntimeout_obj = \nis_new_proxy_conn = True\n\n def urlopen(self, method, url, body=None, headers=None, retries=None,\n redirect=True, assert_same_host=True, timeout=_Default,\n pool_timeout=None, release_conn=None, chunked=False,\n body_pos=None, **response_kw):\n \"\"\"\n Get a connection from the pool and perform an HTTP request. This is the\n lowest level call for making a request, so you'll need to specify all\n the raw details.\n \n .. note::\n \n More commonly, it's appropriate to use a convenience method provided\n by :class:`.RequestMethods`, such as :meth:`request`.\n \n .. note::\n \n `release_conn` will only behave as expected if\n `preload_content=False` because we want to make\n `preload_content=False` the default behaviour someday soon without\n breaking backwards compatibility.\n \n :param method:\n HTTP request method (such as GET, POST, PUT, etc.)\n \n :param body:\n Data to send in the request body (useful for creating\n POST requests, see HTTPConnectionPool.post_url for\n more convenience).\n \n :param headers:\n Dictionary of custom headers to send, such as User-Agent,\n If-None-Match, etc. If None, pool headers are used. If provided,\n these headers completely replace any pool-specific headers.\n \n :param retries:\n Configure the number of retries to allow before raising a\n :class:`~urllib3.exceptions.MaxRetryError` exception.\n \n Pass ``None`` to retry until you receive a response. Pass a\n :class:`~urllib3.util.retry.Retry` object for fine-grained control\n over different types of retries.\n Pass an integer number to retry connection errors that many times,\n but no other types of errors. Pass zero to never retry.\n \n If ``False``, then retries are disabled and any exception is raised\n immediately. Also, instead of raising a MaxRetryError on redirects,\n the redirect response will be returned.\n \n :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.\n \n :param redirect:\n If True, automatically handle redirects (status codes 301, 302,\n 303, 307, 308). Each redirect counts as a retry. Disabling retries\n will disable redirect, too.\n \n :param assert_same_host:\n If ``True``, will make sure that the host of the pool requests is\n consistent else will raise HostChangedError. When False, you can\n use the pool on an HTTP proxy and request foreign hosts.\n \n :param timeout:\n If specified, overrides the default timeout for this one\n request. It may be a float (in seconds) or an instance of\n :class:`urllib3.util.Timeout`.\n \n :param pool_timeout:\n If set and the pool is set to block=True, then this method will\n block for ``pool_timeout`` seconds and raise EmptyPoolError if no\n connection is available within the time period.\n \n :param release_conn:\n If False, then the urlopen call will not release the connection\n back into the pool once a response is received (but will release if\n you read the entire contents of the response such as when\n `preload_content=True`). This is useful if you're not preloading\n the response's content immediately. You will need to call\n ``r.release_conn()`` on the response ``r`` to return the connection\n back into the pool. If None, it takes the value of\n ``response_kw.get('preload_content', True)``.\n \n :param chunked:\n If True, urllib3 will send the body using chunked transfer\n encoding. Otherwise, urllib3 will send the body using the standard\n content-length form. Defaults to False.\n \n :param int body_pos:\n Position to seek to in file-like body in the event of a retry or\n redirect. Typically this won't need to be set because urllib3 will\n auto-populate the value when needed.\n \n :param \\\\**response_kw:\n Additional parameters are passed to\n :meth:`urllib3.response.HTTPResponse.from_httplib`\n \"\"\"\n if headers is None:\n headers = self.headers\n \n if not isinstance(retries, Retry):\n retries = Retry.from_int(retries, redirect=redirect, default=self.retries)\n \n if release_conn is None:\n release_conn = response_kw.get('preload_content', True)\n \n # Check host\n if assert_same_host and not self.is_same_host(url):\n raise HostChangedError(self, url, retries)\n \n conn = None\n \n # Track whether `conn` needs to be released before\n # returning/raising/recursing. Update this variable if necessary, and\n # leave `release_conn` constant throughout the function. That way, if\n # the function recurses, the original value of `release_conn` will be\n # passed down into the recursive call, and its value will be respected.\n #\n # See issue #651 [1] for details.\n #\n # [1] \n release_this_conn = release_conn\n \n # Merge the proxy headers. Only do this in HTTP. We have to copy the\n # headers dict so we can safely change it without those changes being\n # reflected in anyone else's copy.\n if self.scheme == 'http':\n headers = headers.copy()\n headers.update(self.proxy_headers)\n \n # Must keep the exception bound to a separate variable or else Python 3\n # complains about UnboundLocalError.\n err = None\n \n # Keep track of whether we cleanly exited the except block. This\n # ensures we do proper cleanup in finally.\n clean_exit = False\n \n # Rewind body position, if needed. Record current position\n # for future rewinds in the event of a redirect/retry.\n body_pos = set_file_position(body, body_pos)\n \n try:\n # Request a connection from the queue.\n timeout_obj = self._get_timeout(timeout)\n conn = self._get_conn(timeout=pool_timeout)\n \n conn.timeout = timeout_obj.connect_timeout\n \n is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)\n if is_new_proxy_conn:\n> self._prepare_proxy(conn)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:595: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nconn = \n\n def _prepare_proxy(self, conn):\n \"\"\"\n Establish tunnel connection early, because otherwise httplib\n would improperly set Host: header to proxy's IP:port.\n \"\"\"\n # Python 2.7+\n try:\n set_tunnel = conn.set_tunnel\n except AttributeError: # Platform-specific: Python 2.6\n set_tunnel = conn._set_tunnel\n \n if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older\n set_tunnel(self._proxy_host, self.port)\n else:\n set_tunnel(self._proxy_host, self.port, self.proxy_headers)\n \n> conn.connect()\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:816: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \n\n def connect(self):\n # Add certificate verification\n conn = self._new_conn()\n \n hostname = self.host\n if getattr(self, '_tunnel_host', None):\n # _tunnel_host was added in Python 2.6.3\n # (See: http://hg.python.org/cpython/rev/0f57b30a152f)\n \n self.sock = conn\n # Calls self._set_hostport(), so self.host is\n # self._tunnel_host below.\n self._tunnel()\n # Mark this connection as not reusable\n self.auto_open = 0\n \n # Override the host with the one we're requesting data from.\n hostname = self._tunnel_host\n \n is_time_off = datetime.date.today() < RECENT_DATE\n if is_time_off:\n warnings.warn((\n 'System time is way off (before {0}). This will probably '\n 'lead to SSL verification errors').format(RECENT_DATE),\n SystemTimeWarning\n )\n \n # Wrap socket using verification with the root certs in\n # trusted_root_certs\n if self.ssl_context is None:\n self.ssl_context = create_urllib3_context(\n ssl_version=resolve_ssl_version(self.ssl_version),\n cert_reqs=resolve_cert_reqs(self.cert_reqs),\n )\n \n context = self.ssl_context\n context.verify_mode = resolve_cert_reqs(self.cert_reqs)\n self.sock = ssl_wrap_socket(\n sock=conn,\n keyfile=self.key_file,\n certfile=self.cert_file,\n ca_certs=self.ca_certs,\n ca_cert_dir=self.ca_cert_dir,\n server_hostname=hostname,\n> ssl_context=context)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connection.py:326: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nsock = \nkeyfile = None, certfile = None, cert_reqs = None\nca_certs = '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/certifi/cacert.pem'\nserver_hostname = 'service.papaya.fm', ssl_version = None, ciphers = None\nssl_context = \nca_cert_dir = None\n\n def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,\n ca_certs=None, server_hostname=None,\n ssl_version=None, ciphers=None, ssl_context=None,\n ca_cert_dir=None):\n \"\"\"\n All arguments except for server_hostname, ssl_context, and ca_cert_dir have\n the same meaning as they do when using :func:`ssl.wrap_socket`.\n \n :param server_hostname:\n When SNI is supported, the expected hostname of the certificate\n :param ssl_context:\n A pre-made :class:`SSLContext` object. If none is provided, one will\n be created using :func:`create_urllib3_context`.\n :param ciphers:\n A string of ciphers we wish the client to support. This is not\n supported on Python 2.6 as the ssl module does not support it.\n :param ca_cert_dir:\n A directory containing CA certificates in multiple separate files, as\n supported by OpenSSL's -CApath flag or the capath argument to\n SSLContext.load_verify_locations().\n \"\"\"\n context = ssl_context\n if context is None:\n # Note: This branch of code and all the variables in it are no longer\n # used by urllib3 itself. We should consider deprecating and removing\n # this code.\n context = create_urllib3_context(ssl_version, cert_reqs,\n ciphers=ciphers)\n \n if ca_certs or ca_cert_dir:\n try:\n context.load_verify_locations(ca_certs, ca_cert_dir)\n except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2\n raise SSLError(e)\n # Py33 raises FileNotFoundError which subclasses OSError\n # These are not equivalent unless we check the errno attribute\n except OSError as e: # Platform-specific: Python 3.3 and beyond\n if e.errno == errno.ENOENT:\n raise SSLError(e)\n raise\n elif getattr(context, 'load_default_certs', None) is not None:\n # try to load OS default certs; works well on Windows (require Python3.4+)\n context.load_default_certs()\n \n if certfile:\n context.load_cert_chain(certfile, keyfile)\n if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI\n> return context.wrap_socket(sock, server_hostname=server_hostname)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/ssl_.py:329: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nsock = \nserver_side = False, do_handshake_on_connect = True, suppress_ragged_eofs = True\nserver_hostname = b'service.papaya.fm'\n\n def wrap_socket(self, sock, server_side=False,\n do_handshake_on_connect=True, suppress_ragged_eofs=True,\n server_hostname=None):\n cnx = OpenSSL.SSL.Connection(self._ctx, sock)\n \n if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3\n server_hostname = server_hostname.encode('utf-8')\n \n if server_hostname is not None:\n cnx.set_tlsext_host_name(server_hostname)\n \n cnx.set_connect_state()\n \n while True:\n try:\n cnx.do_handshake()\n except OpenSSL.SSL.WantReadError:\n rd = util.wait_for_read(sock, sock.gettimeout())\n if not rd:\n raise timeout('select timed out')\n continue\n except OpenSSL.SSL.Error as e:\n> raise ssl.SSLError('bad handshake: %r' % e)\nE ssl.SSLError: (\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",)\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/contrib/pyopenssl.py:448: SSLError\n\nDuring handling of the above exception, another exception occurred:\n\nself = \nrequest = , stream = False\ntimeout = , verify = True\ncert = None\nproxies = OrderedDict([('http', 'http://127.0.0.1:8888'), ('https', 'http://127.0.0.1:8888')])\n\n def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):\n \"\"\"Sends PreparedRequest object. Returns Response object.\n \n :param request: The :class:`PreparedRequest ` being sent.\n :param stream: (optional) Whether to stream the request content.\n :param timeout: (optional) How long to wait for the server to send\n data before giving up, as a float, or a :ref:`(connect timeout,\n read timeout) ` tuple.\n :type timeout: float or tuple or urllib3 Timeout object\n :param verify: (optional) Either a boolean, in which case it controls whether\n we verify the server's TLS certificate, or a string, in which case it\n must be a path to a CA bundle to use\n :param cert: (optional) Any user-provided SSL certificate to be trusted.\n :param proxies: (optional) The proxies dictionary to apply to the request.\n :rtype: requests.Response\n \"\"\"\n \n conn = self.get_connection(request.url, proxies)\n \n self.cert_verify(conn, request.url, verify, cert)\n url = self.request_url(request, proxies)\n self.add_headers(request)\n \n chunked = not (request.body is None or 'Content-Length' in request.headers)\n \n if isinstance(timeout, tuple):\n try:\n connect, read = timeout\n timeout = TimeoutSauce(connect=connect, read=read)\n except ValueError as e:\n # this may raise a string formatting error.\n err = (\"Invalid timeout {0}. Pass a (connect, read) \"\n \"timeout tuple, or a single float to set \"\n \"both timeouts to the same value\".format(timeout))\n raise ValueError(err)\n elif isinstance(timeout, TimeoutSauce):\n pass\n else:\n timeout = TimeoutSauce(connect=timeout, read=timeout)\n \n try:\n if not chunked:\n resp = conn.urlopen(\n method=request.method,\n url=url,\n body=request.body,\n headers=request.headers,\n redirect=False,\n assert_same_host=False,\n preload_content=False,\n decode_content=False,\n retries=self.max_retries,\n> timeout=timeout\n )\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py:440: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nmethod = 'POST', url = '/api/registerFromThirdPart'\nbody = '{\"loginOptions\": \"device_login\", \"timeZone\": \"Asia/Shanghai\", \"deviceInfoList\": [\"019F9E50-9561-4FFB-87F9-5FDD8A579B67\", \"3145728000\", \"414\", \"736\"], \"deviceId\": \"bf38b545-b766-8c2a-8ec7-e80ccf22a3b4\"}'\nheaders = {'Connection': 'keep-alive', 'Accept': '*/*', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'Accept-...': 'gzip, deflate', 'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'Content-Length': '201'}\nretries = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nredirect = False, assert_same_host = False\ntimeout = \npool_timeout = None, release_conn = False, chunked = False, body_pos = None\nresponse_kw = {'decode_content': False, 'preload_content': False}, conn = None\nrelease_this_conn = True, err = None, clean_exit = False\ntimeout_obj = \nis_new_proxy_conn = True\n\n def urlopen(self, method, url, body=None, headers=None, retries=None,\n redirect=True, assert_same_host=True, timeout=_Default,\n pool_timeout=None, release_conn=None, chunked=False,\n body_pos=None, **response_kw):\n \"\"\"\n Get a connection from the pool and perform an HTTP request. This is the\n lowest level call for making a request, so you'll need to specify all\n the raw details.\n \n .. note::\n \n More commonly, it's appropriate to use a convenience method provided\n by :class:`.RequestMethods`, such as :meth:`request`.\n \n .. note::\n \n `release_conn` will only behave as expected if\n `preload_content=False` because we want to make\n `preload_content=False` the default behaviour someday soon without\n breaking backwards compatibility.\n \n :param method:\n HTTP request method (such as GET, POST, PUT, etc.)\n \n :param body:\n Data to send in the request body (useful for creating\n POST requests, see HTTPConnectionPool.post_url for\n more convenience).\n \n :param headers:\n Dictionary of custom headers to send, such as User-Agent,\n If-None-Match, etc. If None, pool headers are used. If provided,\n these headers completely replace any pool-specific headers.\n \n :param retries:\n Configure the number of retries to allow before raising a\n :class:`~urllib3.exceptions.MaxRetryError` exception.\n \n Pass ``None`` to retry until you receive a response. Pass a\n :class:`~urllib3.util.retry.Retry` object for fine-grained control\n over different types of retries.\n Pass an integer number to retry connection errors that many times,\n but no other types of errors. Pass zero to never retry.\n \n If ``False``, then retries are disabled and any exception is raised\n immediately. Also, instead of raising a MaxRetryError on redirects,\n the redirect response will be returned.\n \n :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.\n \n :param redirect:\n If True, automatically handle redirects (status codes 301, 302,\n 303, 307, 308). Each redirect counts as a retry. Disabling retries\n will disable redirect, too.\n \n :param assert_same_host:\n If ``True``, will make sure that the host of the pool requests is\n consistent else will raise HostChangedError. When False, you can\n use the pool on an HTTP proxy and request foreign hosts.\n \n :param timeout:\n If specified, overrides the default timeout for this one\n request. It may be a float (in seconds) or an instance of\n :class:`urllib3.util.Timeout`.\n \n :param pool_timeout:\n If set and the pool is set to block=True, then this method will\n block for ``pool_timeout`` seconds and raise EmptyPoolError if no\n connection is available within the time period.\n \n :param release_conn:\n If False, then the urlopen call will not release the connection\n back into the pool once a response is received (but will release if\n you read the entire contents of the response such as when\n `preload_content=True`). This is useful if you're not preloading\n the response's content immediately. You will need to call\n ``r.release_conn()`` on the response ``r`` to return the connection\n back into the pool. If None, it takes the value of\n ``response_kw.get('preload_content', True)``.\n \n :param chunked:\n If True, urllib3 will send the body using chunked transfer\n encoding. Otherwise, urllib3 will send the body using the standard\n content-length form. Defaults to False.\n \n :param int body_pos:\n Position to seek to in file-like body in the event of a retry or\n redirect. Typically this won't need to be set because urllib3 will\n auto-populate the value when needed.\n \n :param \\\\**response_kw:\n Additional parameters are passed to\n :meth:`urllib3.response.HTTPResponse.from_httplib`\n \"\"\"\n if headers is None:\n headers = self.headers\n \n if not isinstance(retries, Retry):\n retries = Retry.from_int(retries, redirect=redirect, default=self.retries)\n \n if release_conn is None:\n release_conn = response_kw.get('preload_content', True)\n \n # Check host\n if assert_same_host and not self.is_same_host(url):\n raise HostChangedError(self, url, retries)\n \n conn = None\n \n # Track whether `conn` needs to be released before\n # returning/raising/recursing. Update this variable if necessary, and\n # leave `release_conn` constant throughout the function. That way, if\n # the function recurses, the original value of `release_conn` will be\n # passed down into the recursive call, and its value will be respected.\n #\n # See issue #651 [1] for details.\n #\n # [1] \n release_this_conn = release_conn\n \n # Merge the proxy headers. Only do this in HTTP. We have to copy the\n # headers dict so we can safely change it without those changes being\n # reflected in anyone else's copy.\n if self.scheme == 'http':\n headers = headers.copy()\n headers.update(self.proxy_headers)\n \n # Must keep the exception bound to a separate variable or else Python 3\n # complains about UnboundLocalError.\n err = None\n \n # Keep track of whether we cleanly exited the except block. This\n # ensures we do proper cleanup in finally.\n clean_exit = False\n \n # Rewind body position, if needed. Record current position\n # for future rewinds in the event of a redirect/retry.\n body_pos = set_file_position(body, body_pos)\n \n try:\n # Request a connection from the queue.\n timeout_obj = self._get_timeout(timeout)\n conn = self._get_conn(timeout=pool_timeout)\n \n conn.timeout = timeout_obj.connect_timeout\n \n is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)\n if is_new_proxy_conn:\n self._prepare_proxy(conn)\n \n # Make the request on the httplib connection object.\n httplib_response = self._make_request(conn, method, url,\n timeout=timeout_obj,\n body=body, headers=headers,\n chunked=chunked)\n \n # If we're going to release the connection in ``finally:``, then\n # the response doesn't need to know about the connection. Otherwise\n # it will also try to release it and we'll have a double-release\n # mess.\n response_conn = conn if not release_conn else None\n \n # Pass method to Response for length checking\n response_kw['request_method'] = method\n \n # Import httplib's response into our own wrapper object\n response = self.ResponseCls.from_httplib(httplib_response,\n pool=self,\n connection=response_conn,\n retries=retries,\n **response_kw)\n \n # Everything went great!\n clean_exit = True\n \n except queue.Empty:\n # Timed out by queue.\n raise EmptyPoolError(self, \"No pool connections are available.\")\n \n except (TimeoutError, HTTPException, SocketError, ProtocolError,\n BaseSSLError, SSLError, CertificateError) as e:\n # Discard the connection for these exceptions. It will be\n # replaced during the next _get_conn() call.\n clean_exit = False\n if isinstance(e, (BaseSSLError, CertificateError)):\n e = SSLError(e)\n elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy:\n e = ProxyError('Cannot connect to proxy.', e)\n elif isinstance(e, (SocketError, HTTPException)):\n e = ProtocolError('Connection aborted.', e)\n \n retries = retries.increment(method, url, error=e, _pool=self,\n> _stacktrace=sys.exc_info()[2])\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py:639: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = Retry(total=0, connect=None, read=False, redirect=None, status=None)\nmethod = 'POST', url = '/api/registerFromThirdPart', response = None\nerror = SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),)\n_pool = \n_stacktrace = \n\n def increment(self, method=None, url=None, response=None, error=None,\n _pool=None, _stacktrace=None):\n \"\"\" Return a new Retry object with incremented retry counters.\n \n :param response: A response object, or None, if the server did not\n return a response.\n :type response: :class:`~urllib3.response.HTTPResponse`\n :param Exception error: An error encountered during the request, or\n None if the response was received successfully.\n \n :return: A new ``Retry`` object.\n \"\"\"\n if self.total is False and error:\n # Disabled, indicate to re-raise the error.\n raise six.reraise(type(error), error, _stacktrace)\n \n total = self.total\n if total is not None:\n total -= 1\n \n connect = self.connect\n read = self.read\n redirect = self.redirect\n status_count = self.status\n cause = 'unknown'\n status = None\n redirect_location = None\n \n if error and self._is_connection_error(error):\n # Connect retry?\n if connect is False:\n raise six.reraise(type(error), error, _stacktrace)\n elif connect is not None:\n connect -= 1\n \n elif error and self._is_read_error(error):\n # Read retry?\n if read is False or not self._is_method_retryable(method):\n raise six.reraise(type(error), error, _stacktrace)\n elif read is not None:\n read -= 1\n \n elif response and response.get_redirect_location():\n # Redirect retry?\n if redirect is not None:\n redirect -= 1\n cause = 'too many redirects'\n redirect_location = response.get_redirect_location()\n status = response.status\n \n else:\n # Incrementing because of a server error like a 500 in\n # status_forcelist and a the given method is in the whitelist\n cause = ResponseError.GENERIC_ERROR\n if response and response.status:\n if status_count is not None:\n status_count -= 1\n cause = ResponseError.SPECIFIC_ERROR.format(\n status_code=response.status)\n status = response.status\n \n history = self.history + (RequestHistory(method, url, error, status, redirect_location),)\n \n new_retry = self.new(\n total=total,\n connect=connect, read=read, redirect=redirect, status=status_count,\n history=history)\n \n if new_retry.is_exhausted():\n> raise MaxRetryError(_pool, url, error or ResponseError(cause))\nE urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/retry.py:388: MaxRetryError\n\nDuring handling of the above exception, another exception occurred:\n\nself = \ncase_data = {'address': '/api/registerFromThirdPart', 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'cookies': False, 'file': False, ...}\n\n @pytest.mark.parametrize(\"case_data\", case_dict[\"test_case\"], ids=[])\n @allure.story(\"registerFromThirdPart\")\n # @pytest.mark.flaky(reruns=3, reruns_delay=3)\n def test_registerfromthirdpart(self, case_data):\n \"\"\"\n \n :param case_data: 测试用例\n :return:\n \"\"\"\n self.init_relevance = ini_request(case_dict, PATH)\n # 发送测试请求\n> api_send_check(case_data, case_dict, self.init_relevance, PATH)\n\ncrm/testcase/api/test_registerFromThirdPart.py:33: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nbin/unit/apiSendCheck.py:23: in api_send_check\n project_dict[\"test_info\"].get(\"address\"), _path, relevance)\nbin/unit/apiSend.py:81: in send_request\n timeout=data[\"timeout\"])\nbin/unit/apiMethod.py:49: in post\n response = requests.post(url=address, data=data, headers=header, timeout=timeout)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/api.py:112: in post\n return request('post', url, data=data, json=json, **kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/api.py:58: in request\n return session.request(method=method, url=url, **kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py:508: in request\n resp = self.send(prep, **send_kwargs)\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py:618: in send\n r = adapter.send(request, **kwargs)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = \nrequest = , stream = False\ntimeout = , verify = True\ncert = None\nproxies = OrderedDict([('http', 'http://127.0.0.1:8888'), ('https', 'http://127.0.0.1:8888')])\n\n def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):\n \"\"\"Sends PreparedRequest object. Returns Response object.\n \n :param request: The :class:`PreparedRequest ` being sent.\n :param stream: (optional) Whether to stream the request content.\n :param timeout: (optional) How long to wait for the server to send\n data before giving up, as a float, or a :ref:`(connect timeout,\n read timeout) ` tuple.\n :type timeout: float or tuple or urllib3 Timeout object\n :param verify: (optional) Either a boolean, in which case it controls whether\n we verify the server's TLS certificate, or a string, in which case it\n must be a path to a CA bundle to use\n :param cert: (optional) Any user-provided SSL certificate to be trusted.\n :param proxies: (optional) The proxies dictionary to apply to the request.\n :rtype: requests.Response\n \"\"\"\n \n conn = self.get_connection(request.url, proxies)\n \n self.cert_verify(conn, request.url, verify, cert)\n url = self.request_url(request, proxies)\n self.add_headers(request)\n \n chunked = not (request.body is None or 'Content-Length' in request.headers)\n \n if isinstance(timeout, tuple):\n try:\n connect, read = timeout\n timeout = TimeoutSauce(connect=connect, read=read)\n except ValueError as e:\n # this may raise a string formatting error.\n err = (\"Invalid timeout {0}. Pass a (connect, read) \"\n \"timeout tuple, or a single float to set \"\n \"both timeouts to the same value\".format(timeout))\n raise ValueError(err)\n elif isinstance(timeout, TimeoutSauce):\n pass\n else:\n timeout = TimeoutSauce(connect=timeout, read=timeout)\n \n try:\n if not chunked:\n resp = conn.urlopen(\n method=request.method,\n url=url,\n body=request.body,\n headers=request.headers,\n redirect=False,\n assert_same_host=False,\n preload_content=False,\n decode_content=False,\n retries=self.max_retries,\n timeout=timeout\n )\n \n # Send the request.\n else:\n if hasattr(conn, 'proxy_pool'):\n conn = conn.proxy_pool\n \n low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)\n \n try:\n low_conn.putrequest(request.method,\n url,\n skip_accept_encoding=True)\n \n for header, value in request.headers.items():\n low_conn.putheader(header, value)\n \n low_conn.endheaders()\n \n for i in request.body:\n low_conn.send(hex(len(i))[2:].encode('utf-8'))\n low_conn.send(b'\\r\\n')\n low_conn.send(i)\n low_conn.send(b'\\r\\n')\n low_conn.send(b'0\\r\\n\\r\\n')\n \n # Receive the response from the server\n try:\n # For Python 2.7+ versions, use buffering of HTTP\n # responses\n r = low_conn.getresponse(buffering=True)\n except TypeError:\n # For compatibility with Python 2.6 versions and back\n r = low_conn.getresponse()\n \n resp = HTTPResponse.from_httplib(\n r,\n pool=conn,\n connection=low_conn,\n preload_content=False,\n decode_content=False\n )\n except:\n # If we hit any problems here, clean up the connection.\n # Then, reraise so that we can handle the actual exception.\n low_conn.close()\n raise\n \n except (ProtocolError, socket.error) as err:\n raise ConnectionError(err, request=request)\n \n except MaxRetryError as e:\n if isinstance(e.reason, ConnectTimeoutError):\n # TODO: Remove this in 3.0.0: see #2811\n if not isinstance(e.reason, NewConnectionError):\n raise ConnectTimeout(e, request=request)\n \n if isinstance(e.reason, ResponseError):\n raise RetryError(e, request=request)\n \n if isinstance(e.reason, _ProxyError):\n raise ProxyError(e, request=request)\n \n if isinstance(e.reason, _SSLError):\n # This branch is for urllib3 v1.22 and later.\n> raise SSLError(e, request=request)\nE requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))\n\n/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py:506: SSLError", 24 | "steps" : [ { 25 | "name" : "POST请求接口", 26 | "time" : { 27 | "start" : 1590999247291, 28 | "stop" : 1590999247292, 29 | "duration" : 1 30 | }, 31 | "status" : "passed", 32 | "steps" : [ ], 33 | "attachments" : [ { 34 | "uid" : "7be0ae4e1a985894", 35 | "name" : "请求接口", 36 | "source" : "7be0ae4e1a985894.attach", 37 | "type" : "text/plain", 38 | "size" : 21 39 | }, { 40 | "uid" : "ce8359096e29e5b7", 41 | "name" : "请求地址", 42 | "source" : "ce8359096e29e5b7.attach", 43 | "type" : "text/plain", 44 | "size" : 51 45 | }, { 46 | "uid" : "a42bc2ee2ac1e349", 47 | "name" : "请求头", 48 | "source" : "a42bc2ee2ac1e349.attach", 49 | "type" : "text/plain", 50 | "size" : 139 51 | }, { 52 | "uid" : "e4d2dd8b170229b0", 53 | "name" : "请求参数", 54 | "source" : "e4d2dd8b170229b0.attach", 55 | "type" : "text/plain", 56 | "size" : 201 57 | } ], 58 | "parameters" : [ ], 59 | "stepsCount" : 0, 60 | "attachmentsCount" : 4, 61 | "shouldDisplayMessage" : false, 62 | "hasContent" : true 63 | } ], 64 | "attachments" : [ { 65 | "uid" : "304af0ca57799642", 66 | "name" : "log", 67 | "source" : "304af0ca57799642.txt", 68 | "type" : "text/plain", 69 | "size" : 1380 70 | } ], 71 | "parameters" : [ ], 72 | "stepsCount" : 1, 73 | "attachmentsCount" : 5, 74 | "shouldDisplayMessage" : true, 75 | "hasContent" : true 76 | }, 77 | "afterStages" : [ ], 78 | "labels" : [ { 79 | "name" : "feature", 80 | "value" : "api" 81 | }, { 82 | "name" : "story", 83 | "value" : "registerFromThirdPart" 84 | }, { 85 | "name" : "subSuite", 86 | "value" : "TestRegisterfromthirdpart" 87 | }, { 88 | "name" : "parentSuite", 89 | "value" : "crm.testcase.api" 90 | }, { 91 | "name" : "suite", 92 | "value" : "test_registerFromThirdPart" 93 | }, { 94 | "name" : "host", 95 | "value" : "xiaoxideMacBook.lan" 96 | }, { 97 | "name" : "thread", 98 | "value" : "6741-MainThread" 99 | }, { 100 | "name" : "framework", 101 | "value" : "pytest" 102 | }, { 103 | "name" : "language", 104 | "value" : "cpython3" 105 | }, { 106 | "name" : "package", 107 | "value" : "crm.testcase.api.test_registerFromThirdPart" 108 | }, { 109 | "name" : "resultFormat", 110 | "value" : "allure2" 111 | } ], 112 | "parameters" : [ { 113 | "name" : "case_data", 114 | "value" : "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" 115 | } ], 116 | "links" : [ ], 117 | "hidden" : false, 118 | "retry" : false, 119 | "extra" : { 120 | "severity" : "normal", 121 | "retries" : [ ], 122 | "categories" : [ { 123 | "name" : "Test defects", 124 | "matchedStatuses" : [ ], 125 | "flaky" : false 126 | } ], 127 | "tags" : [ ] 128 | }, 129 | "source" : "90e76208292b8b5b.json", 130 | "parameterValues" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 131 | } -------------------------------------------------------------------------------- /content/report/html/data/timeline.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "ab17fc5a4eb3bca4b216b548c7f9fcbc", 3 | "name" : "timeline", 4 | "children" : [ { 5 | "name" : "xiaoxideMacBook.lan", 6 | "children" : [ { 7 | "name" : "6741-MainThread", 8 | "children" : [ { 9 | "name" : "test_registerfromthirdpart[case_data0]", 10 | "uid" : "90e76208292b8b5b", 11 | "parentUid" : "ea793beac19a09d4f0bb3c680637ad0f", 12 | "status" : "broken", 13 | "time" : { 14 | "start" : 1590999247287, 15 | "stop" : 1590999248398, 16 | "duration" : 1111 17 | }, 18 | "flaky" : false, 19 | "newFailed" : false, 20 | "parameters" : [ "{'request_type': 'POST', 'parameter_type': 'application/x-www-form-urlencoded', 'info': 'registerFromThirdPart', 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'file': False, 'test_name': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'check': {'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json', 'check_type': 'json'}, 'headers': {'appversion': '3.4.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0'}, 'relevance': None, 'cookies': False, 'http_type': 'https'}" ] 21 | } ], 22 | "uid" : "ea793beac19a09d4f0bb3c680637ad0f" 23 | }, { 24 | "name" : "26484-MainThread", 25 | "children" : [ { 26 | "name" : "test_registerfromthirdpart[case_data0]", 27 | "uid" : "7a5644b324f85d27", 28 | "parentUid" : "55d23626a2c55ad149d15aec94a1afd3", 29 | "status" : "passed", 30 | "time" : { 31 | "start" : 1594014671665, 32 | "stop" : 1594014672562, 33 | "duration" : 897 34 | }, 35 | "flaky" : false, 36 | "newFailed" : false, 37 | "parameters" : [ "{'file': False, 'check': {'check_type': 'json', 'expected_code': 200, 'expected_request': 'result_registerFromThirdPart.json'}, 'headers': {'user-agent': 'PapayaFM/1802 CFNetwork/1121.2.2 Darwin/19.3.0', 'deviceid': 'bf38b545-b766-8c2a-8ec7-e80ccf22a3b4', 'appversion': '3.4.0'}, 'cookies': False, 'timeout': 60, 'parameter': 'registerFromThirdPart.json', 'parameter_type': 'application/x-www-form-urlencoded', 'test_name': 'registerFromThirdPart', 'info': 'registerFromThirdPart', 'address': '/api/registerFromThirdPart', 'request_type': 'POST', 'relevance': None, 'http_type': 'https'}" ] 38 | } ], 39 | "uid" : "55d23626a2c55ad149d15aec94a1afd3" 40 | } ], 41 | "uid" : "60d6f9ee33f21695800f326c72fb0026" 42 | } ] 43 | } -------------------------------------------------------------------------------- /content/report/html/export/influxDbData.txt: -------------------------------------------------------------------------------- 1 | launch_status failed=0 1594014674000000000 2 | launch_status broken=1 1594014674000000000 3 | launch_status passed=1 1594014674000000000 4 | launch_status skipped=0 1594014674000000000 5 | launch_status unknown=0 1594014674000000000 6 | launch_time duration=3015425275 1594014674000000000 7 | launch_time min_duration=897 1594014674000000000 8 | launch_time max_duration=1111 1594014674000000000 9 | launch_time sum_duration=2008 1594014674000000000 10 | launch_problems test_defects=1 1594014674000000000 11 | launch_retries retries=0 1594014674000000000 12 | launch_retries run=2 1594014674000000000 13 | -------------------------------------------------------------------------------- /content/report/html/export/mail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Allure Report summary mail 6 | 7 | 8 | Mail body 9 | 10 | 11 | -------------------------------------------------------------------------------- /content/report/html/export/prometheusData.txt: -------------------------------------------------------------------------------- 1 | launch_status_failed 0 2 | launch_status_broken 1 3 | launch_status_passed 1 4 | launch_status_skipped 0 5 | launch_status_unknown 0 6 | launch_time_duration 3015425275 7 | launch_time_min_duration 897 8 | launch_time_max_duration 1111 9 | launch_time_sum_duration 2008 10 | launch_problems_test_defects 1 11 | launch_retries_retries 0 12 | launch_retries_run 2 13 | -------------------------------------------------------------------------------- /content/report/html/favicon.ico: -------------------------------------------------------------------------------- 1 | module.exports = __webpack_public_path__ + "favicon.ico"; -------------------------------------------------------------------------------- /content/report/html/history/categories-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "Test defects" : 1 4 | } 5 | } ] -------------------------------------------------------------------------------- /content/report/html/history/duration-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "duration" : 3015425275 4 | } 5 | } ] -------------------------------------------------------------------------------- /content/report/html/history/history-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "failed" : 0, 4 | "broken" : 1, 5 | "skipped" : 0, 6 | "passed" : 1, 7 | "unknown" : 0, 8 | "total" : 2 9 | } 10 | } ] -------------------------------------------------------------------------------- /content/report/html/history/history.json: -------------------------------------------------------------------------------- 1 | { 2 | "5d7c8a95eea77353216e45474fe0ca72" : { 3 | "statistic" : { 4 | "failed" : 0, 5 | "broken" : 0, 6 | "skipped" : 0, 7 | "passed" : 1, 8 | "unknown" : 0, 9 | "total" : 1 10 | }, 11 | "items" : [ { 12 | "uid" : "7a5644b324f85d27", 13 | "status" : "passed", 14 | "time" : { 15 | "start" : 1594014671665, 16 | "stop" : 1594014672562, 17 | "duration" : 897 18 | } 19 | } ] 20 | }, 21 | "c726df5b4b0e58b446c43e76a1c2c73b" : { 22 | "statistic" : { 23 | "failed" : 0, 24 | "broken" : 1, 25 | "skipped" : 0, 26 | "passed" : 0, 27 | "unknown" : 0, 28 | "total" : 1 29 | }, 30 | "items" : [ { 31 | "uid" : "90e76208292b8b5b", 32 | "status" : "broken", 33 | "statusDetails" : "requests.exceptions.SSLError: HTTPSConnectionPool(host='service.papaya.fm', port=443): Max retries exceeded with url: /api/registerFromThirdPart (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))", 34 | "time" : { 35 | "start" : 1590999247287, 36 | "stop" : 1590999248398, 37 | "duration" : 1111 38 | } 39 | } ] 40 | } 41 | } -------------------------------------------------------------------------------- /content/report/html/history/retry-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "run" : 2, 4 | "retry" : 0 5 | } 6 | } ] -------------------------------------------------------------------------------- /content/report/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Allure Report 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /content/report/html/plugins/behaviors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | allure.api.addTranslation('en', { 4 | tab: { 5 | behaviors: { 6 | name: 'Behaviors' 7 | } 8 | }, 9 | widget: { 10 | behaviors: { 11 | name: 'Features by stories', 12 | showAll: 'show all' 13 | } 14 | } 15 | }); 16 | 17 | allure.api.addTranslation('ru', { 18 | tab: { 19 | behaviors: { 20 | name: 'Функциональность' 21 | } 22 | }, 23 | widget: { 24 | behaviors: { 25 | name: 'Функциональность', 26 | showAll: 'показать все' 27 | } 28 | } 29 | }); 30 | 31 | allure.api.addTranslation('zh', { 32 | tab: { 33 | behaviors: { 34 | name: '功能' 35 | } 36 | }, 37 | widget: { 38 | behaviors: { 39 | name: '特性场景', 40 | showAll: '显示所有' 41 | } 42 | } 43 | }); 44 | 45 | allure.api.addTranslation('de', { 46 | tab: { 47 | behaviors: { 48 | name: 'Verhalten' 49 | } 50 | }, 51 | widget: { 52 | behaviors: { 53 | name: 'Features nach Stories', 54 | showAll: 'Zeige alle' 55 | } 56 | } 57 | }); 58 | 59 | allure.api.addTranslation('he', { 60 | tab: { 61 | behaviors: { 62 | name: 'התנהגויות' 63 | } 64 | }, 65 | widget: { 66 | behaviors: { 67 | name: 'תכונות לפי סיפורי משתמש', 68 | showAll: 'הצג הכול' 69 | } 70 | } 71 | }); 72 | 73 | allure.api.addTranslation('br', { 74 | tab: { 75 | behaviors: { 76 | name: 'Comportamentos' 77 | } 78 | }, 79 | widget: { 80 | behaviors: { 81 | name: 'Funcionalidades por história', 82 | showAll: 'Mostrar tudo' 83 | } 84 | } 85 | }); 86 | 87 | allure.api.addTranslation('ja', { 88 | tab: { 89 | behaviors: { 90 | name: '振る舞い' 91 | } 92 | }, 93 | widget: { 94 | behaviors: { 95 | name: 'ストーリー別の機能', 96 | showAll: '全て表示' 97 | } 98 | } 99 | }); 100 | 101 | allure.api.addTranslation('es', { 102 | tab: { 103 | behaviors: { 104 | name: 'Funcionalidades' 105 | } 106 | }, 107 | widget: { 108 | behaviors: { 109 | name: 'Funcionalidades por Historias de Usuario', 110 | showAll: 'mostrar todo' 111 | } 112 | } 113 | }); 114 | 115 | allure.api.addTranslation('kr', { 116 | tab: { 117 | behaviors: { 118 | name: '동작' 119 | } 120 | }, 121 | widget: { 122 | behaviors: { 123 | name: '스토리별 기능', 124 | showAll: '전체 보기' 125 | } 126 | } 127 | }); 128 | 129 | allure.api.addTab('behaviors', { 130 | title: 'tab.behaviors.name', icon: 'fa fa-list', 131 | route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)', 132 | onEnter: (function (testGroup, testResult, testResultTab) { 133 | return new allure.components.TreeLayout({ 134 | testGroup: testGroup, 135 | testResult: testResult, 136 | testResultTab: testResultTab, 137 | tabName: 'tab.behaviors.name', 138 | baseUrl: 'behaviors', 139 | url: 'data/behaviors.json', 140 | csvUrl: 'data/behaviors.csv' 141 | }); 142 | }) 143 | }); 144 | 145 | allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.extend({ 146 | rowTag: 'a', 147 | title: 'widget.behaviors.name', 148 | baseUrl: 'behaviors', 149 | showLinks: true 150 | })); -------------------------------------------------------------------------------- /content/report/html/plugins/packages/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | allure.api.addTranslation('en', { 4 | tab: { 5 | packages: { 6 | name: 'Packages' 7 | } 8 | } 9 | }); 10 | 11 | allure.api.addTranslation('ru', { 12 | tab: { 13 | packages: { 14 | name: 'Пакеты' 15 | } 16 | } 17 | }); 18 | 19 | allure.api.addTranslation('zh', { 20 | tab: { 21 | packages: { 22 | name: '包' 23 | } 24 | } 25 | }); 26 | 27 | allure.api.addTranslation('de', { 28 | tab: { 29 | packages: { 30 | name: 'Pakete' 31 | } 32 | } 33 | }); 34 | 35 | allure.api.addTranslation('he', { 36 | tab: { 37 | packages: { 38 | name: 'חבילות' 39 | } 40 | } 41 | }); 42 | 43 | allure.api.addTranslation('br', { 44 | tab: { 45 | packages: { 46 | name: 'Pacotes' 47 | } 48 | } 49 | }); 50 | 51 | allure.api.addTranslation('ja', { 52 | tab: { 53 | packages: { 54 | name: 'パッケージ' 55 | } 56 | } 57 | }); 58 | 59 | allure.api.addTranslation('es', { 60 | tab: { 61 | packages: { 62 | name: 'Paquetes' 63 | } 64 | } 65 | }); 66 | 67 | allure.api.addTranslation('kr', { 68 | tab: { 69 | packages: { 70 | name: '패키지' 71 | } 72 | } 73 | }); 74 | 75 | allure.api.addTab('packages', { 76 | title: 'tab.packages.name', icon: 'fa fa-align-left', 77 | route: 'packages(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)', 78 | onEnter: (function (testGroup, testResult, testResultTab) { 79 | return new allure.components.TreeLayout({ 80 | testGroup: testGroup, 81 | testResult: testResult, 82 | testResultTab: testResultTab, 83 | tabName: 'tab.packages.name', 84 | baseUrl: 'packages', 85 | url: 'data/packages.json' 86 | }); 87 | }) 88 | }); 89 | -------------------------------------------------------------------------------- /content/report/html/plugins/screen-diff/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var settings = allure.getPluginSettings('screen-diff', {diffType: 'diff'}); 3 | 4 | function renderImage(src) { 5 | return '
' + 6 | '' + 7 | '
'; 8 | } 9 | 10 | function renderDiffContent(type, data) { 11 | function findImage(name) { 12 | if (data.testStage && data.testStage.attachments) { 13 | return data.testStage.attachments.filter(function (attachment) { 14 | return attachment.name === name; 15 | })[0]; 16 | } 17 | return null; 18 | } 19 | 20 | var diffImage = findImage('diff'); 21 | var actualImage = findImage('actual'); 22 | var expectedImage = findImage('expected'); 23 | 24 | if (!diffImage && !actualImage && !expectedImage) { 25 | return 'Diff, actual and expected image have not been provided.'; 26 | } 27 | 28 | if (type === 'diff') { 29 | if (!diffImage) { 30 | return renderImage(actualImage.source); 31 | } 32 | return renderImage(diffImage.source); 33 | } 34 | if (type === 'overlay') { 35 | return '
' + 36 | '' + 37 | '
' + 38 | '' + 39 | '
' + 40 | '
'; 41 | } 42 | } 43 | 44 | var ScreenDiffView = Backbone.Marionette.View.extend({ 45 | className: 'pane__section', 46 | events: { 47 | 'click [name="screen-diff-type"]': 'onDiffTypeChange', 48 | 'mousemove .screen-diff__overlay': 'onOverlayMove' 49 | }, 50 | templateContext: function () { 51 | return { 52 | diffType: settings.get('diffType') 53 | } 54 | }, 55 | template: function (data) { 56 | var testType = data.labels.filter(function (label) { 57 | return label.name === 'testType' 58 | })[0]; 59 | 60 | if (!testType || testType.value !== 'screenshotDiff') { 61 | return ''; 62 | } 63 | 64 | return '

Screen Diff

' + 65 | '
' + 66 | '
' + 67 | '' + 68 | '' + 69 | '
' + 70 | renderDiffContent(data.diffType, data) + 71 | '
'; 72 | }, 73 | adjustImageSize: function (event) { 74 | var overImage = this.$(event.target); 75 | overImage.width(overImage.width()); 76 | }, 77 | onRender: function () { 78 | const diffType = settings.get('diffType'); 79 | this.$('[name="screen-diff-type"][value="' + diffType + '"]').prop('checked', true); 80 | if (diffType === 'overlay') { 81 | this.$('.screen-diff__image-over img').on('load', this.adjustImageSize.bind(this)); 82 | } 83 | }, 84 | onOverlayMove: function (event) { 85 | var pageX = event.pageX; 86 | var containerScroll = this.$('.screen-diff__container').scrollLeft(); 87 | var elementX = event.currentTarget.getBoundingClientRect().left; 88 | var delta = pageX - elementX + containerScroll; 89 | this.$('.screen-diff__image-over').width(delta); 90 | }, 91 | onDiffTypeChange: function (event) { 92 | settings.save('diffType', event.target.value); 93 | this.render(); 94 | } 95 | }); 96 | allure.api.addTestResultBlock(ScreenDiffView, {position: 'before'}); 97 | })(); 98 | -------------------------------------------------------------------------------- /content/report/html/plugins/screen-diff/styles.css: -------------------------------------------------------------------------------- 1 | .screen-diff__switchers { 2 | margin-bottom: 1em; 3 | } 4 | 5 | .screen-diff__switchers label + label { 6 | margin-left: 1em; 7 | } 8 | 9 | .screen-diff__overlay { 10 | position: relative; 11 | cursor: col-resize; 12 | } 13 | 14 | .screen-diff__container { 15 | overflow-x: auto; 16 | } 17 | 18 | .screen-diff__image-over { 19 | top: 0; 20 | left: 0; 21 | bottom: 0; 22 | background: #fff; 23 | position: absolute; 24 | overflow: hidden; 25 | box-shadow: 2px 0 1px -1px #aaa; 26 | } 27 | -------------------------------------------------------------------------------- /content/report/html/widgets/behaviors.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 1, 3 | "items" : [ { 4 | "uid" : "8f7c46175c4d7b5ea277e197185d81f1", 5 | "name" : "api", 6 | "statistic" : { 7 | "failed" : 0, 8 | "broken" : 1, 9 | "skipped" : 0, 10 | "passed" : 0, 11 | "unknown" : 0, 12 | "total" : 1 13 | } 14 | } ] 15 | } -------------------------------------------------------------------------------- /content/report/html/widgets/categories-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "Test defects" : 1 4 | } 5 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 1, 3 | "items" : [ { 4 | "uid" : "bdbf199525818fae7a8651db9eafe741", 5 | "name" : "Test defects", 6 | "statistic" : { 7 | "failed" : 0, 8 | "broken" : 1, 9 | "skipped" : 0, 10 | "passed" : 0, 11 | "unknown" : 0, 12 | "total" : 1 13 | } 14 | } ] 15 | } -------------------------------------------------------------------------------- /content/report/html/widgets/duration-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "duration" : 3015425275 4 | } 5 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/duration.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "90e76208292b8b5b", 3 | "name" : "test_registerfromthirdpart[case_data0]", 4 | "time" : { 5 | "start" : 1590999247287, 6 | "stop" : 1590999248398, 7 | "duration" : 1111 8 | }, 9 | "status" : "broken", 10 | "severity" : "normal" 11 | }, { 12 | "uid" : "7a5644b324f85d27", 13 | "name" : "test_registerfromthirdpart[case_data0]", 14 | "time" : { 15 | "start" : 1594014671665, 16 | "stop" : 1594014672562, 17 | "duration" : 897 18 | }, 19 | "status" : "passed", 20 | "severity" : "normal" 21 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/environment.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /content/report/html/widgets/executors.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /content/report/html/widgets/history-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "failed" : 0, 4 | "broken" : 1, 5 | "skipped" : 0, 6 | "passed" : 1, 7 | "unknown" : 0, 8 | "total" : 2 9 | } 10 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/launch.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /content/report/html/widgets/retry-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "run" : 2, 4 | "retry" : 0 5 | } 6 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/severity.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "7a5644b324f85d27", 3 | "name" : "test_registerfromthirdpart[case_data0]", 4 | "time" : { 5 | "start" : 1594014671665, 6 | "stop" : 1594014672562, 7 | "duration" : 897 8 | }, 9 | "status" : "passed", 10 | "severity" : "normal" 11 | }, { 12 | "uid" : "90e76208292b8b5b", 13 | "name" : "test_registerfromthirdpart[case_data0]", 14 | "time" : { 15 | "start" : 1590999247287, 16 | "stop" : 1590999248398, 17 | "duration" : 1111 18 | }, 19 | "status" : "broken", 20 | "severity" : "normal" 21 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/status-chart.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "90e76208292b8b5b", 3 | "name" : "test_registerfromthirdpart[case_data0]", 4 | "time" : { 5 | "start" : 1590999247287, 6 | "stop" : 1590999248398, 7 | "duration" : 1111 8 | }, 9 | "status" : "broken", 10 | "severity" : "normal" 11 | }, { 12 | "uid" : "7a5644b324f85d27", 13 | "name" : "test_registerfromthirdpart[case_data0]", 14 | "time" : { 15 | "start" : 1594014671665, 16 | "stop" : 1594014672562, 17 | "duration" : 897 18 | }, 19 | "status" : "passed", 20 | "severity" : "normal" 21 | } ] -------------------------------------------------------------------------------- /content/report/html/widgets/suites.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 1, 3 | "items" : [ { 4 | "uid" : "f04ab4cab6a039f79a05b60ec90c8240", 5 | "name" : "crm.testcase.api", 6 | "statistic" : { 7 | "failed" : 0, 8 | "broken" : 1, 9 | "skipped" : 0, 10 | "passed" : 1, 11 | "unknown" : 0, 12 | "total" : 2 13 | } 14 | } ] 15 | } -------------------------------------------------------------------------------- /content/report/html/widgets/summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "reportName" : "Allure Report", 3 | "testRuns" : [ ], 4 | "statistic" : { 5 | "failed" : 0, 6 | "broken" : 1, 7 | "skipped" : 0, 8 | "passed" : 1, 9 | "unknown" : 0, 10 | "total" : 2 11 | }, 12 | "time" : { 13 | "start" : 1590999247287, 14 | "stop" : 1594014672562, 15 | "duration" : 3015425275, 16 | "minDuration" : 897, 17 | "maxDuration" : 1111, 18 | "sumDuration" : 2008 19 | } 20 | } -------------------------------------------------------------------------------- /content/report/xml/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/report/xml/.DS_Store -------------------------------------------------------------------------------- /content/resource/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/05 3 | # @Author : XiaoXi 4 | # @PROJECT : api_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /content/testcase/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/testcase/.DS_Store -------------------------------------------------------------------------------- /content/testcase/Template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | 6 | import allure 7 | import pytest 8 | from setupMain import project_path 9 | from bin.config.confManage import dir_manage 10 | from bin.unit.initializeCase import ini_case 11 | from bin.unit.initializePremise import ini_request 12 | from bin.unit.apiSendCheck import api_send_check 13 | 14 | PATH = project_path + dir_manage('${page_dir}$') + "offer" 15 | 16 | case_dict = ini_case(PATH, "Template") 17 | 18 | 19 | @allure.feature(case_dict["test_info"]["title"]) 20 | class TestTemplate: 21 | 22 | @pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[]) 23 | @allure.story("Template") 24 | @pytest.mark.flaky(reruns=3, reruns_delay=3) 25 | def test_template(self, case_data): 26 | """ 27 | 28 | :param case_data: 测试用例 29 | :return: 30 | """ 31 | self.init_relevance = ini_request(case_dict, PATH) 32 | # 发送测试请求 33 | api_send_check(case_data, case_dict, self.init_relevance, PATH) 34 | 35 | 36 | -------------------------------------------------------------------------------- /content/testcase/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/05 3 | # @Author : XiaoXi 4 | # @PROJECT : api_service 5 | # @File : __init__.py.py 6 | -------------------------------------------------------------------------------- /content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-35-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-35-PYTEST.pyc -------------------------------------------------------------------------------- /content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-35-pytest-5.2.0.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-35-pytest-5.2.0.pyc -------------------------------------------------------------------------------- /content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-38-pytest-5.4.3.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/content/testcase/api/__pycache__/test_registerFromThirdPart.cpython-38-pytest-5.4.3.pyc -------------------------------------------------------------------------------- /content/testcase/api/test_registerFromThirdPart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/06 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | 6 | import allure 7 | import pytest 8 | from setupMain import project_path 9 | from bin.config.confManage import dir_manage 10 | from bin.unit.initializeCase import ini_case 11 | from bin.unit.initializePremise import ini_request 12 | from bin.unit.apiSendCheck import api_send_check 13 | 14 | PATH = project_path + dir_manage('${page_dir}$') + "api" 15 | 16 | case_dict = ini_case(PATH, "registerFromThirdPart") 17 | 18 | 19 | @allure.feature(case_dict["test_info"]["title"]) 20 | class TestRegisterfromthirdpart: 21 | 22 | @pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[]) 23 | @allure.story("registerFromThirdPart") 24 | @pytest.mark.flaky(reruns=3, reruns_delay=3) 25 | def test_registerfromthirdpart(self, case_data): 26 | """ 27 | 28 | :param case_data: 测试用例 29 | :return: 30 | """ 31 | self.init_relevance = ini_request(case_dict, PATH) 32 | # 发送测试请求 33 | api_send_check(case_data, case_dict, self.init_relevance, PATH) 34 | 35 | 36 | -------------------------------------------------------------------------------- /doc/Operation.md: -------------------------------------------------------------------------------- 1 | ## 系统环境准备: 2 | 3 | 1、jdk1.8以上 4 | 2、allure==2.13 5 | 3、python3.8 6 | 4、Charles 7 | 8 | ## python环境: 9 | pytest==5.4.0 10 | allure-pytest==2.8.15 11 | allure-python-commons==2.8.15 12 | pytest-rerunfailures==9.0 13 | allure-python-commons==2.8.15 14 | configparser==5.0.0 15 | PyYAML==5.3.1 16 | requests==2.23.0 17 | requests-toolbelt==0.9.1 18 | simplejson==3.17.0 19 | ruamel.yaml==0.16.10 20 | 21 | ## 配置步骤: 22 | 1、git clone 代码到本地 23 | 2、修改/bin/config/config.ini,directory中将CRM修改为对应项目目录 24 | 3、使用Charles录制对应接口数据,export到本地,文件类型为JSON Session File(.chlsj) 25 | 4、接口文件放在config.ini中data_dir目录 26 | 5、运行setupMain.py 27 | -------------------------------------------------------------------------------- /doc/api_service.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/doc/api_service.xmind -------------------------------------------------------------------------------- /log/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangxiaoxi3/API_service/191675fd7bbf6a98b5cee3744b4bdd5082055829/log/.DS_Store -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==5.4.0 2 | allure-pytest==2.8.15 3 | allure-python-commons==2.8.15 4 | pytest-rerunfailures==9.0 5 | allure-python-commons==2.8.15 6 | configparser==5.0.0 7 | PyYAML==5.3.1 8 | requests==2.23.0 9 | requests-toolbelt==0.9.1 10 | simplejson==3.17.0 11 | ruamel.yaml==0.16.10 -------------------------------------------------------------------------------- /setupMain.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2019/05 3 | # @Author : XiaoXi 4 | # @PROJECT : Aff_service 5 | # @File : setup.py 6 | 7 | import os 8 | 9 | 10 | import pytest 11 | from bin.config.confManage import dir_manage 12 | from bin.script.logs import LogConfig 13 | project_path = os.path.split(os.path.realpath(__file__))[0] 14 | 15 | if ':' in project_path: 16 | project_path = project_path.replace('\\', '/') 17 | else: 18 | pass 19 | 20 | 21 | if __name__ == '__main__': 22 | LogConfig(project_path) 23 | from bin.script.writeCase import write_case 24 | write_case(project_path + dir_manage('${data_dir}$')) 25 | args = ['-s', '-q', '--alluredir', project_path + dir_manage('${report_xml_dir}$')] 26 | pytest.main(args) 27 | cmd = 'allure generate %s -o %s -c' % (project_path + dir_manage('${report_xml_dir}$'), 28 | project_path + dir_manage('${report_html_dir}$')) 29 | os.system(cmd) 30 | 31 | --------------------------------------------------------------------------------