mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 06:23:52 +08:00
Merge pull request #949 from httprunner/v3
## 3.1.2 (2020-06-29) **Fixed** - fix: missing setup/teardown hooks for referenced testcase - fix: compatibility for `black` on Android termux that does not support multiprocessing well - fix: mishandling of request header `Content-Length` for GET method - fix: validate with jmespath containing variable or function, e.g. `body.locations[$index].name` **Changed** - change: import locust at beginning to monkey patch all modules - change: open file in binary mode
This commit is contained in:
8
.github/workflows/integration_test.yml
vendored
8
.github/workflows/integration_test.yml
vendored
@@ -1,6 +1,12 @@
|
|||||||
name: integration_test
|
name: integration_test
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
integration_test:
|
integration_test:
|
||||||
|
|||||||
8
.github/workflows/unittest.yml
vendored
8
.github/workflows/unittest.yml
vendored
@@ -1,6 +1,12 @@
|
|||||||
name: unittest
|
name: unittest
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unittest:
|
unittest:
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
|
## 3.1.2 (2020-06-29)
|
||||||
|
|
||||||
|
**Fixed**
|
||||||
|
|
||||||
|
- fix: missing setup/teardown hooks for referenced testcase
|
||||||
|
- fix: compatibility for `black` on Android termux that does not support multiprocessing well
|
||||||
|
- fix: mishandling of request header `Content-Length` for GET method
|
||||||
|
- fix: validate with jmespath containing variable or function, e.g. `body.locations[$index].name`
|
||||||
|
|
||||||
|
**Changed**
|
||||||
|
|
||||||
|
- change: import locust at beginning to monkey patch all modules
|
||||||
|
- change: open file in binary mode
|
||||||
|
|
||||||
## 3.1.1 (2020-06-23)
|
## 3.1.1 (2020-06-23)
|
||||||
|
|
||||||
**Added**
|
**Added**
|
||||||
@@ -8,7 +22,7 @@
|
|||||||
|
|
||||||
**Fixed**
|
**Fixed**
|
||||||
|
|
||||||
- fix #942: type_match None
|
- fix: ValueError when type_match None
|
||||||
- fix: override referenced testcase export in teststep
|
- fix: override referenced testcase export in teststep
|
||||||
- fix: avoid duplicate import
|
- fix: avoid duplicate import
|
||||||
- fix: override locust weight
|
- fix: override locust weight
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: basic.yml
|
# FROM: basic.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -33,5 +33,4 @@ teststeps:
|
|||||||
validate:
|
validate:
|
||||||
- eq: ["status_code", 500]
|
- eq: ["status_code", 500]
|
||||||
- eq: [headers."Content-Type", "html/text"]
|
- eq: [headers."Content-Type", "html/text"]
|
||||||
- eq: [body.headers."Content-Type", "application/json"]
|
|
||||||
- eq: [body.headers.Host, "127.0.0.1:8888"]
|
- eq: [body.headers.Host, "127.0.0.1:8888"]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: hooks.yml
|
# FROM: hooks.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
@@ -26,7 +26,6 @@ class TestCaseHooks(HttpRunner):
|
|||||||
.validate()
|
.validate()
|
||||||
.assert_equal("status_code", 500)
|
.assert_equal("status_code", 500)
|
||||||
.assert_equal('headers."Content-Type"', "html/text")
|
.assert_equal('headers."Content-Type"', "html/text")
|
||||||
.assert_equal('body.headers."Content-Type"', "application/json")
|
|
||||||
.assert_equal("body.headers.Host", "127.0.0.1:8888")
|
.assert_equal("body.headers.Host", "127.0.0.1:8888")
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: load_image.yml
|
# FROM: load_image.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: upload.yml
|
# FROM: upload.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: validate.yml
|
# FROM: validate.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/request_with_functions.yml
|
# FROM: request_methods/request_with_functions.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/request_with_testcase_reference.yml
|
# FROM: request_methods/request_with_testcase_reference.yml
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -35,7 +35,9 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
|
|||||||
.with_variables(
|
.with_variables(
|
||||||
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
|
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
|
||||||
)
|
)
|
||||||
|
.setup_hook("${sleep(0.1)}")
|
||||||
.call(RequestWithFunctions)
|
.call(RequestWithFunctions)
|
||||||
|
.teardown_hook("${sleep(0.2)}")
|
||||||
.export(*["foo3"])
|
.export(*["foo3"])
|
||||||
),
|
),
|
||||||
Step(
|
Step(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/hardcode.yml
|
# FROM: request_methods/hardcode.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/request_with_functions.yml
|
# FROM: request_methods/request_with_functions.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ teststeps:
|
|||||||
variables:
|
variables:
|
||||||
foo1: testcase_ref_bar1
|
foo1: testcase_ref_bar1
|
||||||
expect_foo1: testcase_ref_bar1
|
expect_foo1: testcase_ref_bar1
|
||||||
|
setup_hooks:
|
||||||
|
- ${sleep(0.1)}
|
||||||
testcase: request_methods/request_with_functions.yml
|
testcase: request_methods/request_with_functions.yml
|
||||||
|
teardown_hooks:
|
||||||
|
- ${sleep(0.2)}
|
||||||
export:
|
export:
|
||||||
- foo3
|
- foo3
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/request_with_testcase_reference.yml
|
# FROM: request_methods/request_with_testcase_reference.yml
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -33,7 +33,9 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
|
|||||||
.with_variables(
|
.with_variables(
|
||||||
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
|
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
|
||||||
)
|
)
|
||||||
|
.setup_hook("${sleep(0.1)}")
|
||||||
.call(RequestWithFunctions)
|
.call(RequestWithFunctions)
|
||||||
|
.teardown_hook("${sleep(0.2)}")
|
||||||
.export(*["foo3"])
|
.export(*["foo3"])
|
||||||
),
|
),
|
||||||
Step(
|
Step(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/request_with_variables.yml
|
# FROM: request_methods/request_with_variables.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/validate_with_functions.yml
|
# FROM: request_methods/validate_with_functions.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# NOTE: Generated By HttpRunner v3.1.0
|
# NOTE: Generated By HttpRunner v3.1.2
|
||||||
# FROM: request_methods/validate_with_variables.yml
|
# FROM: request_methods/validate_with_variables.yml
|
||||||
|
|
||||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "3.1.1"
|
__version__ = "3.1.2"
|
||||||
__description__ = "One-stop solution for HTTP(S) testing."
|
__description__ = "One-stop solution for HTTP(S) testing."
|
||||||
|
|
||||||
# import firstly for monkey patch if needed
|
# import firstly for monkey patch if needed
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import io
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
@@ -28,9 +27,9 @@ def load_har_log_entries(file_path):
|
|||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with io.open(file_path, "r+", encoding="utf-8-sig") as f:
|
with open(file_path, mode="rb") as f:
|
||||||
try:
|
try:
|
||||||
content_json = json.loads(f.read())
|
content_json = json.load(f)
|
||||||
return content_json["log"]["entries"]
|
return content_json["log"]["entries"]
|
||||||
except (TypeError, JSONDecodeError) as ex:
|
except (TypeError, JSONDecodeError) as ex:
|
||||||
logger.error(f"failed to load HAR file {file_path}: {ex}")
|
logger.error(f"failed to load HAR file {file_path}: {ex}")
|
||||||
@@ -108,7 +107,7 @@ def dump_yaml(testcase, yaml_file):
|
|||||||
"""
|
"""
|
||||||
logger.info("dump testcase to YAML format.")
|
logger.info("dump testcase to YAML format.")
|
||||||
|
|
||||||
with io.open(yaml_file, "w", encoding="utf-8") as outfile:
|
with open(yaml_file, "w", encoding="utf-8") as outfile:
|
||||||
yaml.dump(
|
yaml.dump(
|
||||||
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
||||||
)
|
)
|
||||||
@@ -121,7 +120,7 @@ def dump_json(testcase, json_file):
|
|||||||
"""
|
"""
|
||||||
logger.info("dump testcase to JSON format.")
|
logger.info("dump testcase to JSON format.")
|
||||||
|
|
||||||
with io.open(json_file, "w", encoding="utf-8") as outfile:
|
with open(json_file, "w", encoding="utf-8") as outfile:
|
||||||
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
||||||
if isinstance(my_json_str, bytes):
|
if isinstance(my_json_str, bytes):
|
||||||
my_json_str = my_json_str.decode("utf-8")
|
my_json_str = my_json_str.decode("utf-8")
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import importlib.util
|
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
|
||||||
|
|
||||||
if sys.argv[0].endswith("locusts"):
|
if "locust" in sys.argv[0]:
|
||||||
try:
|
try:
|
||||||
# monkey patch ssl at beginning to avoid RecursionError when running locust.
|
# monkey patch all at beginning to avoid RecursionError when running locust.
|
||||||
from gevent import monkey
|
# `from gevent import monkey; monkey.patch_all()` will be triggered when importing locust
|
||||||
|
|
||||||
monkey.patch_ssl()
|
|
||||||
from locust import main as locust_main
|
from locust import main as locust_main
|
||||||
|
|
||||||
|
print("NOTICE: gevent monkey patches have been applied !!!")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
msg = """
|
msg = """
|
||||||
Locust is not installed, install first and try again.
|
Locust is not installed, install first and try again.
|
||||||
@@ -20,9 +16,14 @@ $ pip install locust
|
|||||||
print(msg)
|
print(msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
""" converted pytest files from YAML/JSON testcases
|
""" converted pytest files from YAML/JSON testcases
|
||||||
"""
|
"""
|
||||||
pytest_files: List = []
|
pytest_files: List = []
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import csv
|
import csv
|
||||||
import importlib
|
import importlib
|
||||||
import io
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -29,7 +28,7 @@ project_meta: Union[ProjectMeta, None] = None
|
|||||||
def _load_yaml_file(yaml_file: Text) -> Dict:
|
def _load_yaml_file(yaml_file: Text) -> Dict:
|
||||||
""" load yaml file and check file content format
|
""" load yaml file and check file content format
|
||||||
"""
|
"""
|
||||||
with io.open(yaml_file, "r", encoding="utf-8") as stream:
|
with open(yaml_file, mode="rb") as stream:
|
||||||
try:
|
try:
|
||||||
yaml_content = yaml.load(stream)
|
yaml_content = yaml.load(stream)
|
||||||
except yaml.YAMLError as ex:
|
except yaml.YAMLError as ex:
|
||||||
@@ -43,7 +42,7 @@ def _load_yaml_file(yaml_file: Text) -> Dict:
|
|||||||
def _load_json_file(json_file: Text) -> Dict:
|
def _load_json_file(json_file: Text) -> Dict:
|
||||||
""" load json file and check file content format
|
""" load json file and check file content format
|
||||||
"""
|
"""
|
||||||
with io.open(json_file, encoding="utf-8") as data_file:
|
with open(json_file, mode="rb") as data_file:
|
||||||
try:
|
try:
|
||||||
json_content = json.load(data_file)
|
json_content = json.load(data_file)
|
||||||
except json.JSONDecodeError as ex:
|
except json.JSONDecodeError as ex:
|
||||||
@@ -128,17 +127,19 @@ def load_dot_env_file(dot_env_path: Text) -> Dict:
|
|||||||
logger.info(f"Loading environment variables from {dot_env_path}")
|
logger.info(f"Loading environment variables from {dot_env_path}")
|
||||||
env_variables_mapping = {}
|
env_variables_mapping = {}
|
||||||
|
|
||||||
with io.open(dot_env_path, "r", encoding="utf-8") as fp:
|
with open(dot_env_path, mode="rb") as fp:
|
||||||
for line in fp:
|
for line in fp:
|
||||||
# maxsplit=1
|
# maxsplit=1
|
||||||
if "=" in line:
|
if b"=" in line:
|
||||||
variable, value = line.split("=", 1)
|
variable, value = line.split(b"=", 1)
|
||||||
elif ":" in line:
|
elif b":" in line:
|
||||||
variable, value = line.split(":", 1)
|
variable, value = line.split(b":", 1)
|
||||||
else:
|
else:
|
||||||
raise exceptions.FileFormatError(".env format error")
|
raise exceptions.FileFormatError(".env format error")
|
||||||
|
|
||||||
env_variables_mapping[variable.strip()] = value.strip()
|
env_variables_mapping[
|
||||||
|
variable.strip().decode("utf-8")
|
||||||
|
] = value.strip().decode("utf-8")
|
||||||
|
|
||||||
utils.set_os_environ(env_variables_mapping)
|
utils.set_os_environ(env_variables_mapping)
|
||||||
return env_variables_mapping
|
return env_variables_mapping
|
||||||
@@ -182,7 +183,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]:
|
|||||||
|
|
||||||
csv_content_list = []
|
csv_content_list = []
|
||||||
|
|
||||||
with io.open(csv_file, encoding="utf-8") as csvfile:
|
with open(csv_file, encoding="utf-8") as csvfile:
|
||||||
reader = csv.DictReader(csvfile)
|
reader = csv.DictReader(csvfile)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
csv_content_list.append(row)
|
csv_content_list.append(row)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from httprunner.loader import (
|
|||||||
convert_relative_project_root_dir,
|
convert_relative_project_root_dir,
|
||||||
)
|
)
|
||||||
from httprunner.response import uniform_validator
|
from httprunner.response import uniform_validator
|
||||||
from httprunner.utils import override_config_variables
|
from httprunner.utils import override_config_variables, is_support_multiprocessing
|
||||||
|
|
||||||
""" cache converted pytest files, avoid duplicate making
|
""" cache converted pytest files, avoid duplicate making
|
||||||
"""
|
"""
|
||||||
@@ -160,7 +160,13 @@ def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]:
|
|||||||
def format_pytest_with_black(*python_paths: Text) -> NoReturn:
|
def format_pytest_with_black(*python_paths: Text) -> NoReturn:
|
||||||
logger.info("format pytest cases with black ...")
|
logger.info("format pytest cases with black ...")
|
||||||
try:
|
try:
|
||||||
subprocess.run(["black", *python_paths])
|
if is_support_multiprocessing() or len(python_paths) <= 1:
|
||||||
|
subprocess.run(["black", *python_paths])
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"this system does not support multiprocessing well, format files one by one ..."
|
||||||
|
)
|
||||||
|
[subprocess.run(["black", path]) for path in python_paths]
|
||||||
except subprocess.CalledProcessError as ex:
|
except subprocess.CalledProcessError as ex:
|
||||||
capture_exception(ex)
|
capture_exception(ex)
|
||||||
logger.error(ex)
|
logger.error(ex)
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ class TRequest(BaseModel):
|
|||||||
url: Url
|
url: Url
|
||||||
params: Dict[Text, Text] = {}
|
params: Dict[Text, Text] = {}
|
||||||
headers: Headers = {}
|
headers: Headers = {}
|
||||||
req_json: Union[Dict, List] = Field({}, alias="json")
|
req_json: Union[Dict, List] = Field(None, alias="json")
|
||||||
data: Union[Text, Dict[Text, Any]] = ""
|
data: Union[Text, Dict[Text, Any]] = None
|
||||||
cookies: Cookies = {}
|
cookies: Cookies = {}
|
||||||
timeout: float = 120
|
timeout: float = 120
|
||||||
allow_redirects: bool = True
|
allow_redirects: bool = True
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ class ResponseObject(object):
|
|||||||
functions_mapping: FunctionsMapping = None,
|
functions_mapping: FunctionsMapping = None,
|
||||||
) -> NoReturn:
|
) -> NoReturn:
|
||||||
|
|
||||||
|
variables_mapping = variables_mapping or {}
|
||||||
|
functions_mapping = functions_mapping or {}
|
||||||
|
|
||||||
self.validation_results = {}
|
self.validation_results = {}
|
||||||
if not validators:
|
if not validators:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class HttpRunner(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __call_hooks(
|
def __call_hooks(
|
||||||
self, hooks: Hooks, step_variables: VariablesMapping, hook_type: Text,
|
self, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text,
|
||||||
) -> NoReturn:
|
) -> NoReturn:
|
||||||
""" call hook actions.
|
""" call hook actions.
|
||||||
|
|
||||||
@@ -106,10 +106,10 @@ class HttpRunner(object):
|
|||||||
request: parsed request dict
|
request: parsed request dict
|
||||||
response: ResponseObject for current response
|
response: ResponseObject for current response
|
||||||
|
|
||||||
hook_type: setup/teardown
|
hook_msg: setup/teardown request/testcase
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.debug(f"call {hook_type} hook actions.")
|
logger.info(f"call hook actions: {hook_msg}")
|
||||||
|
|
||||||
if not isinstance(hooks, List):
|
if not isinstance(hooks, List):
|
||||||
logger.error(f"Invalid hooks format: {hooks}")
|
logger.error(f"Invalid hooks format: {hooks}")
|
||||||
@@ -153,7 +153,7 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
# setup hooks
|
# setup hooks
|
||||||
if step.setup_hooks:
|
if step.setup_hooks:
|
||||||
self.__call_hooks(step.setup_hooks, step.variables, "setup")
|
self.__call_hooks(step.setup_hooks, step.variables, "setup request")
|
||||||
|
|
||||||
# prepare arguments
|
# prepare arguments
|
||||||
method = parsed_request_dict.pop("method")
|
method = parsed_request_dict.pop("method")
|
||||||
@@ -169,7 +169,7 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
# teardown hooks
|
# teardown hooks
|
||||||
if step.teardown_hooks:
|
if step.teardown_hooks:
|
||||||
self.__call_hooks(step.teardown_hooks, step.variables, "teardown")
|
self.__call_hooks(step.teardown_hooks, step.variables, "teardown request")
|
||||||
|
|
||||||
def log_req_resp_details():
|
def log_req_resp_details():
|
||||||
err_msg = "\n{} DETAILED REQUEST & RESPONSE {}\n".format("*" * 32, "*" * 32)
|
err_msg = "\n{} DETAILED REQUEST & RESPONSE {}\n".format("*" * 32, "*" * 32)
|
||||||
@@ -236,6 +236,10 @@ class HttpRunner(object):
|
|||||||
step_variables = step.variables
|
step_variables = step.variables
|
||||||
step_export = step.export
|
step_export = step.export
|
||||||
|
|
||||||
|
# setup hooks
|
||||||
|
if step.setup_hooks:
|
||||||
|
self.__call_hooks(step.setup_hooks, step_variables, "setup testcase")
|
||||||
|
|
||||||
if hasattr(step.testcase, "config") and hasattr(step.testcase, "teststeps"):
|
if hasattr(step.testcase, "config") and hasattr(step.testcase, "teststeps"):
|
||||||
testcase_cls = step.testcase
|
testcase_cls = step.testcase
|
||||||
case_result = (
|
case_result = (
|
||||||
@@ -269,6 +273,10 @@ class HttpRunner(object):
|
|||||||
f"Invalid teststep referenced testcase: {step.dict()}"
|
f"Invalid teststep referenced testcase: {step.dict()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# teardown hooks
|
||||||
|
if step.teardown_hooks:
|
||||||
|
self.__call_hooks(step.teardown_hooks, step.variables, "teardown testcase")
|
||||||
|
|
||||||
step_data.data = case_result.get_step_datas() # list of step data
|
step_data.data = case_result.get_step_datas() # list of step data
|
||||||
step_data.export_vars = case_result.get_export_variables()
|
step_data.export_vars = case_result.get_export_variables()
|
||||||
step_data.success = case_result.success
|
step_data.success = case_result.success
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def create_scaffold(project_name):
|
|||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
def create_file(path, file_content=""):
|
def create_file(path, file_content=""):
|
||||||
with open(path, "w") as f:
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
f.write(file_content)
|
f.write(file_content)
|
||||||
msg = f"created file: {path}"
|
msg = f"created file: {path}"
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|||||||
@@ -350,6 +350,14 @@ class StepRefCase(object):
|
|||||||
def __init__(self, step_context: TStep):
|
def __init__(self, step_context: TStep):
|
||||||
self.__step_context = step_context
|
self.__step_context = step_context
|
||||||
|
|
||||||
|
def teardown_hook(self, hook: Text, assign_var_name: Text = None) -> "StepRefCase":
|
||||||
|
if assign_var_name:
|
||||||
|
self.__step_context.teardown_hooks.append({assign_var_name: hook})
|
||||||
|
else:
|
||||||
|
self.__step_context.teardown_hooks.append(hook)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
def export(self, *var_name: Text) -> "StepRefCase":
|
def export(self, *var_name: Text) -> "StepRefCase":
|
||||||
self.__step_context.export.extend(var_name)
|
self.__step_context.export.extend(var_name)
|
||||||
return self
|
return self
|
||||||
@@ -366,6 +374,14 @@ class RunTestCase(object):
|
|||||||
self.__step_context.variables.update(variables)
|
self.__step_context.variables.update(variables)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def setup_hook(self, hook: Text, assign_var_name: Text = None) -> "RunTestCase":
|
||||||
|
if assign_var_name:
|
||||||
|
self.__step_context.setup_hooks.append({assign_var_name: hook})
|
||||||
|
else:
|
||||||
|
self.__step_context.setup_hooks.append(hook)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
def call(self, testcase: Callable) -> StepRefCase:
|
def call(self, testcase: Callable) -> StepRefCase:
|
||||||
self.__step_context.testcase = testcase
|
self.__step_context.testcase = testcase
|
||||||
return StepRefCase(self.__step_context)
|
return StepRefCase(self.__step_context)
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import json
|
|||||||
import os.path
|
import os.path
|
||||||
import platform
|
import platform
|
||||||
import uuid
|
import uuid
|
||||||
|
from multiprocessing import Queue
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import __version__
|
from httprunner import __version__
|
||||||
from httprunner import exceptions
|
from httprunner import exceptions
|
||||||
from httprunner.models import VariablesMapping
|
from httprunner.models import VariablesMapping
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
|
|
||||||
def init_sentry_sdk():
|
def init_sentry_sdk():
|
||||||
@@ -209,3 +211,12 @@ def override_config_variables(
|
|||||||
variables = copy.deepcopy(config_variables)
|
variables = copy.deepcopy(config_variables)
|
||||||
variables.update(step_new_variables)
|
variables.update(step_new_variables)
|
||||||
return variables
|
return variables
|
||||||
|
|
||||||
|
|
||||||
|
def is_support_multiprocessing() -> bool:
|
||||||
|
try:
|
||||||
|
Queue()
|
||||||
|
return True
|
||||||
|
except (ImportError, OSError):
|
||||||
|
# system that does not support semaphores(dependency of multiprocessing), like Android termux
|
||||||
|
return False
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "httprunner"
|
name = "httprunner"
|
||||||
version = "3.1.1"
|
version = "3.1.2"
|
||||||
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"
|
||||||
|
|||||||
39
tests/response_test.py
Normal file
39
tests/response_test.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from httprunner.response import ResponseObject
|
||||||
|
|
||||||
|
|
||||||
|
class TestResponse(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
resp = requests.post(
|
||||||
|
"https://httpbin.org/anything",
|
||||||
|
json={
|
||||||
|
"locations": [
|
||||||
|
{"name": "Seattle", "state": "WA"},
|
||||||
|
{"name": "New York", "state": "NY"},
|
||||||
|
{"name": "Bellevue", "state": "WA"},
|
||||||
|
{"name": "Olympia", "state": "WA"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.resp_obj = ResponseObject(resp)
|
||||||
|
|
||||||
|
def test_extract(self):
|
||||||
|
extract_mapping = self.resp_obj.extract(
|
||||||
|
{"var_1": "body.json.locations[0]", "var_2": "body.json.locations[3].name"}
|
||||||
|
)
|
||||||
|
self.assertEqual(extract_mapping["var_1"], {"name": "Seattle", "state": "WA"})
|
||||||
|
self.assertEqual(extract_mapping["var_2"], "Olympia")
|
||||||
|
|
||||||
|
def test_validate(self):
|
||||||
|
variables_mapping = {"index": 1}
|
||||||
|
self.resp_obj.validate(
|
||||||
|
[
|
||||||
|
{"eq": ["body.json.locations[0].name", "Seattle"]},
|
||||||
|
{"eq": ["body.json.locations[0]", {"name": "Seattle", "state": "WA"}]},
|
||||||
|
{"eq": ["body.json.locations[$index].name", "New York"]},
|
||||||
|
],
|
||||||
|
variables_mapping=variables_mapping,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user