mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-22 00:29:37 +08:00
Merge branch 'master' into master
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
__version__ = "2.2.5"
|
||||
__version__ = "2.2.6"
|
||||
__description__ = "One-stop solution for HTTP(S) testing."
|
||||
|
||||
__all__ = ["__version__", "__description__"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# encoding: utf-8
|
||||
|
||||
|
||||
def main_hrun():
|
||||
""" API test: parse command line options and run commands.
|
||||
"""
|
||||
@@ -11,7 +12,7 @@ def main_hrun():
|
||||
from httprunner.compat import is_py2
|
||||
from httprunner.validator import validate_json_file
|
||||
from httprunner.utils import (create_scaffold, get_python2_retire_msg,
|
||||
prettify_json_file)
|
||||
prettify_json_file)
|
||||
|
||||
parser = argparse.ArgumentParser(description=__description__)
|
||||
parser.add_argument(
|
||||
|
||||
@@ -17,10 +17,12 @@ try:
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
## file loader
|
||||
# file loader
|
||||
###############################################################################
|
||||
|
||||
|
||||
def _check_format(file_path, content):
|
||||
""" check testcase format if valid
|
||||
"""
|
||||
@@ -239,9 +241,10 @@ def locate_file(start_path, file_name):
|
||||
|
||||
|
||||
###############################################################################
|
||||
## debugtalk.py module loader
|
||||
# debugtalk.py module loader
|
||||
###############################################################################
|
||||
|
||||
|
||||
def load_module_functions(module):
|
||||
""" load python module functions.
|
||||
|
||||
@@ -290,9 +293,10 @@ def load_debugtalk_functions():
|
||||
|
||||
|
||||
###############################################################################
|
||||
## testcase loader
|
||||
# testcase loader
|
||||
###############################################################################
|
||||
|
||||
|
||||
project_mapping = {}
|
||||
tests_def_mapping = {
|
||||
"PWD": None,
|
||||
@@ -720,14 +724,16 @@ def load_api_folder(api_folder_path):
|
||||
if isinstance(api_items, list):
|
||||
for api_item in api_items:
|
||||
key, api_dict = api_item.popitem()
|
||||
api_id = api_dict.get("id") or api_dict.get("def") or api_dict.get("name")
|
||||
api_id = api_dict.get("id") or api_dict.get("def") \
|
||||
or api_dict.get("name")
|
||||
if key != "api" or not api_id:
|
||||
raise exceptions.ParamsError(
|
||||
"Invalid API defined in {}".format(api_file_path))
|
||||
|
||||
if api_id in api_definition_mapping:
|
||||
raise exceptions.ParamsError(
|
||||
"Duplicated API ({}) defined in {}".format(api_id, api_file_path))
|
||||
"Duplicated API ({}) defined in {}".format(
|
||||
api_id, api_file_path))
|
||||
else:
|
||||
api_definition_mapping[api_id] = api_dict
|
||||
|
||||
@@ -745,7 +751,8 @@ def locate_debugtalk_py(start_path):
|
||||
""" locate debugtalk.py file
|
||||
|
||||
Args:
|
||||
start_path (str): start locating path, maybe testcase file path or directory path
|
||||
start_path (str): start locating path,
|
||||
maybe testcase file path or directory path
|
||||
|
||||
Returns:
|
||||
str: debugtalk.py file path, None if not found
|
||||
@@ -769,7 +776,8 @@ def load_project_tests(test_path, dot_env_path=None):
|
||||
dot_env_path (str): specified .env file path
|
||||
|
||||
Returns:
|
||||
dict: project loaded api/testcases definitions, environments and debugtalk.py functions.
|
||||
dict: project loaded api/testcases definitions,
|
||||
environments and debugtalk.py functions.
|
||||
|
||||
"""
|
||||
# locate debugtalk.py file
|
||||
@@ -876,6 +884,7 @@ def load_tests(path, dot_env_path=None):
|
||||
}
|
||||
|
||||
def __load_file_content(path):
|
||||
loaded_content = None
|
||||
try:
|
||||
loaded_content = load_test_file(path)
|
||||
except exceptions.FileFormatError:
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
import ast
|
||||
import builtins
|
||||
import os
|
||||
import re
|
||||
|
||||
from httprunner import exceptions, utils, validator
|
||||
from httprunner.compat import basestring, builtin_str, numeric_types, str
|
||||
from httprunner.compat import basestring, numeric_types, str
|
||||
|
||||
# use $$ to escape $ notation
|
||||
dolloar_regex_compile = re.compile(r"\$\$")
|
||||
@@ -872,10 +871,23 @@ def __prepare_config(config, project_mapping, session_variables_set=None):
|
||||
"""
|
||||
# get config variables
|
||||
raw_config_variables = config.pop("variables", {})
|
||||
raw_config_variables_mapping = utils.ensure_mapping_format(raw_config_variables)
|
||||
|
||||
override_variables = utils.deepcopy_dict(project_mapping.get("variables", {}))
|
||||
functions = project_mapping.get("functions", {})
|
||||
|
||||
if isinstance(raw_config_variables, basestring) and function_regex_compile.match(raw_config_variables):
|
||||
# config variables are generated by calling function
|
||||
# e.g.
|
||||
# "config": {
|
||||
# "name": "basic test with httpbin",
|
||||
# "variables": "${gen_variables()}"
|
||||
# }
|
||||
raw_config_variables_mapping = parse_lazy_data(
|
||||
prepare_lazy_data(raw_config_variables, functions_mapping=functions)
|
||||
)
|
||||
else:
|
||||
raw_config_variables_mapping = utils.ensure_mapping_format(raw_config_variables)
|
||||
|
||||
# override config variables with passed in variables
|
||||
raw_config_variables_mapping.update(override_variables)
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import json
|
||||
import re
|
||||
import jsonpath
|
||||
|
||||
from httprunner import exceptions, logger, utils
|
||||
from httprunner.compat import OrderedDict, basestring, is_py2
|
||||
from requests.models import PreparedRequest
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
|
||||
text_extractor_regexp_compile = re.compile(r".*\(.*\).*")
|
||||
|
||||
@@ -38,6 +38,35 @@ class ResponseObject(object):
|
||||
logger.log_error(err_msg)
|
||||
raise exceptions.ParamsError(err_msg)
|
||||
|
||||
def _extract_field_with_jsonpath(self, field):
|
||||
"""
|
||||
JSONPath Docs: https://goessner.net/articles/JsonPath/
|
||||
For example, response body like below:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"items": [{
|
||||
"id": 1,
|
||||
"name": "Bob"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "James"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "操作成功"
|
||||
}
|
||||
|
||||
:param field: Jsonpath expression, e.g. 1)$.code 2) $..items.*.id
|
||||
:return: A list that extracted from json repsonse example. 1) [200] 2) [1, 2]
|
||||
"""
|
||||
result = jsonpath.jsonpath(self.parsed_body(), field)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
raise exceptions.ExtractFailure("\tjsonpath {} get nothing\n".format(field))
|
||||
|
||||
def _extract_field_with_regex(self, field):
|
||||
""" extract field from response content with regex.
|
||||
requests.Response body could be json or html text.
|
||||
@@ -81,7 +110,7 @@ class ResponseObject(object):
|
||||
"content.person.name.first_name"
|
||||
|
||||
"""
|
||||
# string.split(sep=None, maxsplit=-1) -> list of strings
|
||||
# string.split(sep=None, maxsplit=1) -> list of strings
|
||||
# e.g. "content.person.name" => ["content", "person.name"]
|
||||
try:
|
||||
top_query, sub_query = field.split('.', 1)
|
||||
@@ -211,7 +240,9 @@ class ResponseObject(object):
|
||||
|
||||
msg = "extract: {}".format(field)
|
||||
|
||||
if text_extractor_regexp_compile.match(field):
|
||||
if field.startswith("$"):
|
||||
value = self._extract_field_with_jsonpath(field)
|
||||
elif text_extractor_regexp_compile.match(field):
|
||||
value = self._extract_field_with_regex(field)
|
||||
else:
|
||||
value = self._extract_field_with_delimiter(field)
|
||||
|
||||
Reference in New Issue
Block a user