mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-27 10:31:32 +08:00
Merge pull request #919 from httprunner/v3
## 3.0.8 (2020-06-04) **Added** - feat: add sentry sdk - feat: extract session variable from referenced testcase step **Fixed** - fix: missing request json - fix: override testsuite/testcase config verify - fix: only strip whitespaces and tabs, \n\r are left because they maybe used in changeset - fix: log testcase duration before raise ValidationFailure **Changed** - change: add httprunner version in generated pytest file
This commit is contained in:
@@ -1,5 +1,23 @@
|
||||
# Release History
|
||||
|
||||
## 3.0.8 (2020-06-04)
|
||||
|
||||
**Added**
|
||||
|
||||
- feat: add sentry sdk
|
||||
- feat: extract session variable from referenced testcase step
|
||||
|
||||
**Fixed**
|
||||
|
||||
- fix: missing request json
|
||||
- fix: override testsuite/testcase config verify
|
||||
- fix: only strip whitespaces and tabs, \n\r are left because they maybe used in changeset
|
||||
- fix: log testcase duration before raise ValidationFailure
|
||||
|
||||
**Changed**
|
||||
|
||||
- change: add httprunner version in generated pytest file
|
||||
|
||||
## 3.0.7 (2020-06-03)
|
||||
|
||||
**Added**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/httpbin/basic.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/httpbin/hooks.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/httpbin/load_image.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/httpbin/upload.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/httpbin/validate.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/request_with_functions.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
@@ -10,6 +10,7 @@ class TestCaseRequestWithFunctions(HttpRunner):
|
||||
.variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"})
|
||||
.base_url("https://postman-echo.com")
|
||||
.verify(False)
|
||||
.export(*["session_foo2"])
|
||||
)
|
||||
|
||||
teststeps = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
|
||||
|
||||
import os
|
||||
@@ -26,6 +26,23 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
|
||||
RunTestCase("request with functions")
|
||||
.with_variables(**{"foo1": "override_bar1"})
|
||||
.call(RequestWithFunctions)
|
||||
.extract(*["session_foo2"])
|
||||
),
|
||||
Step(
|
||||
RunRequest("post form data")
|
||||
.with_variables(**{"foo1": "bar1"})
|
||||
.post("/post")
|
||||
.with_headers(
|
||||
**{
|
||||
"User-Agent": "HttpRunner/${get_httprunner_version()}",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
)
|
||||
.with_data("foo1=$foo1&foo2=$session_foo2")
|
||||
.validate()
|
||||
.assert_equal("status_code", 200)
|
||||
.assert_equal("body.form.foo1", "session_bar1")
|
||||
.assert_equal("body.form.foo2", "session_bar2")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/hardcode.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -4,6 +4,7 @@ config:
|
||||
foo1: session_bar1
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
export: ["session_foo2"]
|
||||
|
||||
teststeps:
|
||||
-
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/request_with_functions.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
@@ -10,6 +10,7 @@ class TestCaseRequestWithFunctions(HttpRunner):
|
||||
.variables(**{"foo1": "session_bar1"})
|
||||
.base_url("https://postman-echo.com")
|
||||
.verify(False)
|
||||
.export(*["session_foo2"])
|
||||
)
|
||||
|
||||
teststeps = [
|
||||
|
||||
@@ -11,3 +11,20 @@ teststeps:
|
||||
variables:
|
||||
foo1: override_bar1
|
||||
testcase: request_methods/request_with_functions.yml
|
||||
extract:
|
||||
- session_foo2
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo1: bar1
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$session_foo2"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "session_bar1"]
|
||||
- eq: ["body.form.foo2", "session_bar2"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
|
||||
|
||||
import os
|
||||
@@ -26,6 +26,23 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
|
||||
RunTestCase("request with functions")
|
||||
.with_variables(**{"foo1": "override_bar1"})
|
||||
.call(RequestWithFunctions)
|
||||
.extract(*["session_foo2"])
|
||||
),
|
||||
Step(
|
||||
RunRequest("post form data")
|
||||
.with_variables(**{"foo1": "bar1"})
|
||||
.post("/post")
|
||||
.with_headers(
|
||||
**{
|
||||
"User-Agent": "HttpRunner/${get_httprunner_version()}",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
)
|
||||
.with_data("foo1=$foo1&foo2=$session_foo2")
|
||||
.validate()
|
||||
.assert_equal("status_code", 200)
|
||||
.assert_equal("body.form.foo1", "session_bar1")
|
||||
.assert_equal("body.form.foo2", "session_bar2")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/request_with_variables.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/validate_with_functions.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NOTICE: Generated By HttpRunner.
|
||||
# NOTICE: Generated By HttpRunner v3.0.8
|
||||
# FROM: examples/postman_echo/request_methods/validate_with_variables.yml
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
__version__ = "3.0.7"
|
||||
__version__ = "3.0.8"
|
||||
__description__ = "One-stop solution for HTTP(S) testing."
|
||||
|
||||
from httprunner.runner import HttpRunner
|
||||
from httprunner.schema import TConfig, TStep
|
||||
from httprunner.testcase import Config, Step, RunRequest, RunTestCase
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"__description__",
|
||||
"HttpRunner",
|
||||
"TConfig",
|
||||
"TStep",
|
||||
"Config",
|
||||
"Step",
|
||||
"RunRequest",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import contextlib
|
||||
import logging
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
from starlette.requests import Request
|
||||
|
||||
router = APIRouter()
|
||||
@@ -37,6 +37,6 @@ async def debug_python(request: Request):
|
||||
resp["code"] = 1
|
||||
resp["message"] = "fail"
|
||||
resp["result"] = str(ex)
|
||||
logging.error(resp)
|
||||
logger.error(resp)
|
||||
|
||||
return resp
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import logging
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
import pkg_resources
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -29,6 +29,6 @@ async def install_dependenies(deps: List[str]):
|
||||
resp["result"][dep] = False
|
||||
resp["code"] = 1
|
||||
resp["message"] = "fail"
|
||||
logging.error(f"failed to install dependency: {dep}")
|
||||
logger.error(f"failed to install dependency: {dep}")
|
||||
|
||||
return resp
|
||||
|
||||
@@ -4,12 +4,16 @@ import sys
|
||||
|
||||
import pytest
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_message
|
||||
|
||||
from httprunner import __description__, __version__
|
||||
from httprunner.compat import ensure_cli_args
|
||||
from httprunner.ext.har2case import init_har2case_parser, main_har2case
|
||||
from httprunner.make import init_make_parser, main_make
|
||||
from httprunner.scaffold import init_parser_scaffold, main_scaffold
|
||||
from httprunner.utils import init_sentry_sdk
|
||||
|
||||
init_sentry_sdk()
|
||||
|
||||
|
||||
def init_parser_run(subparsers):
|
||||
@@ -20,6 +24,7 @@ def init_parser_run(subparsers):
|
||||
|
||||
|
||||
def main_run(extra_args):
|
||||
capture_message("start to run")
|
||||
# keep compatibility with v2
|
||||
extra_args = ensure_cli_args(extra_args)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from requests.exceptions import (
|
||||
MissingSchema,
|
||||
RequestException,
|
||||
)
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner.schema import RequestData, ResponseData
|
||||
from httprunner.schema import SessionData, ReqRespData
|
||||
@@ -42,20 +43,21 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
||||
# record actual request info
|
||||
request_headers = dict(resp_obj.request.headers)
|
||||
request_cookies = resp_obj.request._cookies.get_dict()
|
||||
request_body = resp_obj.request.body
|
||||
try:
|
||||
request_body = json.loads(request_body)
|
||||
except json.JSONDecodeError:
|
||||
# str: Unexpected UTF-8 BOM (decode using utf-8-sig)
|
||||
pass
|
||||
except UnicodeDecodeError:
|
||||
# bytes/bytearray: request body in protobuf
|
||||
pass
|
||||
except TypeError:
|
||||
# neither str nor bytes/bytearray, e.g. None
|
||||
pass
|
||||
|
||||
if request_body:
|
||||
request_body = resp_obj.request.body
|
||||
if request_body is not None:
|
||||
try:
|
||||
request_body = json.loads(request_body)
|
||||
except json.JSONDecodeError:
|
||||
# str: a=1&b=2
|
||||
pass
|
||||
except UnicodeDecodeError as ex:
|
||||
# bytes/bytearray: request body in protobuf
|
||||
capture_exception(ex)
|
||||
except TypeError as ex:
|
||||
# neither str nor bytes/bytearray, e.g. <MultipartEncoder>
|
||||
capture_exception(ex)
|
||||
|
||||
request_content_type = lower_dict_keys(request_headers).get("content-type")
|
||||
if request_content_type and "multipart/form-data" in request_content_type:
|
||||
# upload file type
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
This module handles compatibility issues between testcase format v2 and v3.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Dict, Text, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import exceptions
|
||||
from httprunner.loader import load_project_meta
|
||||
from httprunner.utils import sort_dict_by_custom_order
|
||||
|
||||
@@ -28,9 +28,8 @@ def convert_jmespath(raw: Text) -> Text:
|
||||
elif item.isdigit():
|
||||
# convert lst.0.name to lst[0].name
|
||||
if len(raw_list) == 0:
|
||||
raise exceptions.FileFormatError(
|
||||
f"Invalid jmespath: {raw}, jmespath should startswith headers/body/status_code/cookies"
|
||||
)
|
||||
logger.error(f"Invalid jmespath: {raw}")
|
||||
sys.exit(1)
|
||||
|
||||
last_item = raw_list.pop()
|
||||
item = f"{last_item}[{item}]"
|
||||
@@ -60,7 +59,8 @@ def convert_extractors(extractors: Union[List, Dict]) -> Dict:
|
||||
elif isinstance(extractors, Dict):
|
||||
v3_extractors = extractors
|
||||
else:
|
||||
raise exceptions.FileFormatError(f"Invalid extractor: {extractors}")
|
||||
logger.error(f"Invalid extractor: {extractors}")
|
||||
sys.exit(1)
|
||||
|
||||
for k, v in v3_extractors.items():
|
||||
v3_extractors[k] = convert_jmespath(v)
|
||||
@@ -133,7 +133,10 @@ def ensure_step_attachment(step: Dict) -> Dict:
|
||||
test_dict["teardown_hooks"] = step["teardown_hooks"]
|
||||
|
||||
if "extract" in step:
|
||||
test_dict["extract"] = convert_extractors(step["extract"])
|
||||
if step.get("request"):
|
||||
test_dict["extract"] = convert_extractors(step["extract"])
|
||||
elif step.get("testcase"):
|
||||
test_dict["extract"] = step["extract"]
|
||||
|
||||
if "validate" in step:
|
||||
test_dict["validate"] = convert_validators(step["validate"])
|
||||
@@ -164,6 +167,8 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
|
||||
for step in test_content["teststeps"]:
|
||||
teststep = {}
|
||||
|
||||
teststep.update(ensure_step_attachment(step))
|
||||
|
||||
if "request" in step:
|
||||
teststep["request"] = step.pop("request")
|
||||
elif "api" in step:
|
||||
@@ -171,7 +176,6 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
|
||||
elif "testcase" in step:
|
||||
teststep["testcase"] = step.pop("testcase")
|
||||
|
||||
teststep.update(ensure_step_attachment(step))
|
||||
teststep = sort_step_by_custom_order(teststep)
|
||||
v3_content["teststeps"].append(teststep)
|
||||
|
||||
@@ -207,7 +211,8 @@ def generate_conftest_for_summary(args: List):
|
||||
# FIXME: several test paths maybe specified
|
||||
break
|
||||
else:
|
||||
raise exceptions.FileNotFound(f"No test path specified!")
|
||||
logger.error(f"No valid test path specified! \nargs: {args}")
|
||||
sys.exit(1)
|
||||
|
||||
project_meta = load_project_meta(test_path)
|
||||
conftest_path = os.path.join(project_meta.PWD, "conftest.py")
|
||||
|
||||
@@ -12,6 +12,7 @@ import os
|
||||
import sys
|
||||
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_message
|
||||
|
||||
from httprunner.ext.har2case.core import HarParser
|
||||
|
||||
@@ -69,6 +70,7 @@ def main_har2case(args):
|
||||
else:
|
||||
output_file_type = "pytest"
|
||||
|
||||
capture_message(f"har2case {output_file_type}")
|
||||
HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -5,6 +5,7 @@ import sys
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner.ext.har2case import utils
|
||||
from httprunner.make import make_testcase, format_pytest_with_black
|
||||
@@ -238,14 +239,11 @@ class HarParser(object):
|
||||
try:
|
||||
resp_content_json = json.loads(content)
|
||||
except JSONDecodeError:
|
||||
logger.warning(
|
||||
"response content can not be loaded as json: {}".format(
|
||||
content.encode("utf-8")
|
||||
)
|
||||
)
|
||||
logger.warning(f"response content can not be loaded as json: {content}")
|
||||
return
|
||||
|
||||
if not isinstance(resp_content_json, dict):
|
||||
# e.g. ['a', 'b']
|
||||
return
|
||||
|
||||
for key, value in resp_content_json.items():
|
||||
@@ -334,7 +332,12 @@ class HarParser(object):
|
||||
logger.info(f"Start to generate testcase from {self.har_file_path}")
|
||||
harfile = os.path.splitext(self.har_file_path)[0]
|
||||
|
||||
testcase = self._make_testcase()
|
||||
try:
|
||||
testcase = self._make_testcase()
|
||||
except Exception as ex:
|
||||
capture_exception(ex)
|
||||
raise
|
||||
|
||||
logger.debug("prepared testcase: {}".format(testcase))
|
||||
|
||||
if file_type == "JSON":
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from json.decoder import JSONDecodeError
|
||||
from urllib.parse import unquote
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
|
||||
def load_har_log_entries(file_path):
|
||||
@@ -32,8 +33,9 @@ def load_har_log_entries(file_path):
|
||||
try:
|
||||
content_json = json.loads(f.read())
|
||||
return content_json["log"]["entries"]
|
||||
except (KeyError, TypeError, JSONDecodeError):
|
||||
logging.error("HAR file content error: {}".format(file_path))
|
||||
except (KeyError, TypeError, JSONDecodeError) as ex:
|
||||
capture_exception(ex)
|
||||
logger.error("HAR file content error: {}".format(file_path))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -103,20 +105,20 @@ def convert_list_to_dict(origin_list):
|
||||
def dump_yaml(testcase, yaml_file):
|
||||
""" dump HAR entries to yaml testcase
|
||||
"""
|
||||
logging.info("dump testcase to YAML format.")
|
||||
logger.info("dump testcase to YAML format.")
|
||||
|
||||
with io.open(yaml_file, "w", encoding="utf-8") as outfile:
|
||||
yaml.dump(
|
||||
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
||||
)
|
||||
|
||||
logging.info("Generate YAML testcase successfully: {}".format(yaml_file))
|
||||
logger.info("Generate YAML testcase successfully: {}".format(yaml_file))
|
||||
|
||||
|
||||
def dump_json(testcase, json_file):
|
||||
""" dump HAR entries to json testcase
|
||||
"""
|
||||
logging.info("dump testcase to JSON format.")
|
||||
logger.info("dump testcase to JSON format.")
|
||||
|
||||
with io.open(json_file, "w", encoding="utf-8") as outfile:
|
||||
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
||||
@@ -125,4 +127,4 @@ def dump_json(testcase, json_file):
|
||||
|
||||
outfile.write(my_json_str)
|
||||
|
||||
logging.info("Generate JSON testcase successfully: {}".format(json_file))
|
||||
logger.info("Generate JSON testcase successfully: {}".format(json_file))
|
||||
|
||||
@@ -290,8 +290,11 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
|
||||
return os.path.abspath(file_path)
|
||||
|
||||
# current working directory
|
||||
if os.path.abspath(start_dir_path) == os.getcwd():
|
||||
raise exceptions.FileNotFound(f"{file_name} not found in {start_path}")
|
||||
cwd = os.getcwd()
|
||||
if os.path.abspath(start_dir_path) == cwd:
|
||||
raise exceptions.FileNotFound(
|
||||
f"{file_name} not found for {start_path}\ncurrent working directory: {cwd}"
|
||||
)
|
||||
|
||||
# system root dir
|
||||
# Windows, e.g. 'E:\\'
|
||||
|
||||
@@ -5,8 +5,9 @@ from typing import Text, List, Tuple, Dict, Set, NoReturn
|
||||
|
||||
import jinja2
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner import exceptions
|
||||
from httprunner import exceptions, __version__
|
||||
from httprunner.compat import ensure_testcase_v3_api, ensure_testcase_v3
|
||||
from httprunner.loader import (
|
||||
load_folder_files,
|
||||
@@ -24,7 +25,7 @@ make_files_cache_set: Set = set()
|
||||
pytest_files_set: Set = set()
|
||||
|
||||
__TEMPLATE__ = jinja2.Template(
|
||||
"""# NOTICE: Generated By HttpRunner.
|
||||
"""# NOTICE: Generated By HttpRunner v{{ version }}
|
||||
# FROM: {{ testcase_path }}
|
||||
{% if imports_list %}
|
||||
import os
|
||||
@@ -131,6 +132,7 @@ def format_pytest_with_black(*python_paths: Text) -> NoReturn:
|
||||
try:
|
||||
subprocess.run(["black", *python_paths])
|
||||
except subprocess.CalledProcessError as ex:
|
||||
capture_exception(ex)
|
||||
logger.error(ex)
|
||||
|
||||
|
||||
@@ -147,6 +149,9 @@ def make_config_chain_style(config: Dict) -> Text:
|
||||
if "verify" in config:
|
||||
config_chain_style += f'.verify({config["verify"]})'
|
||||
|
||||
if "export" in config:
|
||||
config_chain_style += f'.export(*{config["export"]})'
|
||||
|
||||
return config_chain_style
|
||||
|
||||
|
||||
@@ -173,6 +178,10 @@ def make_request_chain_style(request: Dict) -> Text:
|
||||
data = f'"{data}"'
|
||||
request_chain_style += f".with_data({data})"
|
||||
|
||||
if "json" in request:
|
||||
req_json = request["json"]
|
||||
request_chain_style += f".with_json({req_json})"
|
||||
|
||||
if "timeout" in request:
|
||||
timeout = request["timeout"]
|
||||
request_chain_style += f".set_timeout({timeout})"
|
||||
@@ -198,7 +207,7 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
|
||||
elif teststep.get("testcase"):
|
||||
step_info = f'RunTestCase("{teststep["name"]}")'
|
||||
else:
|
||||
raise exceptions.TestCaseFormatError
|
||||
raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}")
|
||||
|
||||
if "variables" in teststep:
|
||||
variables = teststep["variables"]
|
||||
@@ -211,11 +220,18 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
|
||||
call_ref_testcase = f".call({testcase})"
|
||||
step_info += call_ref_testcase
|
||||
|
||||
if "extract" in teststep:
|
||||
step_info += ".extract()"
|
||||
|
||||
for extract_name, extract_path in teststep["extract"].items():
|
||||
step_info += f'.with_jmespath("{extract_path}", "{extract_name}")'
|
||||
extract_info = teststep.get("extract")
|
||||
if extract_info:
|
||||
if isinstance(extract_info, Dict):
|
||||
# request step
|
||||
step_info += ".extract()"
|
||||
for extract_name, extract_path in extract_info.items():
|
||||
step_info += f'.with_jmespath("{extract_path}", "{extract_name}")'
|
||||
elif isinstance(extract_info, List):
|
||||
# reference testcase step
|
||||
step_info += f".extract(*{extract_info})"
|
||||
else:
|
||||
raise exceptions.TestCaseFormatError(f"Invalid extract: {extract_info}")
|
||||
|
||||
if "validate" in teststep:
|
||||
step_info += ".validate()"
|
||||
@@ -298,6 +314,7 @@ def make_testcase(
|
||||
)
|
||||
|
||||
data = {
|
||||
"version": __version__,
|
||||
"testcase_path": __ensure_cwd_relative(testcase_path),
|
||||
"class_name": f"TestCase{testcase_cls_name}",
|
||||
"imports_list": imports_list,
|
||||
@@ -326,10 +343,10 @@ def make_testsuite(testsuite: Dict) -> NoReturn:
|
||||
# validate testsuite format
|
||||
load_testsuite(testsuite)
|
||||
|
||||
config = testsuite["config"]
|
||||
testsuite_path = config["path"]
|
||||
testsuite_config = testsuite["config"]
|
||||
testsuite_path = testsuite_config["path"]
|
||||
|
||||
testsuite_variables = config.get("variables", {})
|
||||
testsuite_variables = testsuite_config.get("variables", {})
|
||||
if isinstance(testsuite_variables, Text):
|
||||
# get variables by function, e.g. ${get_variables()}
|
||||
project_meta = load_project_meta(testsuite_path)
|
||||
@@ -357,9 +374,12 @@ def make_testsuite(testsuite: Dict) -> NoReturn:
|
||||
# override testcase name
|
||||
testcase_dict["config"]["name"] = testcase["name"]
|
||||
# override base_url
|
||||
base_url = testsuite["config"].get("base_url") or testcase.get("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 verify
|
||||
if "verify" in testsuite_config:
|
||||
testcase_dict["config"]["verify"] = testsuite_config["verify"]
|
||||
# override variables
|
||||
testcase_dict["config"].setdefault("variables", {})
|
||||
testcase_dict["config"]["variables"].update(testcase.get("variables", {}))
|
||||
|
||||
@@ -3,6 +3,8 @@ import builtins
|
||||
import re
|
||||
from typing import Any, Set, Text, Callable, List, Dict
|
||||
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner import loader, utils, exceptions
|
||||
from httprunner.schema import VariablesMapping, FunctionsMapping
|
||||
|
||||
@@ -70,7 +72,8 @@ def regex_findall_variables(content: Text) -> List[Text]:
|
||||
for var_tuple in variable_regex_compile.findall(content):
|
||||
vars_list.append(var_tuple[0] or var_tuple[1])
|
||||
return vars_list
|
||||
except TypeError:
|
||||
except TypeError as ex:
|
||||
capture_exception(ex)
|
||||
return []
|
||||
|
||||
|
||||
@@ -102,7 +105,8 @@ def regex_findall_functions(content: Text) -> List[Text]:
|
||||
"""
|
||||
try:
|
||||
return function_regex_compile.findall(content)
|
||||
except TypeError:
|
||||
except TypeError as ex:
|
||||
capture_exception(ex)
|
||||
return []
|
||||
|
||||
|
||||
@@ -358,7 +362,8 @@ def parse_data(
|
||||
# content in string format may contains variables and functions
|
||||
variables_mapping = variables_mapping or {}
|
||||
functions_mapping = functions_mapping or {}
|
||||
raw_data = raw_data.strip()
|
||||
# only strip whitespaces and tabs, \n\r is left because they maybe used in changeset
|
||||
raw_data = raw_data.strip(" \t")
|
||||
return parse_string(raw_data, variables_mapping, functions_mapping)
|
||||
|
||||
elif isinstance(raw_data, (list, set, tuple)):
|
||||
|
||||
@@ -46,6 +46,7 @@ class HttpRunner(object):
|
||||
__step_datas: List[StepData] = None
|
||||
__session: HttpSession = None
|
||||
__session_variables: VariablesMapping = {}
|
||||
__export_variables: VariablesMapping = {}
|
||||
# time
|
||||
__start_at: float = 0
|
||||
__duration: float = 0
|
||||
@@ -101,6 +102,7 @@ class HttpRunner(object):
|
||||
method = parsed_request_dict.pop("method")
|
||||
url_path = parsed_request_dict.pop("url")
|
||||
url = build_url(self.__config.base_url, url_path)
|
||||
parsed_request_dict["verify"] = self.__config.verify
|
||||
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
|
||||
|
||||
# request
|
||||
@@ -147,6 +149,8 @@ class HttpRunner(object):
|
||||
except ValidationFailure:
|
||||
self.__session.data.success = False
|
||||
log_req_resp_details()
|
||||
# log testcase duration before raise ValidationFailure
|
||||
self.__duration = time.time() - self.__start_at
|
||||
raise
|
||||
finally:
|
||||
# save request & response meta data
|
||||
@@ -248,6 +252,7 @@ class HttpRunner(object):
|
||||
self.__step_datas: List[StepData] = []
|
||||
self.__session = self.__session or HttpSession()
|
||||
self.__session_variables = {}
|
||||
self.__export_variables = {}
|
||||
|
||||
# run teststeps
|
||||
for step in self.__teststeps:
|
||||
@@ -269,6 +274,11 @@ class HttpRunner(object):
|
||||
self.__session_variables.update(extract_mapping)
|
||||
|
||||
self.__duration = time.time() - self.__start_at
|
||||
|
||||
self.__export_variables = self.get_export_variables()
|
||||
if self.__export_variables:
|
||||
logger.info(f"export variables: {self.__export_variables}")
|
||||
|
||||
return self
|
||||
|
||||
def run_path(self, path: Text) -> "HttpRunner":
|
||||
@@ -293,6 +303,9 @@ class HttpRunner(object):
|
||||
return self.__step_datas
|
||||
|
||||
def get_export_variables(self) -> Dict:
|
||||
if self.__export_variables:
|
||||
return self.__export_variables
|
||||
|
||||
export_vars_mapping = {}
|
||||
for var_name in self.__config.export:
|
||||
if var_name not in self.__session_variables:
|
||||
|
||||
@@ -2,6 +2,7 @@ import os.path
|
||||
import sys
|
||||
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_message
|
||||
|
||||
|
||||
def init_parser_scaffold(subparsers):
|
||||
@@ -140,5 +141,6 @@ def sleep(n_secs):
|
||||
|
||||
|
||||
def main_scaffold(args):
|
||||
capture_message("startproject with scaffold")
|
||||
create_scaffold(args.project_name)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -66,7 +66,7 @@ class TStep(BaseModel):
|
||||
variables: VariablesMapping = {}
|
||||
setup_hooks: Hook = []
|
||||
teardown_hooks: Hook = []
|
||||
extract: Dict[Text, Text] = {}
|
||||
extract: Union[Dict[Text, Text], List[Text]] = {}
|
||||
validators: Validators = Field([], alias="validate")
|
||||
validate_script: List[Text] = []
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class Config(object):
|
||||
self.__variables = {}
|
||||
self.__base_url = ""
|
||||
self.__verify = False
|
||||
self.__export = []
|
||||
|
||||
caller_frame = inspect.stack()[1]
|
||||
self.__path = caller_frame.filename
|
||||
@@ -40,45 +41,52 @@ class Config(object):
|
||||
self.__verify = verify
|
||||
return self
|
||||
|
||||
def export(self, *export_var_name: Text) -> "Config":
|
||||
self.__export.extend(export_var_name)
|
||||
return self
|
||||
|
||||
def perform(self) -> TConfig:
|
||||
return TConfig(
|
||||
name=self.__name,
|
||||
base_url=self.__base_url,
|
||||
verify=self.__verify,
|
||||
variables=self.__variables,
|
||||
export=list(set(self.__export)),
|
||||
path=self.__path,
|
||||
)
|
||||
|
||||
|
||||
class StepValidation(object):
|
||||
class StepRequestValidation(object):
|
||||
def __init__(self, step: TStep):
|
||||
self.__t_step = step
|
||||
|
||||
def assert_equal(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
|
||||
def assert_equal(
|
||||
self, jmes_path: Text, expected_value: Any
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"equal": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_not_equal(
|
||||
self, jmes_path: Text, expected_value: Any
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"not_equal": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_greater_than(
|
||||
self, jmes_path: Text, expected_value: Union[int, float]
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"greater_than": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_less_than(
|
||||
self, jmes_path: Text, expected_value: Union[int, float]
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"less_than": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_greater_or_equals(
|
||||
self, jmes_path: Text, expected_value: Union[int, float]
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append(
|
||||
{"greater_or_equals": [jmes_path, expected_value]}
|
||||
)
|
||||
@@ -86,19 +94,19 @@ class StepValidation(object):
|
||||
|
||||
def assert_less_or_equals(
|
||||
self, jmes_path: Text, expected_value: Union[int, float]
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"less_or_equals": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_length_equal(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"length_equal": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_length_greater_than(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append(
|
||||
{"length_greater_than": [jmes_path, expected_value]}
|
||||
)
|
||||
@@ -106,7 +114,7 @@ class StepValidation(object):
|
||||
|
||||
def assert_length_less_than(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append(
|
||||
{"length_less_than": [jmes_path, expected_value]}
|
||||
)
|
||||
@@ -114,7 +122,7 @@ class StepValidation(object):
|
||||
|
||||
def assert_length_greater_or_equals(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append(
|
||||
{"length_greater_or_equals": [jmes_path, expected_value]}
|
||||
)
|
||||
@@ -122,7 +130,7 @@ class StepValidation(object):
|
||||
|
||||
def assert_length_less_or_equals(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append(
|
||||
{"length_less_or_equals": [jmes_path, expected_value]}
|
||||
)
|
||||
@@ -130,41 +138,43 @@ class StepValidation(object):
|
||||
|
||||
def assert_string_equals(
|
||||
self, jmes_path: Text, expected_value: int
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"string_equals": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_startswith(
|
||||
self, jmes_path: Text, expected_value: Text
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"startswith": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_endswith(
|
||||
self, jmes_path: Text, expected_value: Text
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"endswith": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_regex_match(
|
||||
self, jmes_path: Text, expected_value: Text
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_contains(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
|
||||
def assert_contains(
|
||||
self, jmes_path: Text, expected_value: Any
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"contains": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_contained_by(
|
||||
self, jmes_path: Text, expected_value: Any
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"contained_by": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
def assert_type_match(
|
||||
self, jmes_path: Text, expected_value: Text
|
||||
) -> "StepValidation":
|
||||
) -> "StepRequestValidation":
|
||||
self.__t_step.validators.append({"type_match": [jmes_path, expected_value]})
|
||||
return self
|
||||
|
||||
@@ -172,11 +182,11 @@ class StepValidation(object):
|
||||
return self.__t_step
|
||||
|
||||
|
||||
class StepExtraction(object):
|
||||
class StepRequestExtraction(object):
|
||||
def __init__(self, step: TStep):
|
||||
self.__t_step = step
|
||||
|
||||
def with_jmespath(self, jmes_path: Text, var_name: Text) -> "StepExtraction":
|
||||
def with_jmespath(self, jmes_path: Text, var_name: Text) -> "StepRequestExtraction":
|
||||
self.__t_step.extract[var_name] = jmes_path
|
||||
return self
|
||||
|
||||
@@ -188,8 +198,8 @@ class StepExtraction(object):
|
||||
# # TODO: extract response json with jsonpath
|
||||
# pass
|
||||
|
||||
def validate(self) -> StepValidation:
|
||||
return StepValidation(self.__t_step)
|
||||
def validate(self) -> StepRequestValidation:
|
||||
return StepRequestValidation(self.__t_step)
|
||||
|
||||
def perform(self) -> TStep:
|
||||
return self.__t_step
|
||||
@@ -215,6 +225,10 @@ class RequestWithOptionalArgs(object):
|
||||
self.__t_step.request.data = data
|
||||
return self
|
||||
|
||||
def with_json(self, req_json) -> "RequestWithOptionalArgs":
|
||||
self.__t_step.request.req_json = req_json
|
||||
return self
|
||||
|
||||
def set_timeout(self, timeout: float) -> "RequestWithOptionalArgs":
|
||||
self.__t_step.request.timeout = timeout
|
||||
return self
|
||||
@@ -234,11 +248,11 @@ class RequestWithOptionalArgs(object):
|
||||
# def hooks(self):
|
||||
# pass
|
||||
|
||||
def extract(self) -> StepExtraction:
|
||||
return StepExtraction(self.__t_step)
|
||||
def extract(self) -> StepRequestExtraction:
|
||||
return StepRequestExtraction(self.__t_step)
|
||||
|
||||
def validate(self) -> StepValidation:
|
||||
return StepValidation(self.__t_step)
|
||||
def validate(self) -> StepRequestValidation:
|
||||
return StepRequestValidation(self.__t_step)
|
||||
|
||||
def perform(self) -> TStep:
|
||||
return self.__t_step
|
||||
@@ -281,6 +295,19 @@ class RunRequest(object):
|
||||
return RequestWithOptionalArgs(self.__t_step)
|
||||
|
||||
|
||||
class StepRefCase(object):
|
||||
def __init__(self, step: TStep):
|
||||
self.__t_step = step
|
||||
self.__t_step.extract = []
|
||||
|
||||
def extract(self, *var_name: Text) -> "StepRefCase":
|
||||
self.__t_step.extract.extend(var_name)
|
||||
return self
|
||||
|
||||
def perform(self) -> TStep:
|
||||
return self.__t_step
|
||||
|
||||
|
||||
class RunTestCase(object):
|
||||
def __init__(self, name: Text):
|
||||
self.__t_step = TStep(name=name)
|
||||
@@ -289,9 +316,9 @@ class RunTestCase(object):
|
||||
self.__t_step.variables.update(variables)
|
||||
return self
|
||||
|
||||
def call(self, testcase: Callable):
|
||||
def call(self, testcase: Callable) -> StepRefCase:
|
||||
self.__t_step.testcase = testcase
|
||||
return self
|
||||
return StepRefCase(self.__t_step)
|
||||
|
||||
def perform(self) -> TStep:
|
||||
return self.__t_step
|
||||
@@ -301,7 +328,11 @@ class Step(object):
|
||||
def __init__(
|
||||
self,
|
||||
step: Union[
|
||||
StepValidation, StepExtraction, RequestWithOptionalArgs, RunTestCase
|
||||
StepRequestValidation,
|
||||
StepRequestExtraction,
|
||||
RequestWithOptionalArgs,
|
||||
RunTestCase,
|
||||
StepRefCase,
|
||||
],
|
||||
):
|
||||
self.__t_step = step.perform()
|
||||
|
||||
@@ -2,14 +2,25 @@ import collections
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
import uuid
|
||||
from typing import Dict, List, Any
|
||||
|
||||
import sentry_sdk
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import __version__
|
||||
from httprunner import exceptions
|
||||
|
||||
|
||||
def init_sentry_sdk():
|
||||
sentry_sdk.init(
|
||||
dsn="https://460e31339bcb428c879aafa6a2e78098@sentry.io/5263855",
|
||||
release="httprunner@{}".format(__version__),
|
||||
)
|
||||
with sentry_sdk.configure_scope() as scope:
|
||||
scope.set_user({"id": uuid.getnode()})
|
||||
|
||||
|
||||
def set_os_environ(variables_mapping):
|
||||
""" set variables mapping to os.environ
|
||||
"""
|
||||
|
||||
32
poetry.lock
generated
32
poetry.lock
generated
@@ -451,6 +451,32 @@ version = "0.9.1"
|
||||
[package.dependencies]
|
||||
requests = ">=2.0.1,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python client for Sentry (https://getsentry.com)"
|
||||
name = "sentry-sdk"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.14.4"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
urllib3 = ">=1.10.0"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp (>=3.5)"]
|
||||
beam = ["beam (>=2.12)"]
|
||||
bottle = ["bottle (>=0.12.13)"]
|
||||
celery = ["celery (>=3)"]
|
||||
django = ["django (>=1.8)"]
|
||||
falcon = ["falcon (>=1.4)"]
|
||||
flask = ["flask (>=0.11)", "blinker (>=1.1)"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
rq = ["rq (>=0.6)"]
|
||||
sanic = ["sanic (>=0.8)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
tornado = ["tornado (>=5)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
@@ -572,7 +598,7 @@ allure = ["allure-pytest"]
|
||||
upload = ["requests-toolbelt", "filetype"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "3b5147c8c95480574c9eaa8f035c536cf18535766f60f768d2e714b257511dae"
|
||||
content-hash = "581cacf33c8afe330e5b6a965d5e16f6266718249cbdfed0d080b7536c5c4590"
|
||||
python-versions = "^3.6"
|
||||
|
||||
[metadata.files]
|
||||
@@ -850,6 +876,10 @@ requests-toolbelt = [
|
||||
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
|
||||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
||||
]
|
||||
sentry-sdk = [
|
||||
{file = "sentry-sdk-0.14.4.tar.gz", hash = "sha256:0e5e947d0f7a969314aa23669a94a9712be5a688ff069ff7b9fc36c66adc160c"},
|
||||
{file = "sentry_sdk-0.14.4-py2.py3-none-any.whl", hash = "sha256:799a8bf76b012e3030a881be00e97bc0b922ce35dde699c6537122b751d80e2c"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
|
||||
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "httprunner"
|
||||
version = "3.0.7"
|
||||
version = "3.0.8"
|
||||
description = "One-stop solution for HTTP(S) testing."
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
@@ -38,6 +38,7 @@ jmespath = "^0.9.5"
|
||||
black = "^19.10b0"
|
||||
pytest = "^5.4.2"
|
||||
pytest-html = "^2.1.1"
|
||||
sentry-sdk = "^0.14.4"
|
||||
allure-pytest = {version = "^2.8.16", optional = true}
|
||||
requests-toolbelt = {version = "^0.9.1", optional = true}
|
||||
filetype = {version = "^1.0.7", optional = true}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from httprunner import compat, exceptions
|
||||
from httprunner import compat
|
||||
|
||||
|
||||
class TestCompat(unittest.TestCase):
|
||||
@@ -19,7 +19,7 @@ class TestCompat(unittest.TestCase):
|
||||
compat.convert_jmespath("body.data.buildings.0.building_id"),
|
||||
"body.data.buildings[0].building_id",
|
||||
)
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
with self.assertRaises(SystemExit):
|
||||
compat.convert_jmespath("2.buildings.0.building_id")
|
||||
|
||||
def test_convert_extractors(self):
|
||||
|
||||
@@ -25,4 +25,4 @@ class TestHttpRunner(unittest.TestCase):
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.name, "request methods testcase: reference testcase")
|
||||
self.assertEqual(result.step_datas[0].name, "request with functions")
|
||||
self.assertEqual(len(result.step_datas), 1)
|
||||
self.assertEqual(len(result.step_datas), 2)
|
||||
|
||||
Reference in New Issue
Block a user