diff --git a/httprunner/api.py b/httprunner/api.py
index daef4269..a9048750 100644
--- a/httprunner/api.py
+++ b/httprunner/api.py
@@ -50,7 +50,7 @@ class HttpRunner(object):
except exceptions.MyBaseFailure as ex:
self.fail(str(ex))
finally:
- self.meta_data = test_runner.get_test_data()
+ self.meta_datas = test_runner.meta_datas
if "config" in test_dict:
# run nested testcase
diff --git a/httprunner/client.py b/httprunner/client.py
index e1798db9..d540fcb1 100644
--- a/httprunner/client.py
+++ b/httprunner/client.py
@@ -125,7 +125,6 @@ class HttpSession(requests.Session):
# record actual request info
self.meta_data["request"]["url"] = (response.history and response.history[0] or response).request.url
self.meta_data["request"]["headers"] = dict(response.request.headers)
- self.meta_data["request"]["body"] = response.request.body
# log request details in debug mode
log_print("request")
diff --git a/httprunner/report.py b/httprunner/report.py
index 15b4d12c..65dbd183 100644
--- a/httprunner/report.py
+++ b/httprunner/report.py
@@ -91,9 +91,169 @@ def stringify_summary(summary):
suite_summary["name"] = "test suite {}".format(index)
for record in suite_summary.get("records"):
- meta_data = record['meta_data']
- stringify_data(meta_data, 'request')
- stringify_data(meta_data, 'response')
+ meta_datas = record['meta_datas']
+ __stringify_meta_datas(meta_datas)
+ meta_datas_expanded = []
+ __expand_meta_datas(meta_datas, meta_datas_expanded)
+ record["meta_datas_expanded"] = meta_datas_expanded
+ record["response_time"] = __get_total_response_time(meta_datas_expanded)
+
+
+def __stringify_request(request_data):
+ """ stringfy HTTP request data
+
+ Args:
+ request_data (dict): HTTP request data in dict.
+
+ {
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.20.0",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept": "*/*",
+ "Connection": "keep-alive",
+ "user_agent": "iOS/10.3",
+ "device_sn": "TESTCASE_CREATE_XXX",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json",
+ "Content-Length": "52"
+ },
+ "start_timestamp": 1543299567.6505039,
+ "json": {
+ "sign": "cb9d60acd09080ea66c8e63a1c78c6459ea00168"
+ },
+ "verify": false
+ }
+
+ """
+ for key, value in request_data.items():
+
+ if isinstance(value, list):
+ value = json.dumps(value, indent=2, ensure_ascii=False)
+
+ elif isinstance(value, bytes):
+ try:
+ encoding = "utf-8"
+ value = escape(value.decode(encoding))
+ except UnicodeDecodeError:
+ pass
+
+ elif not isinstance(value, (basestring, numeric_types, Iterable)):
+ # class instance, e.g. MultipartEncoder()
+ value = repr(value)
+
+ request_data[key] = value
+
+
+def __stringify_response(response_data):
+ """ stringfy HTTP response data
+
+ Args:
+ response_data (dict):
+
+ {
+ "status_code": 404,
+ "headers": {
+ "Content-Type": "application/json",
+ "Content-Length": "30",
+ "Server": "Werkzeug/0.14.1 Python/3.7.0",
+ "Date": "Tue, 27 Nov 2018 06:19:27 GMT"
+ },
+ "content_size": 30,
+ "response_time_ms": 3.63,
+ "elapsed_ms": 2.197,
+ "encoding": "None",
+ "content_type": "application/json",
+ "ok": false,
+ "url": "http://127.0.0.1:5000/api/users/9001",
+ "reason": "NOT FOUND",
+ "cookies": {},
+ "json": {
+ "success": false,
+ "data": {}
+ }
+ }
+
+ """
+ for key, value in response_data.items():
+
+ if isinstance(value, list):
+ value = json.dumps(value, indent=2, ensure_ascii=False)
+
+ elif isinstance(value, bytes):
+ try:
+ encoding = response_data.get("encoding")
+ if not encoding or encoding == "None":
+ encoding = "utf-8"
+
+ if key == "content" and "image" in response_data["content_type"]:
+ # display image
+ value = "data:{};base64,{}".format(
+ response_data["content_type"],
+ b64encode(value).decode(encoding)
+ )
+ else:
+ value = escape(value.decode(encoding))
+ except UnicodeDecodeError:
+ pass
+
+ elif not isinstance(value, (basestring, numeric_types, Iterable)):
+ # class instance, e.g. MultipartEncoder()
+ value = repr(value)
+
+ response_data[key] = value
+
+
+def __expand_meta_datas(meta_datas, meta_datas_expanded):
+ """ expand meta_datas to one level
+
+ Args:
+ meta_datas (dict/list): maybe in nested format
+
+ Returns:
+ list: expanded list in one level
+
+ Examples:
+ >>> meta_datas = [
+ [
+ dict1,
+ dict2
+ ],
+ dict3
+ ]
+ >>> meta_datas_expanded = []
+ >>> __expand_meta_datas(meta_datas, meta_datas_expanded)
+ >>> print(meta_datas_expanded)
+ [dict1, dict2, dict3]
+
+ """
+ if isinstance(meta_datas, dict):
+ meta_datas_expanded.append(meta_datas)
+ elif isinstance(meta_datas, list):
+ for meta_data in meta_datas:
+ __expand_meta_datas(meta_data, meta_datas_expanded)
+
+
+def __get_total_response_time(meta_datas_expanded):
+ """ caculate total response time of all meta_datas
+ """
+ response_time = 0
+ for meta_data in meta_datas_expanded:
+ response_time += meta_data["response"]["response_time_ms"]
+
+ return "{:.2f}".format(response_time)
+
+
+def __stringify_meta_datas(meta_datas):
+
+ if isinstance(meta_datas, list):
+ for _meta_data in meta_datas:
+ __stringify_meta_datas(_meta_data)
+ elif isinstance(meta_datas, dict):
+ __stringify_request(meta_datas["request"])
+ __stringify_response(meta_datas["response"])
def render_html_report(summary, report_template=None, report_dir=None):
@@ -136,46 +296,6 @@ def render_html_report(summary, report_template=None, report_dir=None):
return report_path
-def stringify_data(meta_data, request_or_response):
- """
- meta_data = {
- "request": {},
- "response": {}
- }
- """
- headers = meta_data[request_or_response]["headers"]
- request_or_response_dict = meta_data[request_or_response]
-
- for key, value in request_or_response_dict.items():
-
- if isinstance(value, list):
- value = json.dumps(value, indent=2, ensure_ascii=False)
-
- elif isinstance(value, bytes):
- try:
- encoding = meta_data["response"].get("encoding")
- if not encoding or encoding == "None":
- encoding = "utf-8"
-
- if request_or_response == "response" and key == "content" \
- and "image" in meta_data["response"]["content_type"]:
- # display image
- value = "data:{};base64,{}".format(
- meta_data["response"]["content_type"],
- b64encode(value).decode(encoding)
- )
- else:
- value = escape(value.decode(encoding))
- except UnicodeDecodeError:
- pass
-
- elif not isinstance(value, (basestring, numeric_types, Iterable)):
- # class instance, e.g. MultipartEncoder()
- value = repr(value)
-
- meta_data[request_or_response][key] = value
-
-
class HtmlTestResult(unittest.TextTestResult):
""" A html result class that can generate formatted html results.
Used by TextTestRunner.
@@ -189,7 +309,7 @@ class HtmlTestResult(unittest.TextTestResult):
'name': test.shortDescription(),
'status': status,
'attachment': attachment,
- "meta_data": test.meta_data
+ "meta_datas": test.meta_datas
}
self.records.append(data)
diff --git a/httprunner/runner.py b/httprunner/runner.py
index 6ea969c3..9fef5302 100644
--- a/httprunner/runner.py
+++ b/httprunner/runner.py
@@ -68,7 +68,7 @@ class Runner(object):
if self.testcase_teardown_hooks:
self.do_hook_actions(self.testcase_teardown_hooks, "teardown")
- def clear_test_data(self):
+ def __clear_test_data(self):
""" clear request and response data
"""
if not isinstance(self.http_client_session, HttpSession):
@@ -77,11 +77,11 @@ class Runner(object):
self.evaluated_validators = []
self.http_client_session.init_meta_data()
- def get_test_data(self):
+ def __get_test_data(self):
""" get request/response data and validate results
"""
if not isinstance(self.http_client_session, HttpSession):
- raise exceptions.FunctionNotFound("get_test_data is only valid in HttpSession!")
+ return
meta_data = self.http_client_session.meta_data
meta_data["validators"] = self.evaluated_validators
@@ -167,7 +167,7 @@ class Runner(object):
"authorization": "$authorization",
"random": "$random"
},
- "body": '{"name": "user", "password": "123456"}'
+ "json": {"name": "user", "password": "123456"}
},
"extract": [], # optional
"validate": [], # optional
@@ -182,7 +182,7 @@ class Runner(object):
"""
# clear meta data first to ensure independence for each test
- self.clear_test_data()
+ self.__clear_test_data()
# check skip
self._handle_skip_feature(test_dict)
@@ -266,14 +266,18 @@ class Runner(object):
def _run_testcase(self, testcase_dict):
""" run single testcase.
"""
+ meta_data_list = []
config = testcase_dict.get("config", {})
test_runner = Runner(config, self.functions, self.http_client_session)
tests = testcase_dict.get("tests", [])
for index, test_dict in enumerate(tests):
test_runner.run_test(test_dict)
+ meta_datas = test_runner.meta_datas
+ meta_data_list.append(meta_datas)
self.session_context.update_seesion_variables(test_runner.extract_sessions())
+ return meta_data_list
def run_test(self, test_dict):
""" run single teststep of testcase.
@@ -308,12 +312,14 @@ class Runner(object):
}
"""
+ self.meta_datas = None
if "config" in test_dict:
# nested testcase
- self._run_testcase(test_dict)
+ self.meta_datas = self._run_testcase(test_dict)
else:
# api
self._run_test(test_dict)
+ self.meta_datas = self.__get_test_data()
def extract_sessions(self):
"""
diff --git a/httprunner/templates/report_template.html b/httprunner/templates/report_template.html
index 8b68a425..9b53e5fd 100644
--- a/httprunner/templates/report_template.html
+++ b/httprunner/templates/report_template.html
@@ -233,28 +233,31 @@
{% for record in test_suite_summary.records %}
{% set record_index = "{}_{}".format(suite_index, loop.index) %}
+ {% set record_meta_datas = record.meta_datas_expanded %}
| {{record.status}}
| {{record.name}} |
- {{ record.meta_data.response.response_time_ms }} ms |
+ {{ record.response_time }} ms |
- log
- |