mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 06:23:52 +08:00
Merge pull request #902 from httprunner/v3
## 3.0.4 (2020-05-19) **Added** - feat: make testsuite and run testsuite - feat: testcase/testsuite config support getting variables by function - feat: har2case with request cookies - feat: log request/response headers and body with indent **Fixed** - fix: extract response cookies - fix: handle errors when no valid testcases generated **Changed** - change: har2case do not ignore request headers, except for header startswith :
This commit is contained in:
@@ -1,5 +1,23 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
|
## 3.0.4 (2020-05-19)
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
- feat: make testsuite and run testsuite
|
||||||
|
- feat: testcase/testsuite config support getting variables by function
|
||||||
|
- feat: har2case with request cookies
|
||||||
|
- feat: log request/response headers and body with indent
|
||||||
|
|
||||||
|
**Fixed**
|
||||||
|
|
||||||
|
- fix: extract response cookies
|
||||||
|
- fix: handle errors when no valid testcases generated
|
||||||
|
|
||||||
|
**Changed**
|
||||||
|
|
||||||
|
- change: har2case do not ignore request headers, except for header startswith :
|
||||||
|
|
||||||
## 3.0.3 (2020-05-17)
|
## 3.0.3 (2020-05-17)
|
||||||
|
|
||||||
**Fixed**
|
**Fixed**
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ teststeps:
|
|||||||
method: GET
|
method: GET
|
||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 200]
|
- eq: ["status_code", 200]
|
||||||
# - startswith: [body.user-agent, "python-requests"]
|
- startswith: [body."user-agent", "python-requests"]
|
||||||
|
|
||||||
-
|
-
|
||||||
name: get without params
|
name: get without params
|
||||||
@@ -58,7 +58,7 @@ teststeps:
|
|||||||
method: GET
|
method: GET
|
||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 200]
|
- eq: ["status_code", 200]
|
||||||
# - eq: [cookies.name, "value"]
|
- eq: [body.cookies.name, "value"]
|
||||||
|
|
||||||
-
|
-
|
||||||
name: extract cookie
|
name: extract cookie
|
||||||
@@ -67,7 +67,7 @@ teststeps:
|
|||||||
method: GET
|
method: GET
|
||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 200]
|
- eq: ["status_code", 200]
|
||||||
# - eq: [cookies.name, "value"]
|
- eq: [body.cookies.name, "value"]
|
||||||
|
|
||||||
-
|
-
|
||||||
name: post data
|
name: post data
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ class TestCaseBasic(HttpRunner):
|
|||||||
**{
|
**{
|
||||||
"name": "user-agent",
|
"name": "user-agent",
|
||||||
"request": {"url": "/user-agent", "method": "GET"},
|
"request": {"url": "/user-agent", "method": "GET"},
|
||||||
"validate": [{"eq": ["status_code", 200]}],
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"startswith": ['body."user-agent"', "python-requests"]},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
TStep(
|
TStep(
|
||||||
@@ -61,14 +64,20 @@ class TestCaseBasic(HttpRunner):
|
|||||||
**{
|
**{
|
||||||
"name": "set cookie",
|
"name": "set cookie",
|
||||||
"request": {"url": "/cookies/set?name=value", "method": "GET"},
|
"request": {"url": "/cookies/set?name=value", "method": "GET"},
|
||||||
"validate": [{"eq": ["status_code", 200]}],
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.cookies.name", "value"]},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
TStep(
|
TStep(
|
||||||
**{
|
**{
|
||||||
"name": "extract cookie",
|
"name": "extract cookie",
|
||||||
"request": {"url": "/cookies", "method": "GET"},
|
"request": {"url": "/cookies", "method": "GET"},
|
||||||
"validate": [{"eq": ["status_code", 200]}],
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.cookies.name", "value"]},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
TStep(
|
TStep(
|
||||||
|
|||||||
@@ -32,5 +32,6 @@ teststeps:
|
|||||||
- ${alter_response($response)}
|
- ${alter_response($response)}
|
||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 200]
|
- eq: ["status_code", 200]
|
||||||
# - eq: ["headers.content-type", "html/text"]
|
# TODO: implement hooks
|
||||||
|
# - eq: [body.headers."Content-Type", "html/text"]
|
||||||
- eq: [body.headers.Host, "httpbin.org"]
|
- eq: [body.headers.Host, "httpbin.org"]
|
||||||
|
|||||||
@@ -7,3 +7,9 @@ def get_httprunner_version():
|
|||||||
|
|
||||||
def sum_two(m, n):
|
def sum_two(m, n):
|
||||||
return m + n
|
return m + n
|
||||||
|
|
||||||
|
|
||||||
|
def get_variables():
|
||||||
|
return {
|
||||||
|
"foo1": "session_bar1"
|
||||||
|
}
|
||||||
|
|||||||
15
examples/postman_echo/request_methods/demo_testsuite.yml
Normal file
15
examples/postman_echo/request_methods/demo_testsuite.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
config:
|
||||||
|
name: "demo testsuite"
|
||||||
|
variables: ${get_variables()}
|
||||||
|
|
||||||
|
testcases:
|
||||||
|
-
|
||||||
|
name: request with functions
|
||||||
|
testcase: request_methods/request_with_functions.yml
|
||||||
|
variables:
|
||||||
|
var1: testsuite_val1
|
||||||
|
-
|
||||||
|
name: request with referenced testcase
|
||||||
|
testcase: request_methods/request_with_testcase_reference.yml
|
||||||
|
variables:
|
||||||
|
var2: testsuite_val2
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
|
# FROM: examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions.yml
|
||||||
|
from httprunner import HttpRunner, TConfig, TStep
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseRequestWithFunctions(HttpRunner):
|
||||||
|
config = TConfig(
|
||||||
|
**{
|
||||||
|
"name": "request with functions",
|
||||||
|
"variables": {"foo1": "session_bar1", "var1": "testsuite_val1"},
|
||||||
|
"base_url": "https://postman-echo.com",
|
||||||
|
"verify": False,
|
||||||
|
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "get with params",
|
||||||
|
"variables": {
|
||||||
|
"foo1": "bar1",
|
||||||
|
"foo2": "session_bar2",
|
||||||
|
"sum_v": "${sum_two(1, 2)}",
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/get",
|
||||||
|
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
|
||||||
|
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
|
||||||
|
},
|
||||||
|
"extract": {"session_foo2": "body.args.foo2"},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.args.foo1", "session_bar1"]},
|
||||||
|
{"eq": ["body.args.sum_v", 3]},
|
||||||
|
{"eq": ["body.args.foo2", "session_bar2"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "post raw text",
|
||||||
|
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"headers": {
|
||||||
|
"User-Agent": "HttpRunner/${get_httprunner_version()}",
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{
|
||||||
|
"eq": [
|
||||||
|
"body.data",
|
||||||
|
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "post form data",
|
||||||
|
"variables": {"foo1": "bar1", "foo2": "bar2"},
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"headers": {
|
||||||
|
"User-Agent": "HttpRunner/${get_httprunner_version()}",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
"data": "foo1=$foo1&foo2=$foo2",
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.form.foo1", "session_bar1"]},
|
||||||
|
{"eq": ["body.form.foo2", "bar2"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseRequestWithFunctions().test_start()
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
|
# FROM: examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference.yml
|
||||||
|
from httprunner import HttpRunner, TConfig, TStep
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseRequestWithTestcaseReference(HttpRunner):
|
||||||
|
config = TConfig(
|
||||||
|
**{
|
||||||
|
"name": "request with referenced testcase",
|
||||||
|
"variables": {"foo1": "session_bar1", "var2": "testsuite_val2"},
|
||||||
|
"base_url": "https://postman-echo.com",
|
||||||
|
"verify": False,
|
||||||
|
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "request with functions",
|
||||||
|
"variables": {"foo1": "override_bar1"},
|
||||||
|
"testcase": "request_methods/request_with_functions.yml",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseRequestWithTestcaseReference().test_start()
|
||||||
@@ -10,6 +10,7 @@ class TestCaseHardcode(HttpRunner):
|
|||||||
"base_url": "https://postman-echo.com",
|
"base_url": "https://postman-echo.com",
|
||||||
"verify": False,
|
"verify": False,
|
||||||
"path": "examples/postman_echo/request_methods/hardcode_test.py",
|
"path": "examples/postman_echo/request_methods/hardcode_test.py",
|
||||||
|
"variables": {},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ config:
|
|||||||
|
|
||||||
teststeps:
|
teststeps:
|
||||||
-
|
-
|
||||||
name: request with variables
|
name: request with functions
|
||||||
variables:
|
variables:
|
||||||
foo1: override_bar1
|
foo1: override_bar1
|
||||||
testcase: request_methods/request_with_variables.yml
|
testcase: request_methods/request_with_functions.yml
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
|
|||||||
teststeps = [
|
teststeps = [
|
||||||
TStep(
|
TStep(
|
||||||
**{
|
**{
|
||||||
"name": "request with variables",
|
"name": "request with functions",
|
||||||
"variables": {"foo1": "override_bar1"},
|
"variables": {"foo1": "override_bar1"},
|
||||||
"testcase": "request_methods/request_with_variables.yml",
|
"testcase": "request_methods/request_with_functions.yml",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
config:
|
config:
|
||||||
name: "request methods testcase with variables"
|
name: "request methods testcase with variables"
|
||||||
variables:
|
variables: ${get_variables()}
|
||||||
foo1: session_bar1
|
|
||||||
base_url: "https://postman-echo.com"
|
base_url: "https://postman-echo.com"
|
||||||
verify: False
|
verify: False
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "3.0.3"
|
__version__ = "3.0.4"
|
||||||
__description__ = "One-stop solution for HTTP(S) testing."
|
__description__ = "One-stop solution for HTTP(S) testing."
|
||||||
|
|
||||||
from httprunner.runner import HttpRunner
|
from httprunner.runner import HttpRunner
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import __description__, __version__, exceptions
|
from httprunner import __description__, __version__, exceptions
|
||||||
from httprunner.ext.har2case import init_har2case_parser, main_har2case
|
from httprunner.ext.har2case import init_har2case_parser, main_har2case
|
||||||
@@ -19,25 +20,30 @@ def init_parser_run(subparsers):
|
|||||||
|
|
||||||
def main_run(extra_args):
|
def main_run(extra_args):
|
||||||
tests_path_list = []
|
tests_path_list = []
|
||||||
for index, item in enumerate(extra_args):
|
extra_args_new = []
|
||||||
|
for item in extra_args:
|
||||||
if not os.path.exists(item):
|
if not os.path.exists(item):
|
||||||
# item is not file/folder path
|
# item is not file/folder path
|
||||||
continue
|
extra_args_new.append(item)
|
||||||
elif os.path.isfile(item):
|
else:
|
||||||
# replace YAML/JSON file path with generated python file
|
# item is file/folder path
|
||||||
extra_args[index], _ = convert_testcase_path(item)
|
tests_path_list.append(item)
|
||||||
|
|
||||||
tests_path_list.append(item)
|
|
||||||
|
|
||||||
if len(tests_path_list) == 0:
|
if len(tests_path_list) == 0:
|
||||||
# has not specified any testcase path
|
# has not specified any testcase path
|
||||||
raise exceptions.ParamsError("Missed testcase path")
|
logger.error(f"No valid testcase path in cli arguments: {extra_args}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
main_make(tests_path_list)
|
testcase_path_list = main_make(tests_path_list)
|
||||||
|
if not testcase_path_list:
|
||||||
|
logger.error("No valid testcases found, exit 1.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if "-s" not in extra_args:
|
extra_args_new.extend(testcase_path_list)
|
||||||
extra_args.insert(0, "-s")
|
if "-s" not in extra_args_new:
|
||||||
pytest.main(extra_args)
|
extra_args_new.insert(0, "-s")
|
||||||
|
|
||||||
|
pytest.main(extra_args_new)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -32,12 +33,19 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
|||||||
def log_print(req_or_resp, r_type):
|
def log_print(req_or_resp, r_type):
|
||||||
msg = f"\n================== {r_type} details ==================\n"
|
msg = f"\n================== {r_type} details ==================\n"
|
||||||
for key, value in req_or_resp.dict().items():
|
for key, value in req_or_resp.dict().items():
|
||||||
msg += "{:<16} : {}\n".format(key, repr(value))
|
if isinstance(value, dict):
|
||||||
|
value = json.dumps(value, indent=4)
|
||||||
|
|
||||||
|
msg += "{:<8} : {}\n".format(key, value)
|
||||||
logger.debug(msg)
|
logger.debug(msg)
|
||||||
|
|
||||||
# record actual request info
|
# record actual request info
|
||||||
request_headers = dict(resp_obj.request.headers)
|
request_headers = dict(resp_obj.request.headers)
|
||||||
request_body = resp_obj.request.body
|
request_body = resp_obj.request.body
|
||||||
|
try:
|
||||||
|
request_body = json.loads(request_body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
if request_body:
|
if request_body:
|
||||||
request_content_type = lower_dict_keys(request_headers).get("content-type")
|
request_content_type = lower_dict_keys(request_headers).get("content-type")
|
||||||
@@ -195,10 +203,6 @@ class HttpSession(requests.Session):
|
|||||||
Safe mode has been removed from requests 1.x.
|
Safe mode has been removed from requests 1.x.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
msg = "processed request:\n"
|
|
||||||
msg += f"> {method} {url}\n"
|
|
||||||
msg += f"> kwargs: {kwargs}"
|
|
||||||
logger.debug(msg)
|
|
||||||
return requests.Session.request(self, method, url, **kwargs)
|
return requests.Session.request(self, method, url, **kwargs)
|
||||||
except (MissingSchema, InvalidSchema, InvalidURL):
|
except (MissingSchema, InvalidSchema, InvalidURL):
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ class TestCaseFormatError(MyBaseError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuiteFormatError(MyBaseError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ParamsError(MyBaseError):
|
class ParamsError(MyBaseError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -14,24 +14,6 @@ except ImportError:
|
|||||||
JSONDecodeError = ValueError
|
JSONDecodeError = ValueError
|
||||||
|
|
||||||
|
|
||||||
IGNORE_REQUEST_HEADERS = [
|
|
||||||
"host",
|
|
||||||
"accept",
|
|
||||||
"content-length",
|
|
||||||
"connection",
|
|
||||||
"accept-encoding",
|
|
||||||
"accept-language",
|
|
||||||
"origin",
|
|
||||||
"cache-control",
|
|
||||||
"pragma",
|
|
||||||
"upgrade-insecure-requests",
|
|
||||||
":authority",
|
|
||||||
":method",
|
|
||||||
":scheme",
|
|
||||||
":path",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class HarParser(object):
|
class HarParser(object):
|
||||||
def __init__(self, har_file_path, filter_str=None, exclude_str=None):
|
def __init__(self, har_file_path, filter_str=None, exclude_str=None):
|
||||||
self.har_file_path = har_file_path
|
self.har_file_path = har_file_path
|
||||||
@@ -93,6 +75,14 @@ class HarParser(object):
|
|||||||
|
|
||||||
teststep_dict["request"]["method"] = method
|
teststep_dict["request"]["method"] = method
|
||||||
|
|
||||||
|
def __make_request_cookies(self, teststep_dict, entry_json):
|
||||||
|
cookies = {}
|
||||||
|
for cookie in entry_json["request"].get("cookies", []):
|
||||||
|
cookies[cookie["name"]] = cookie["value"]
|
||||||
|
|
||||||
|
if cookies:
|
||||||
|
teststep_dict["request"]["cookies"] = cookies
|
||||||
|
|
||||||
def __make_request_headers(self, teststep_dict, entry_json):
|
def __make_request_headers(self, teststep_dict, entry_json):
|
||||||
""" parse HAR entry request headers, and make teststep headers.
|
""" parse HAR entry request headers, and make teststep headers.
|
||||||
header in IGNORE_REQUEST_HEADERS will be ignored.
|
header in IGNORE_REQUEST_HEADERS will be ignored.
|
||||||
@@ -119,7 +109,7 @@ class HarParser(object):
|
|||||||
"""
|
"""
|
||||||
teststep_headers = {}
|
teststep_headers = {}
|
||||||
for header in entry_json["request"].get("headers", []):
|
for header in entry_json["request"].get("headers", []):
|
||||||
if header["name"].lower() in IGNORE_REQUEST_HEADERS:
|
if header["name"] == "cookie" or header["name"].startswith(":"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
teststep_headers[header["name"]] = header["value"]
|
teststep_headers[header["name"]] = header["value"]
|
||||||
@@ -288,6 +278,7 @@ class HarParser(object):
|
|||||||
|
|
||||||
self.__make_request_url(teststep_dict, entry_json)
|
self.__make_request_url(teststep_dict, entry_json)
|
||||||
self.__make_request_method(teststep_dict, entry_json)
|
self.__make_request_method(teststep_dict, entry_json)
|
||||||
|
self.__make_request_cookies(teststep_dict, entry_json)
|
||||||
self.__make_request_headers(teststep_dict, entry_json)
|
self.__make_request_headers(teststep_dict, entry_json)
|
||||||
self._make_request_data(teststep_dict, entry_json)
|
self._make_request_data(teststep_dict, entry_json)
|
||||||
self._make_validate(teststep_dict, entry_json)
|
self._make_validate(teststep_dict, entry_json)
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Union, Text, List, Tuple
|
from typing import Union, Text, List, Tuple, Dict
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import exceptions
|
from httprunner import exceptions
|
||||||
from httprunner.exceptions import TestCaseFormatError
|
from httprunner.loader import (
|
||||||
from httprunner.loader import load_testcase_file, load_folder_files
|
load_folder_files,
|
||||||
|
load_test_file,
|
||||||
|
load_testcase,
|
||||||
|
load_testsuite,
|
||||||
|
load_project_meta,
|
||||||
|
)
|
||||||
|
from httprunner.parser import parse_data
|
||||||
|
|
||||||
__TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
__TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
# FROM: {{ testcase_path }}
|
# FROM: {{ testcase_path }}
|
||||||
@@ -39,7 +45,9 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
|
|||||||
|
|
||||||
file_suffix = file_suffix.lower()
|
file_suffix = file_suffix.lower()
|
||||||
if file_suffix not in [".json", ".yml", ".yaml"]:
|
if file_suffix not in [".json", ".yml", ".yaml"]:
|
||||||
raise exceptions.ParamsError("")
|
raise exceptions.ParamsError(
|
||||||
|
"testcase file should have .yaml/.yml/.json suffix"
|
||||||
|
)
|
||||||
|
|
||||||
file_name = raw_file_name.replace(" ", "_").replace(".", "_").replace("-", "_")
|
file_name = raw_file_name.replace(" ", "_").replace(".", "_").replace("-", "_")
|
||||||
testcase_dir = os.path.dirname(testcase_path)
|
testcase_dir = os.path.dirname(testcase_path)
|
||||||
@@ -51,18 +59,40 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
|
|||||||
return testcase_python_path, name_in_title_case
|
return testcase_python_path, name_in_title_case
|
||||||
|
|
||||||
|
|
||||||
def make_testcase(testcase_path: str) -> Union[str, None]:
|
def format_pytest_with_black(python_paths: List[Text]):
|
||||||
logger.info(f"start to make testcase: {testcase_path}")
|
logger.info("format pytest cases with black ...")
|
||||||
try:
|
try:
|
||||||
testcase, _ = load_testcase_file(testcase_path)
|
subprocess.run(["black", *python_paths])
|
||||||
except TestCaseFormatError:
|
except subprocess.CalledProcessError as ex:
|
||||||
return None
|
logger.error(ex)
|
||||||
|
|
||||||
|
|
||||||
|
def make_testcase(testcase: Dict) -> Union[str, None]:
|
||||||
|
"""convert valid testcase dict to pytest file path"""
|
||||||
|
try:
|
||||||
|
# validate testcase format
|
||||||
|
load_testcase(testcase)
|
||||||
|
except exceptions.TestCaseFormatError as ex:
|
||||||
|
logger.error(f"TestCaseFormatError: {ex}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
testcase_path = testcase["config"]["path"]
|
||||||
|
logger.info(f"start to make testcase: {testcase_path}")
|
||||||
|
|
||||||
template = jinja2.Template(__TMPL__)
|
template = jinja2.Template(__TMPL__)
|
||||||
|
|
||||||
testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path)
|
testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path)
|
||||||
|
|
||||||
config = testcase["config"]
|
config = testcase["config"]
|
||||||
|
|
||||||
|
config.setdefault("variables", {})
|
||||||
|
if isinstance(config["variables"], Text):
|
||||||
|
# get variables by function, e.g. ${get_variables()}
|
||||||
|
project_meta = load_project_meta(testcase_path)
|
||||||
|
config["variables"] = parse_data(
|
||||||
|
config["variables"], {}, project_meta.functions
|
||||||
|
)
|
||||||
|
|
||||||
config["path"] = testcase_python_path
|
config["path"] = testcase_python_path
|
||||||
data = {
|
data = {
|
||||||
"testcase_path": testcase_path,
|
"testcase_path": testcase_path,
|
||||||
@@ -79,42 +109,119 @@ def make_testcase(testcase_path: str) -> Union[str, None]:
|
|||||||
return testcase_python_path
|
return testcase_python_path
|
||||||
|
|
||||||
|
|
||||||
def format_with_black(tests_path: Text):
|
def make_testsuite(testsuite: Dict) -> List[Text]:
|
||||||
logger.info("format testcases with black ...")
|
"""convert valid testsuite dict to pytest folder with testcases"""
|
||||||
tests_path, _ = convert_testcase_path(tests_path)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(["black", tests_path])
|
# validate testcase format
|
||||||
except subprocess.CalledProcessError as ex:
|
load_testsuite(testsuite)
|
||||||
logger.error(ex)
|
except exceptions.TestSuiteFormatError as ex:
|
||||||
|
logger.error(f"TestSuiteFormatError: {ex}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
config = testsuite["config"]
|
||||||
|
testsuite_path = config["path"]
|
||||||
|
project_meta = load_project_meta(testsuite_path)
|
||||||
|
project_working_directory = project_meta.PWD
|
||||||
|
|
||||||
|
testsuite_variables = config.get("variables", {})
|
||||||
|
if isinstance(testsuite_variables, Text):
|
||||||
|
# get variables by function, e.g. ${get_variables()}
|
||||||
|
testsuite_variables = parse_data(
|
||||||
|
testsuite_variables, {}, project_meta.functions
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"start to make testsuite: {testsuite_path}")
|
||||||
|
|
||||||
|
# create directory with testsuite file name, put its testcases under this directory
|
||||||
|
testsuite_dir = testsuite_path.replace(".", "_")
|
||||||
|
os.makedirs(testsuite_dir, exist_ok=True)
|
||||||
|
|
||||||
|
testcase_files = []
|
||||||
|
|
||||||
|
for testcase in testsuite["testcases"]:
|
||||||
|
# get referenced testcase content
|
||||||
|
testcase_file = testcase["testcase"]
|
||||||
|
testcase_path = os.path.join(project_working_directory, testcase_file)
|
||||||
|
testcase_dict = load_test_file(testcase_path)
|
||||||
|
testcase_dict.setdefault("config", {})
|
||||||
|
testcase_dict["config"]["path"] = os.path.join(
|
||||||
|
testsuite_dir, os.path.basename(testcase_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
# override testcase name
|
||||||
|
testcase_dict["config"]["name"] = testcase["name"]
|
||||||
|
# override base_url
|
||||||
|
base_url = testsuite["config"].get("base_url") or testcase.get("base_url")
|
||||||
|
if base_url:
|
||||||
|
testcase_dict["config"]["base_url"] = base_url
|
||||||
|
# override variables
|
||||||
|
testcase_dict["config"].setdefault("variables", {})
|
||||||
|
testcase_dict["config"]["variables"].update(testcase.get("variables", {}))
|
||||||
|
testcase_dict["config"]["variables"].update(testsuite_variables)
|
||||||
|
|
||||||
|
# make testcase
|
||||||
|
testcase_path = make_testcase(testcase_dict)
|
||||||
|
testcase_files.append(testcase_path)
|
||||||
|
|
||||||
|
return testcase_files
|
||||||
|
|
||||||
|
|
||||||
def make(tests_path: Text) -> List:
|
def __make(tests_path: Text) -> List:
|
||||||
testcases = []
|
test_files = []
|
||||||
if os.path.isdir(tests_path):
|
if os.path.isdir(tests_path):
|
||||||
files_list = load_folder_files(tests_path)
|
files_list = load_folder_files(tests_path)
|
||||||
testcases.extend(files_list)
|
test_files.extend(files_list)
|
||||||
elif os.path.isfile(tests_path):
|
elif os.path.isfile(tests_path):
|
||||||
testcases.append(tests_path)
|
test_files.append(tests_path)
|
||||||
else:
|
else:
|
||||||
raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
|
raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
|
||||||
|
|
||||||
testcase_path_list = []
|
testcase_path_list = []
|
||||||
for testcase_path in testcases:
|
for test_file in test_files:
|
||||||
testcase_path = make_testcase(testcase_path)
|
try:
|
||||||
if not testcase_path:
|
test_content = load_test_file(test_file)
|
||||||
|
test_content.setdefault("config", {})["path"] = test_file
|
||||||
|
except (exceptions.FileNotFound, exceptions.FileFormatError) as ex:
|
||||||
|
logger.warning(ex)
|
||||||
continue
|
continue
|
||||||
testcase_path_list.append(testcase_path)
|
|
||||||
|
|
||||||
format_with_black(tests_path)
|
# testcase
|
||||||
|
if "teststeps" in test_content:
|
||||||
|
try:
|
||||||
|
testcase_file = make_testcase(test_content)
|
||||||
|
except exceptions.TestCaseFormatError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
testcase_path_list.append(testcase_file)
|
||||||
|
|
||||||
|
# testsuite
|
||||||
|
elif "testcases" in test_content:
|
||||||
|
try:
|
||||||
|
testcase_files = make_testsuite(test_content)
|
||||||
|
except exceptions.TestSuiteFormatError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
testcase_path_list.extend(testcase_files)
|
||||||
|
|
||||||
|
# invalid format
|
||||||
|
else:
|
||||||
|
raise exceptions.FileFormatError(
|
||||||
|
f"test file is neither testcase nor testsuite: {test_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not testcase_path_list:
|
||||||
|
logger.warning(f"No valid testcase generated on {tests_path}")
|
||||||
|
return []
|
||||||
|
|
||||||
return testcase_path_list
|
return testcase_path_list
|
||||||
|
|
||||||
|
|
||||||
def main_make(tests_paths: List[Text]) -> List:
|
def main_make(tests_paths: List[Text]) -> List:
|
||||||
testcase_path_list = []
|
testcase_path_list = []
|
||||||
for tests_path in tests_paths:
|
for tests_path in tests_paths:
|
||||||
testcase_path_list.extend(make(tests_path))
|
testcase_path_list.extend(__make(tests_path))
|
||||||
|
|
||||||
|
format_pytest_with_black(testcase_path_list)
|
||||||
return testcase_path_list
|
return testcase_path_list
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from httprunner.ext.make import make_testcase, main_make, convert_testcase_path
|
|
||||||
|
from httprunner.ext.make import main_make, convert_testcase_path
|
||||||
|
|
||||||
|
|
||||||
class TestLoader(unittest.TestCase):
|
class TestLoader(unittest.TestCase):
|
||||||
def test_make_testcase(self):
|
def test_make_testcase(self):
|
||||||
path = "examples/postman_echo/request_methods/request_with_variables.yml"
|
path = ["examples/postman_echo/request_methods/request_with_variables.yml"]
|
||||||
testcase_python_path = make_testcase(path)
|
testcase_python_list = main_make(path)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
testcase_python_path,
|
testcase_python_list[0],
|
||||||
"examples/postman_echo/request_methods/request_with_variables_test.py",
|
"examples/postman_echo/request_methods/request_with_variables_test.py",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,42 +22,47 @@ class TestLoader(unittest.TestCase):
|
|||||||
|
|
||||||
def test_convert_testcase_path(self):
|
def test_convert_testcase_path(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("mubu.login.yml")[0],
|
convert_testcase_path("mubu.login.yml")[0], "mubu_login_test.py"
|
||||||
"mubu_login_test.py"
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to/mubu.login.yml")[0],
|
convert_testcase_path("/path/to/mubu.login.yml")[0],
|
||||||
"/path/to/mubu_login_test.py"
|
"/path/to/mubu_login_test.py",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/mubu.login.yml")[0],
|
convert_testcase_path("/path/to 2/mubu.login.yml")[0],
|
||||||
"/path/to 2/mubu_login_test.py"
|
"/path/to 2/mubu_login_test.py",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/mubu.login.yml")[1],
|
convert_testcase_path("/path/to 2/mubu.login.yml")[1], "MubuLogin"
|
||||||
"MubuLogin"
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("mubu login.yml")[0],
|
convert_testcase_path("mubu login.yml")[0], "mubu_login_test.py"
|
||||||
"mubu_login_test.py"
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/mubu login.yml")[1],
|
convert_testcase_path("/path/to 2/mubu login.yml")[1], "MubuLogin"
|
||||||
"MubuLogin"
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/mubu-login.yml")[0],
|
convert_testcase_path("/path/to 2/mubu-login.yml")[0],
|
||||||
"/path/to 2/mubu_login_test.py"
|
"/path/to 2/mubu_login_test.py",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/mubu-login.yml")[1],
|
convert_testcase_path("/path/to 2/mubu-login.yml")[1], "MubuLogin"
|
||||||
"MubuLogin"
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
convert_testcase_path("/path/to 2/幕布login.yml")[0],
|
convert_testcase_path("/path/to 2/幕布login.yml")[0],
|
||||||
"/path/to 2/幕布login_test.py"
|
"/path/to 2/幕布login_test.py",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(convert_testcase_path("/path/to/幕布login.yml")[1], "幕布Login")
|
||||||
convert_testcase_path("/path/to/幕布login.yml")[1],
|
|
||||||
"幕布Login"
|
def test_make_testsuite(self):
|
||||||
|
path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
|
||||||
|
testcase_python_list = main_make(path)
|
||||||
|
self.assertEqual(len(testcase_python_list), 2)
|
||||||
|
self.assertIn(
|
||||||
|
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
|
||||||
|
testcase_python_list,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
|
||||||
|
testcase_python_list,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from pydantic import ValidationError
|
|||||||
|
|
||||||
from httprunner import builtin, utils
|
from httprunner import builtin, utils
|
||||||
from httprunner import exceptions
|
from httprunner import exceptions
|
||||||
from httprunner.schema import TestCase, ProjectMeta
|
from httprunner.schema import TestCase, ProjectMeta, TestSuite
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# PyYAML version >= 5.1
|
# PyYAML version >= 5.1
|
||||||
@@ -34,7 +34,8 @@ def _load_yaml_file(yaml_file: Text) -> Dict:
|
|||||||
try:
|
try:
|
||||||
yaml_content = yaml.load(stream)
|
yaml_content = yaml.load(stream)
|
||||||
except yaml.YAMLError as ex:
|
except yaml.YAMLError as ex:
|
||||||
logger.error(str(ex))
|
err_msg = f"YAMLError:\nfile: {yaml_file}\nerror: {ex}"
|
||||||
|
logger.error(err_msg)
|
||||||
raise exceptions.FileFormatError
|
raise exceptions.FileFormatError
|
||||||
|
|
||||||
return yaml_content
|
return yaml_content
|
||||||
@@ -46,42 +47,65 @@ def _load_json_file(json_file: Text) -> Dict:
|
|||||||
with io.open(json_file, encoding="utf-8") as data_file:
|
with io.open(json_file, encoding="utf-8") as data_file:
|
||||||
try:
|
try:
|
||||||
json_content = json.load(data_file)
|
json_content = json.load(data_file)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError as ex:
|
||||||
err_msg = f"JSONDecodeError: JSON file format error: {json_file}"
|
err_msg = f"JSONDecodeError:\nfile: {json_file}\nerror: {ex}"
|
||||||
logger.error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.FileFormatError(err_msg)
|
raise exceptions.FileFormatError(err_msg)
|
||||||
|
|
||||||
return json_content
|
return json_content
|
||||||
|
|
||||||
|
|
||||||
def load_testcase_file(testcase_file: Text) -> Tuple[Dict, TestCase]:
|
def load_test_file(test_file: Text) -> Dict:
|
||||||
"""load testcase file and validate with pydantic model"""
|
"""load testcase/testsuite file content"""
|
||||||
if not os.path.isfile(testcase_file):
|
if not os.path.isfile(test_file):
|
||||||
raise exceptions.FileNotFound(f"testcase file not exists: {testcase_file}")
|
raise exceptions.FileNotFound(f"test file not exists: {test_file}")
|
||||||
|
|
||||||
file_suffix = os.path.splitext(testcase_file)[1].lower()
|
file_suffix = os.path.splitext(test_file)[1].lower()
|
||||||
if file_suffix == ".json":
|
if file_suffix == ".json":
|
||||||
testcase_content = _load_json_file(testcase_file)
|
test_file_content = _load_json_file(test_file)
|
||||||
elif file_suffix in [".yaml", ".yml"]:
|
elif file_suffix in [".yaml", ".yml"]:
|
||||||
testcase_content = _load_yaml_file(testcase_file)
|
test_file_content = _load_yaml_file(test_file)
|
||||||
else:
|
else:
|
||||||
# '' or other suffix
|
# '' or other suffix
|
||||||
raise exceptions.FileFormatError(
|
raise exceptions.FileFormatError(
|
||||||
f"testcase file should be YAML/JSON format, invalid testcase file: {testcase_file}"
|
f"testcase/testsuite file should be YAML/JSON format, invalid format file: {test_file}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return test_file_content
|
||||||
|
|
||||||
|
|
||||||
|
def load_testcase(testcase: Dict) -> TestCase:
|
||||||
|
path = testcase["config"]["path"]
|
||||||
try:
|
try:
|
||||||
# validate with pydantic TestCase model
|
# validate with pydantic TestCase model
|
||||||
testcase_obj = TestCase.parse_obj(testcase_content)
|
testcase_obj = TestCase.parse_obj(testcase)
|
||||||
except ValidationError as ex:
|
except ValidationError as ex:
|
||||||
err_msg = f"Invalid testcase format: {testcase_file}"
|
err_msg = f"TestCase ValidationError:\nfile: {path}\nerror: {ex}"
|
||||||
logger.error(f"{err_msg}\n{ex}")
|
logger.error(err_msg)
|
||||||
raise exceptions.TestCaseFormatError(err_msg)
|
raise exceptions.TestCaseFormatError(err_msg)
|
||||||
|
|
||||||
testcase_content["config"]["path"] = testcase_file
|
return testcase_obj
|
||||||
testcase_obj.config.path = testcase_file
|
|
||||||
|
|
||||||
return testcase_content, testcase_obj
|
|
||||||
|
def load_testcase_file(testcase_file: Text) -> TestCase:
|
||||||
|
"""load testcase file and validate with pydantic model"""
|
||||||
|
testcase_content = load_test_file(testcase_file)
|
||||||
|
testcase_content.setdefault("config", {})["path"] = testcase_file
|
||||||
|
testcase_obj = load_testcase(testcase_content)
|
||||||
|
return testcase_obj
|
||||||
|
|
||||||
|
|
||||||
|
def load_testsuite(testsuite: Dict) -> TestSuite:
|
||||||
|
path = testsuite["config"]["path"]
|
||||||
|
try:
|
||||||
|
# validate with pydantic TestCase model
|
||||||
|
testsuite_obj = TestSuite.parse_obj(testsuite)
|
||||||
|
except ValidationError as ex:
|
||||||
|
err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}"
|
||||||
|
logger.error(err_msg)
|
||||||
|
raise exceptions.TestSuiteFormatError(err_msg)
|
||||||
|
|
||||||
|
return testsuite_obj
|
||||||
|
|
||||||
|
|
||||||
def load_dot_env_file(dot_env_path: Text) -> Dict:
|
def load_dot_env_file(dot_env_path: Text) -> Dict:
|
||||||
@@ -349,6 +373,14 @@ def init_project_working_directory(test_path: Text) -> Tuple[Text, Text]:
|
|||||||
return debugtalk_path, project_working_directory
|
return debugtalk_path, project_working_directory
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_working_directory(test_path: Text) -> Text:
|
||||||
|
global project_working_directory
|
||||||
|
if not project_working_directory:
|
||||||
|
init_project_working_directory(test_path)
|
||||||
|
|
||||||
|
return project_working_directory
|
||||||
|
|
||||||
|
|
||||||
def load_debugtalk_functions() -> Dict[Text, Callable]:
|
def load_debugtalk_functions() -> Dict[Text, Callable]:
|
||||||
""" load project debugtalk.py module functions
|
""" load project debugtalk.py module functions
|
||||||
debugtalk.py should be located in project working directory.
|
debugtalk.py should be located in project working directory.
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ from httprunner import exceptions, loader
|
|||||||
class TestLoader(unittest.TestCase):
|
class TestLoader(unittest.TestCase):
|
||||||
def test_load_testcase_file(self):
|
def test_load_testcase_file(self):
|
||||||
path = "examples/postman_echo/request_methods/request_with_variables.yml"
|
path = "examples/postman_echo/request_methods/request_with_variables.yml"
|
||||||
testcase_json, testcase_obj = loader.load_testcase_file(path)
|
testcase_obj = loader.load_testcase_file(path)
|
||||||
self.assertEqual(
|
|
||||||
testcase_json["config"]["name"], "request methods testcase with variables"
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
testcase_obj.config.name, "request methods testcase with variables"
|
testcase_obj.config.name, "request methods testcase with variables"
|
||||||
)
|
)
|
||||||
self.assertEqual(len(testcase_json["teststeps"]), 3)
|
|
||||||
self.assertEqual(len(testcase_obj.teststeps), 3)
|
self.assertEqual(len(testcase_obj.teststeps), 3)
|
||||||
|
|
||||||
def test_load_json_file_file_format_error(self):
|
def test_load_json_file_file_format_error(self):
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ class ResponseObject(object):
|
|||||||
self.resp_obj_meta = {
|
self.resp_obj_meta = {
|
||||||
"status_code": resp_obj.status_code,
|
"status_code": resp_obj.status_code,
|
||||||
"headers": resp_obj.headers,
|
"headers": resp_obj.headers,
|
||||||
|
"cookies": dict(resp_obj.cookies),
|
||||||
"body": body,
|
"body": body,
|
||||||
}
|
}
|
||||||
self.validation_results: Dict = {}
|
self.validation_results: Dict = {}
|
||||||
@@ -163,6 +164,8 @@ class ResponseObject(object):
|
|||||||
|
|
||||||
# check item
|
# check item
|
||||||
check_item = u_validator["check"]
|
check_item = u_validator["check"]
|
||||||
|
# TODO: validate variable or function
|
||||||
|
# check_item = parse_data(check_item, variables_mapping, functions_mapping)
|
||||||
check_value = jmespath.search(check_item, self.resp_obj_meta)
|
check_value = jmespath.search(check_item, self.resp_obj_meta)
|
||||||
check_value = parse_string_value(check_value)
|
check_value = parse_string_value(check_value)
|
||||||
|
|
||||||
|
|||||||
@@ -65,12 +65,8 @@ class HttpRunner(object):
|
|||||||
method = parsed_request_dict.pop("method")
|
method = parsed_request_dict.pop("method")
|
||||||
url_path = parsed_request_dict.pop("url")
|
url_path = parsed_request_dict.pop("url")
|
||||||
url = build_url(self.config.base_url, url_path)
|
url = build_url(self.config.base_url, url_path)
|
||||||
|
|
||||||
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
|
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
|
||||||
|
|
||||||
logger.info(f"{method} {url}")
|
|
||||||
logger.debug(f"request kwargs(raw): {parsed_request_dict}")
|
|
||||||
|
|
||||||
# request
|
# request
|
||||||
self.__session = self.__session or HttpSession()
|
self.__session = self.__session or HttpSession()
|
||||||
resp = self.__session.request(method, url, **parsed_request_dict)
|
resp = self.__session.request(method, url, **parsed_request_dict)
|
||||||
@@ -148,7 +144,7 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
def __run_step(self, step: TStep):
|
def __run_step(self, step: TStep):
|
||||||
"""run teststep, teststep maybe a request or referenced testcase"""
|
"""run teststep, teststep maybe a request or referenced testcase"""
|
||||||
logger.info(f"run step: {step.name}")
|
logger.info(f"run step begin: {step.name} >>>>>>")
|
||||||
|
|
||||||
if step.request:
|
if step.request:
|
||||||
step_data = self.__run_step_request(step)
|
step_data = self.__run_step_request(step)
|
||||||
@@ -160,6 +156,7 @@ class HttpRunner(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.__step_datas.append(step_data)
|
self.__step_datas.append(step_data)
|
||||||
|
logger.info(f"run step end: {step.name} <<<<<<\n")
|
||||||
return step_data.export
|
return step_data.export
|
||||||
|
|
||||||
def run(self, testcase: TestCase):
|
def run(self, testcase: TestCase):
|
||||||
@@ -209,7 +206,7 @@ class HttpRunner(object):
|
|||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise exceptions.ParamsError(f"Invalid testcase path: {path}")
|
raise exceptions.ParamsError(f"Invalid testcase path: {path}")
|
||||||
|
|
||||||
_, testcase_obj = load_testcase_file(path)
|
testcase_obj = load_testcase_file(path)
|
||||||
return self.run(testcase_obj)
|
return self.run(testcase_obj)
|
||||||
|
|
||||||
def get_step_datas(self) -> List[StepData]:
|
def get_step_datas(self) -> List[StepData]:
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ class TestHttpRunner(unittest.TestCase):
|
|||||||
|
|
||||||
def test_run_testcase_by_path_request_only(self):
|
def test_run_testcase_by_path_request_only(self):
|
||||||
self.runner.run_path(
|
self.runner.run_path(
|
||||||
"examples/postman_echo/request_methods/request_with_variables.yml"
|
"examples/postman_echo/request_methods/request_with_functions.yml"
|
||||||
)
|
)
|
||||||
result = self.runner.get_summary()
|
result = self.runner.get_summary()
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEqual(result.name, "request methods testcase with variables")
|
self.assertEqual(result.name, "request methods testcase with functions")
|
||||||
self.assertEqual(result.step_datas[0].name, "get with params")
|
self.assertEqual(result.step_datas[0].name, "get with params")
|
||||||
self.assertEqual(len(result.step_datas), 3)
|
self.assertEqual(len(result.step_datas), 3)
|
||||||
|
|
||||||
@@ -24,5 +24,5 @@ class TestHttpRunner(unittest.TestCase):
|
|||||||
result = self.runner.get_summary()
|
result = self.runner.get_summary()
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEqual(result.name, "request methods testcase: reference testcase")
|
self.assertEqual(result.name, "request methods testcase: reference testcase")
|
||||||
self.assertEqual(result.step_datas[0].name, "request with variables")
|
self.assertEqual(result.step_datas[0].name, "request with functions")
|
||||||
self.assertEqual(len(result.step_datas), 1)
|
self.assertEqual(len(result.step_datas), 1)
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class TConfig(BaseModel):
|
|||||||
name: Name
|
name: Name
|
||||||
verify: Verify = False
|
verify: Verify = False
|
||||||
base_url: BaseUrl = ""
|
base_url: BaseUrl = ""
|
||||||
variables: VariablesMapping = {}
|
# Text: prepare variables in debugtalk.py, ${get_variable()}
|
||||||
|
variables: Union[VariablesMapping, Text] = {}
|
||||||
setup_hooks: Hook = []
|
setup_hooks: Hook = []
|
||||||
teardown_hooks: Hook = []
|
teardown_hooks: Hook = []
|
||||||
export: Export = []
|
export: Export = []
|
||||||
@@ -160,6 +161,18 @@ class PlatformInfo(BaseModel):
|
|||||||
platform: Text
|
platform: Text
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseRef(BaseModel):
|
||||||
|
name: Text
|
||||||
|
base_url: Text = ""
|
||||||
|
testcase: Text
|
||||||
|
variables: VariablesMapping = {}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuite(BaseModel):
|
||||||
|
config: TConfig
|
||||||
|
testcases: List[TestCaseRef]
|
||||||
|
|
||||||
|
|
||||||
class Stat(BaseModel):
|
class Stat(BaseModel):
|
||||||
total: int = 0
|
total: int = 0
|
||||||
success: int = 0
|
success: int = 0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "httprunner"
|
name = "httprunner"
|
||||||
version = "3.0.3"
|
version = "3.0.4"
|
||||||
description = "One-stop solution for HTTP(S) testing."
|
description = "One-stop solution for HTTP(S) testing."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
Reference in New Issue
Block a user