diff --git a/httprunner/client.py b/httprunner/client.py
index 8a420550..de060ff3 100644
--- a/httprunner/client.py
+++ b/httprunner/client.py
@@ -44,23 +44,84 @@ class HttpSession(requests.Session):
"""
self.meta_data = {
"name": "",
- "request": {
- "url": "N/A",
- "method": "N/A",
- "headers": {},
- "start_timestamp": None
- },
- "response": {
- "status_code": "N/A",
- "headers": {},
+ "data": [
+ {
+ "request": {
+ "url": "N/A",
+ "method": "N/A",
+ "headers": {}
+ },
+ "response": {
+ "status_code": "N/A",
+ "headers": {},
+ "encoding": None,
+ "content_type": ""
+ }
+ }
+ ],
+ "stat": {
"content_size": "N/A",
"response_time_ms": "N/A",
"elapsed_ms": "N/A",
- "encoding": None,
- "content_type": ""
}
}
+ def get_req_resp_record(self, resp_obj):
+ """ get request and response info from Response() object.
+ """
+ def log_print(req_resp_dict, r_type):
+ msg = "\n================== {} details ==================\n".format(r_type)
+ for key, value in req_resp_dict[r_type].items():
+ msg += "{:<16} : {}\n".format(key, repr(value))
+ logger.log_debug(msg)
+
+ req_resp_dict = {
+ "request": {},
+ "response": {}
+ }
+
+ # record actual request info
+ req_resp_dict["request"]["url"] = resp_obj.request.url
+ req_resp_dict["request"]["headers"] = dict(resp_obj.request.headers)
+
+ request_body = resp_obj.request.body
+ if request_body:
+ req_resp_dict["request"]["body"] = omit_long_data(request_body)
+
+ # log request details in debug mode
+ log_print(req_resp_dict, "request")
+
+ # record response info
+ req_resp_dict["response"]["ok"] = resp_obj.ok
+ req_resp_dict["response"]["url"] = resp_obj.url
+ req_resp_dict["response"]["status_code"] = resp_obj.status_code
+ req_resp_dict["response"]["reason"] = resp_obj.reason
+ req_resp_dict["response"]["cookies"] = resp_obj.cookies or {}
+ req_resp_dict["response"]["encoding"] = resp_obj.encoding
+ resp_headers = dict(resp_obj.headers)
+ req_resp_dict["response"]["headers"] = resp_headers
+
+ lower_resp_headers = lower_dict_keys(resp_headers)
+ content_type = lower_resp_headers.get("content-type", "")
+ req_resp_dict["response"]["content_type"] = content_type
+
+ if "image" in content_type:
+ # response is image type, record bytes content only
+ req_resp_dict["response"]["content"] = resp_obj.content
+ else:
+ try:
+ # try to record json data
+ req_resp_dict["response"]["json"] = resp_obj.json()
+ except ValueError:
+ # only record at most 512 text charactors
+ resp_text = resp_obj.text
+ req_resp_dict["response"]["text"] = omit_long_data(resp_text)
+
+ # log response details in debug mode
+ log_print(req_resp_dict, "response")
+
+ return req_resp_dict
+
def request(self, method, url, name=None, **kwargs):
"""
Constructs and sends a :py:class:`requests.Request`.
@@ -100,78 +161,42 @@ class HttpSession(requests.Session):
:param cert: (optional)
if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
"""
- def log_print(request_response):
- msg = "\n================== {} details ==================\n".format(request_response)
- for key, value in self.meta_data[request_response].items():
- msg += "{:<16} : {}\n".format(key, repr(value))
- logger.log_debug(msg)
-
# record test name
self.meta_data["name"] = name
# record original request info
- self.meta_data["request"]["method"] = method
- self.meta_data["request"]["url"] = url
- self.meta_data["request"].update(kwargs)
- self.meta_data["request"]["start_timestamp"] = time.time()
-
- request_data = self.meta_data["request"].get("data")
- if request_data:
- self.meta_data["request"]["data"] = omit_long_data(request_data)
+ self.meta_data["data"][0]["request"]["method"] = method
+ self.meta_data["data"][0]["request"]["url"] = url
+ kwargs.setdefault("timeout", 120)
+ self.meta_data["data"][0]["request"].update(kwargs)
# prepend url with hostname unless it's already an absolute URL
url = build_url(self.base_url, url)
- kwargs.setdefault("timeout", 120)
+ start_timestamp = time.time()
response = self._send_request_safe_mode(method, url, **kwargs)
-
- # record the consumed time
- self.meta_data["response"]["response_time_ms"] = \
- round((time.time() - self.meta_data["request"]["start_timestamp"]) * 1000, 2)
- self.meta_data["response"]["elapsed_ms"] = response.elapsed.microseconds / 1000.0
-
- # 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)
-
- # log request details in debug mode
- log_print("request")
-
- # record response info
- self.meta_data["response"]["ok"] = response.ok
- self.meta_data["response"]["url"] = response.url
- self.meta_data["response"]["status_code"] = response.status_code
- self.meta_data["response"]["reason"] = response.reason
- self.meta_data["response"]["cookies"] = response.cookies or {}
- self.meta_data["response"]["encoding"] = response.encoding
- resp_headers = dict(response.headers)
- self.meta_data["response"]["headers"] = resp_headers
-
- lower_resp_headers = lower_dict_keys(resp_headers)
- content_type = lower_resp_headers.get("content-type", "")
- self.meta_data["response"]["content_type"] = content_type
-
- if "image" in content_type:
- # response is image type, record bytes content only
- self.meta_data["response"]["content"] = response.content
- else:
- try:
- # try to record json data
- self.meta_data["response"]["json"] = response.json()
- except ValueError:
- # only record at most 512 text charactors
- resp_text = response.text
- self.meta_data["response"]["text"] = omit_long_data(resp_text)
+ response_time_ms = round((time.time() - start_timestamp) * 1000, 2)
# get the length of the content, but if the argument stream is set to True, we take
# the size from the content-length header, in order to not trigger fetching of the body
if kwargs.get("stream", False):
- self.meta_data["response"]["content_size"] = int(self.meta_data["response"]["headers"].get("content-length") or 0)
+ content_size = int(dict(response.headers).get("content-length") or 0)
else:
- self.meta_data["response"]["content_size"] = len(response.content or "")
+ content_size = len(response.content or "")
- # log response details in debug mode
- log_print("response")
+ # record the consumed time
+ self.meta_data["stat"] = {
+ "response_time_ms": response_time_ms,
+ "elapsed_ms": response.elapsed.microseconds / 1000.0,
+ "content_size": content_size
+ }
+
+ # record request and response histories, include 30X redirection
+ response_list = response.history + [response]
+ self.meta_data["data"] = [
+ self.get_req_resp_record(resp_obj)
+ for resp_obj in response_list
+ ]
try:
response.raise_for_status()
@@ -180,9 +205,9 @@ class HttpSession(requests.Session):
else:
logger.log_info(
"""status_code: {}, response_time(ms): {} ms, response_length: {} bytes\n""".format(
- self.meta_data["response"]["status_code"],
- self.meta_data["response"]["response_time_ms"],
- self.meta_data["response"]["content_size"]
+ response.status_code,
+ response_time_ms,
+ content_size
)
)
diff --git a/httprunner/report.py b/httprunner/report.py
index 8c12f8ad..bf448e5a 100644
--- a/httprunner/report.py
+++ b/httprunner/report.py
@@ -117,7 +117,6 @@ def __stringify_request(request_data):
"Content-Type": "application/json",
"Content-Length": "52"
},
- "start_timestamp": 1543299567.6505039,
"json": {
"sign": "cb9d60acd09080ea66c8e63a1c78c6459ea00168"
},
@@ -161,9 +160,6 @@ def __stringify_response(response_data):
"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,
@@ -245,7 +241,7 @@ def __get_total_response_time(meta_datas_expanded):
try:
response_time = 0
for meta_data in meta_datas_expanded:
- response_time += meta_data["response"]["response_time_ms"]
+ response_time += meta_data["stat"]["response_time_ms"]
return "{:.2f}".format(response_time)
@@ -260,8 +256,10 @@ def __stringify_meta_datas(meta_datas):
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"])
+ data_list = meta_datas["data"]
+ for data in data_list:
+ __stringify_request(data["request"])
+ __stringify_response(data["response"])
def render_html_report(summary, report_template=None, report_dir=None):
diff --git a/httprunner/runner.py b/httprunner/runner.py
index c815efda..3b5ca787 100644
--- a/httprunner/runner.py
+++ b/httprunner/runner.py
@@ -256,6 +256,8 @@ class Runner(object):
# log request
err_msg += "====== request details ======\n"
+ err_msg += "url: {}\n".format(url)
+ err_msg += "method: {}\n".format(method)
err_msg += "headers: {}\n".format(parsed_test_request.pop("headers", {}))
for k, v in parsed_test_request.items():
v = utils.omit_long_data(v)
diff --git a/httprunner/templates/report_template.html b/httprunner/templates/report_template.html
index 6cffaa49..8e4414d1 100644
--- a/httprunner/templates/report_template.html
+++ b/httprunner/templates/report_template.html
@@ -132,6 +132,9 @@
overflow: auto;
text-align: left;
}
+ .popup .separator {
+ color:royalblue
+ }
@media screen and (max-width: 700px) {
.box {
@@ -224,15 +227,22 @@
Name: {{ meta_data.name }}
+
+ {% for req_resp in meta_data.data %}
+
+ {% if loop.index > 1 %}
+
==================================== redirect to ====================================
+ {% endif %}
+
Request:
- {% for key, value in meta_data.request.items() %}
+ {% for key, value in req_resp.request.items() %}
| {{key}} |
{% if key == "headers" %}
- {% for header_key, header_value in meta_data.request.headers.items() %}
+ {% for header_key, header_value in req_resp.request.headers.items() %}
{{ header_key }}: {{ header_value }}
@@ -249,27 +259,24 @@
Response:
- {% for key, value in meta_data.response.items() %}
- {% if key in ["elapsed_ms", "response_time_ms", "content_size", "content_type"] %}
- {% continue %}
- {% endif %}
+ {% for key, value in req_resp.response.items() %}
| {{key}} |
{% if key == "headers" %}
- {% for header_key, header_value in meta_data.response.headers.items() %}
+ {% for header_key, header_value in req_resp.response.headers.items() %}
{{ header_key }}: {{ header_value }}
{% endfor %}
{% elif key == "content" %}
- {% if "image" in meta_data.response.content_type %}
-
+ {% if "image" in req_resp.response.content_type %}
+
{% else %}
{{ value }}
{% endif %}
{% elif key == "text" %}
- {{ meta_data.response.text | e }}
+ {{ req_resp.response.text | e }}
{% else %}
{{ value }}
{% endif %}
@@ -278,6 +285,7 @@
{% endfor %}
|
+ {% endfor %}
Validators:
@@ -312,15 +320,15 @@
| content_size(bytes) |
- {{ meta_data.response.content_size }} |
+ {{ meta_data.stat.content_size }} |
| response_time(ms) |
- {{ meta_data.response.response_time_ms }} |
+ {{ meta_data.stat.response_time_ms }} |
| elapsed(ms) |
- {{ meta_data.response.elapsed_ms }} |
+ {{ meta_data.stat.elapsed_ms }} |
diff --git a/tests/httpbin/api/302_redirect.yml b/tests/httpbin/api/302_redirect.yml
new file mode 100644
index 00000000..e61eb3b3
--- /dev/null
+++ b/tests/httpbin/api/302_redirect.yml
@@ -0,0 +1,10 @@
+
+name: 302 redirect
+request:
+ url: https://httpbin.org/redirect-to?url=https%3A%2F%2Fdebugtalk.com&status_code=302
+ # params:
+ # url: https%3A%2F%2Fdebugtalk.com
+ # status_code: 302
+ method: GET
+validate:
+ - eq: ["status_code", 200]
diff --git a/tests/test_api.py b/tests/test_api.py
index 4de36228..c5726baa 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -143,7 +143,10 @@ class TestHttpRunner(ApiServerUnittest):
summary = self.runner.summary
self.assertTrue(summary["success"])
self.assertEqual(summary["stat"]["testsRun"], 1)
- self.assertEqual(summary["details"][0]["records"][0]["meta_datas"]["response"]["json"]["data"], "abc")
+ self.assertEqual(
+ summary["details"][0]["records"][0]["meta_datas"]["data"][0]["response"]["json"]["data"],
+ "abc"
+ )
def test_html_report_repsonse_image(self):
report_save_dir = os.path.join(os.getcwd(), 'reports', "demo")
@@ -286,6 +289,19 @@ class TestHttpRunner(ApiServerUnittest):
self.assertEqual(summary["stat"]["testsRun"], 1)
self.assertEqual(summary["stat"]["successes"], 1)
+ def test_request_302_logs(self):
+ path = "tests/httpbin/api/302_redirect.yml"
+ self.runner.run(path)
+ summary = self.runner.summary
+ self.assertTrue(summary["success"])
+ self.assertEqual(summary["stat"]["testsRun"], 1)
+ self.assertEqual(summary["stat"]["successes"], 1)
+
+ req_resp_data = summary["details"][0]["records"][0]["meta_datas"]["data"]
+ self.assertEqual(len(req_resp_data), 2)
+ self.assertEqual(req_resp_data[0]["response"]["status_code"], 302)
+ self.assertEqual(req_resp_data[1]["response"]["status_code"], 200)
+
def test_run_testcase_hardcode(self):
for testcase_file_path in self.testcase_file_path_list:
self.runner.run(testcase_file_path)
|