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:
debugtalk
2020-06-04 18:43:04 +08:00
committed by GitHub
37 changed files with 312 additions and 108 deletions
+18
View File
@@ -1,5 +1,23 @@
# Release History # 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) ## 3.0.7 (2020-06-03)
**Added** **Added**
+1 -1
View File
@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/httpbin/basic.yml # FROM: examples/httpbin/basic.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+1 -1
View File
@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/httpbin/hooks.yml # FROM: examples/httpbin/hooks.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+1 -1
View File
@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/httpbin/load_image.yml # FROM: examples/httpbin/load_image.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+1 -1
View File
@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/httpbin/upload.yml # FROM: examples/httpbin/upload.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+1 -1
View File
@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/httpbin/validate.yml # FROM: examples/httpbin/validate.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase 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: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
@@ -10,6 +10,7 @@ class TestCaseRequestWithFunctions(HttpRunner):
.variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"}) .variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"})
.base_url("https://postman-echo.com") .base_url("https://postman-echo.com")
.verify(False) .verify(False)
.export(*["session_foo2"])
) )
teststeps = [ 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 # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os import os
@@ -26,6 +26,23 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
RunTestCase("request with functions") RunTestCase("request with functions")
.with_variables(**{"foo1": "override_bar1"}) .with_variables(**{"foo1": "override_bar1"})
.call(RequestWithFunctions) .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: examples/postman_echo/request_methods/hardcode.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
@@ -4,6 +4,7 @@ config:
foo1: session_bar1 foo1: session_bar1
base_url: "https://postman-echo.com" base_url: "https://postman-echo.com"
verify: False verify: False
export: ["session_foo2"]
teststeps: 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: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
@@ -10,6 +10,7 @@ class TestCaseRequestWithFunctions(HttpRunner):
.variables(**{"foo1": "session_bar1"}) .variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com") .base_url("https://postman-echo.com")
.verify(False) .verify(False)
.export(*["session_foo2"])
) )
teststeps = [ teststeps = [
@@ -11,3 +11,20 @@ teststeps:
variables: variables:
foo1: override_bar1 foo1: override_bar1
testcase: request_methods/request_with_functions.yml 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 # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os import os
@@ -26,6 +26,23 @@ class TestCaseRequestWithTestcaseReference(HttpRunner):
RunTestCase("request with functions") RunTestCase("request with functions")
.with_variables(**{"foo1": "override_bar1"}) .with_variables(**{"foo1": "override_bar1"})
.call(RequestWithFunctions) .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: examples/postman_echo/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 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/postman_echo/request_methods/validate_with_functions.yml # FROM: examples/postman_echo/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 @@
# NOTICE: Generated By HttpRunner. # NOTICE: Generated By HttpRunner v3.0.8
# FROM: examples/postman_echo/request_methods/validate_with_variables.yml # FROM: examples/postman_echo/request_methods/validate_with_variables.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+1 -4
View File
@@ -1,16 +1,13 @@
__version__ = "3.0.7" __version__ = "3.0.8"
__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
from httprunner.schema import TConfig, TStep
from httprunner.testcase import Config, Step, RunRequest, RunTestCase from httprunner.testcase import Config, Step, RunRequest, RunTestCase
__all__ = [ __all__ = [
"__version__", "__version__",
"__description__", "__description__",
"HttpRunner", "HttpRunner",
"TConfig",
"TStep",
"Config", "Config",
"Step", "Step",
"RunRequest", "RunRequest",
+2 -2
View File
@@ -1,9 +1,9 @@
import contextlib import contextlib
import logging
import sys import sys
from io import StringIO from io import StringIO
from fastapi import APIRouter from fastapi import APIRouter
from loguru import logger
from starlette.requests import Request from starlette.requests import Request
router = APIRouter() router = APIRouter()
@@ -37,6 +37,6 @@ async def debug_python(request: Request):
resp["code"] = 1 resp["code"] = 1
resp["message"] = "fail" resp["message"] = "fail"
resp["result"] = str(ex) resp["result"] = str(ex)
logging.error(resp) logger.error(resp)
return resp return resp
+2 -2
View File
@@ -1,9 +1,9 @@
import logging
import subprocess import subprocess
from typing import List from typing import List
import pkg_resources import pkg_resources
from fastapi import APIRouter from fastapi import APIRouter
from loguru import logger
router = APIRouter() router = APIRouter()
@@ -29,6 +29,6 @@ async def install_dependenies(deps: List[str]):
resp["result"][dep] = False resp["result"][dep] = False
resp["code"] = 1 resp["code"] = 1
resp["message"] = "fail" resp["message"] = "fail"
logging.error(f"failed to install dependency: {dep}") logger.error(f"failed to install dependency: {dep}")
return resp return resp
+5
View File
@@ -4,12 +4,16 @@ import sys
import pytest import pytest
from loguru import logger from loguru import logger
from sentry_sdk import capture_message
from httprunner import __description__, __version__ from httprunner import __description__, __version__
from httprunner.compat import ensure_cli_args from httprunner.compat import ensure_cli_args
from httprunner.ext.har2case import init_har2case_parser, main_har2case from httprunner.ext.har2case import init_har2case_parser, main_har2case
from httprunner.make import init_make_parser, main_make from httprunner.make import init_make_parser, main_make
from httprunner.scaffold import init_parser_scaffold, main_scaffold from httprunner.scaffold import init_parser_scaffold, main_scaffold
from httprunner.utils import init_sentry_sdk
init_sentry_sdk()
def init_parser_run(subparsers): def init_parser_run(subparsers):
@@ -20,6 +24,7 @@ def init_parser_run(subparsers):
def main_run(extra_args): def main_run(extra_args):
capture_message("start to run")
# keep compatibility with v2 # keep compatibility with v2
extra_args = ensure_cli_args(extra_args) extra_args = ensure_cli_args(extra_args)
+15 -13
View File
@@ -11,6 +11,7 @@ from requests.exceptions import (
MissingSchema, MissingSchema,
RequestException, RequestException,
) )
from sentry_sdk import capture_exception
from httprunner.schema import RequestData, ResponseData from httprunner.schema import RequestData, ResponseData
from httprunner.schema import SessionData, ReqRespData from httprunner.schema import SessionData, ReqRespData
@@ -42,20 +43,21 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
# record actual request info # record actual request info
request_headers = dict(resp_obj.request.headers) request_headers = dict(resp_obj.request.headers)
request_cookies = resp_obj.request._cookies.get_dict() 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") request_content_type = lower_dict_keys(request_headers).get("content-type")
if request_content_type and "multipart/form-data" in request_content_type: if request_content_type and "multipart/form-data" in request_content_type:
# upload file type # upload file type
+13 -8
View File
@@ -2,11 +2,11 @@
This module handles compatibility issues between testcase format v2 and v3. This module handles compatibility issues between testcase format v2 and v3.
""" """
import os import os
import sys
from typing import List, Dict, Text, Union from typing import List, Dict, Text, Union
from loguru import logger from loguru import logger
from httprunner import exceptions
from httprunner.loader import load_project_meta from httprunner.loader import load_project_meta
from httprunner.utils import sort_dict_by_custom_order from httprunner.utils import sort_dict_by_custom_order
@@ -28,9 +28,8 @@ def convert_jmespath(raw: Text) -> Text:
elif item.isdigit(): elif item.isdigit():
# convert lst.0.name to lst[0].name # convert lst.0.name to lst[0].name
if len(raw_list) == 0: if len(raw_list) == 0:
raise exceptions.FileFormatError( logger.error(f"Invalid jmespath: {raw}")
f"Invalid jmespath: {raw}, jmespath should startswith headers/body/status_code/cookies" sys.exit(1)
)
last_item = raw_list.pop() last_item = raw_list.pop()
item = f"{last_item}[{item}]" item = f"{last_item}[{item}]"
@@ -60,7 +59,8 @@ def convert_extractors(extractors: Union[List, Dict]) -> Dict:
elif isinstance(extractors, Dict): elif isinstance(extractors, Dict):
v3_extractors = extractors v3_extractors = extractors
else: else:
raise exceptions.FileFormatError(f"Invalid extractor: {extractors}") logger.error(f"Invalid extractor: {extractors}")
sys.exit(1)
for k, v in v3_extractors.items(): for k, v in v3_extractors.items():
v3_extractors[k] = convert_jmespath(v) v3_extractors[k] = convert_jmespath(v)
@@ -133,7 +133,10 @@ def ensure_step_attachment(step: Dict) -> Dict:
test_dict["teardown_hooks"] = step["teardown_hooks"] test_dict["teardown_hooks"] = step["teardown_hooks"]
if "extract" in step: 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: if "validate" in step:
test_dict["validate"] = convert_validators(step["validate"]) 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"]: for step in test_content["teststeps"]:
teststep = {} teststep = {}
teststep.update(ensure_step_attachment(step))
if "request" in step: if "request" in step:
teststep["request"] = step.pop("request") teststep["request"] = step.pop("request")
elif "api" in step: elif "api" in step:
@@ -171,7 +176,6 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
elif "testcase" in step: elif "testcase" in step:
teststep["testcase"] = step.pop("testcase") teststep["testcase"] = step.pop("testcase")
teststep.update(ensure_step_attachment(step))
teststep = sort_step_by_custom_order(teststep) teststep = sort_step_by_custom_order(teststep)
v3_content["teststeps"].append(teststep) v3_content["teststeps"].append(teststep)
@@ -207,7 +211,8 @@ def generate_conftest_for_summary(args: List):
# FIXME: several test paths maybe specified # FIXME: several test paths maybe specified
break break
else: 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) project_meta = load_project_meta(test_path)
conftest_path = os.path.join(project_meta.PWD, "conftest.py") conftest_path = os.path.join(project_meta.PWD, "conftest.py")
+2
View File
@@ -12,6 +12,7 @@ import os
import sys import sys
from loguru import logger from loguru import logger
from sentry_sdk import capture_message
from httprunner.ext.har2case.core import HarParser from httprunner.ext.har2case.core import HarParser
@@ -69,6 +70,7 @@ def main_har2case(args):
else: else:
output_file_type = "pytest" output_file_type = "pytest"
capture_message(f"har2case {output_file_type}")
HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type) HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
return 0 return 0
+9 -6
View File
@@ -5,6 +5,7 @@ import sys
import urllib.parse as urlparse import urllib.parse as urlparse
from loguru import logger from loguru import logger
from sentry_sdk import capture_exception
from httprunner.ext.har2case import utils from httprunner.ext.har2case import utils
from httprunner.make import make_testcase, format_pytest_with_black from httprunner.make import make_testcase, format_pytest_with_black
@@ -238,14 +239,11 @@ class HarParser(object):
try: try:
resp_content_json = json.loads(content) resp_content_json = json.loads(content)
except JSONDecodeError: except JSONDecodeError:
logger.warning( logger.warning(f"response content can not be loaded as json: {content}")
"response content can not be loaded as json: {}".format(
content.encode("utf-8")
)
)
return return
if not isinstance(resp_content_json, dict): if not isinstance(resp_content_json, dict):
# e.g. ['a', 'b']
return return
for key, value in resp_content_json.items(): 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}") logger.info(f"Start to generate testcase from {self.har_file_path}")
harfile = os.path.splitext(self.har_file_path)[0] 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)) logger.debug("prepared testcase: {}".format(testcase))
if file_type == "JSON": if file_type == "JSON":
+9 -7
View File
@@ -1,11 +1,12 @@
import io import io
import json import json
import logging
import sys import sys
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from urllib.parse import unquote from urllib.parse import unquote
import yaml import yaml
from loguru import logger
from sentry_sdk import capture_exception
def load_har_log_entries(file_path): def load_har_log_entries(file_path):
@@ -32,8 +33,9 @@ def load_har_log_entries(file_path):
try: try:
content_json = json.loads(f.read()) content_json = json.loads(f.read())
return content_json["log"]["entries"] return content_json["log"]["entries"]
except (KeyError, TypeError, JSONDecodeError): except (KeyError, TypeError, JSONDecodeError) as ex:
logging.error("HAR file content error: {}".format(file_path)) capture_exception(ex)
logger.error("HAR file content error: {}".format(file_path))
sys.exit(1) sys.exit(1)
@@ -103,20 +105,20 @@ def convert_list_to_dict(origin_list):
def dump_yaml(testcase, yaml_file): def dump_yaml(testcase, yaml_file):
""" dump HAR entries to yaml testcase """ 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: with io.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
) )
logging.info("Generate YAML testcase successfully: {}".format(yaml_file)) logger.info("Generate YAML testcase successfully: {}".format(yaml_file))
def dump_json(testcase, json_file): def dump_json(testcase, json_file):
""" dump HAR entries to json testcase """ 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: with io.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)
@@ -125,4 +127,4 @@ def dump_json(testcase, json_file):
outfile.write(my_json_str) outfile.write(my_json_str)
logging.info("Generate JSON testcase successfully: {}".format(json_file)) logger.info("Generate JSON testcase successfully: {}".format(json_file))
+5 -2
View File
@@ -290,8 +290,11 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
return os.path.abspath(file_path) return os.path.abspath(file_path)
# current working directory # current working directory
if os.path.abspath(start_dir_path) == os.getcwd(): cwd = os.getcwd()
raise exceptions.FileNotFound(f"{file_name} not found in {start_path}") 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 # system root dir
# Windows, e.g. 'E:\\' # Windows, e.g. 'E:\\'
+32 -12
View File
@@ -5,8 +5,9 @@ from typing import Text, List, Tuple, Dict, Set, NoReturn
import jinja2 import jinja2
from loguru import logger 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.compat import ensure_testcase_v3_api, ensure_testcase_v3
from httprunner.loader import ( from httprunner.loader import (
load_folder_files, load_folder_files,
@@ -24,7 +25,7 @@ make_files_cache_set: Set = set()
pytest_files_set: Set = set() pytest_files_set: Set = set()
__TEMPLATE__ = jinja2.Template( __TEMPLATE__ = jinja2.Template(
"""# NOTICE: Generated By HttpRunner. """# NOTICE: Generated By HttpRunner v{{ version }}
# FROM: {{ testcase_path }} # FROM: {{ testcase_path }}
{% if imports_list %} {% if imports_list %}
import os import os
@@ -131,6 +132,7 @@ def format_pytest_with_black(*python_paths: Text) -> NoReturn:
try: try:
subprocess.run(["black", *python_paths]) subprocess.run(["black", *python_paths])
except subprocess.CalledProcessError as ex: except subprocess.CalledProcessError as ex:
capture_exception(ex)
logger.error(ex) logger.error(ex)
@@ -147,6 +149,9 @@ def make_config_chain_style(config: Dict) -> Text:
if "verify" in config: if "verify" in config:
config_chain_style += f'.verify({config["verify"]})' config_chain_style += f'.verify({config["verify"]})'
if "export" in config:
config_chain_style += f'.export(*{config["export"]})'
return config_chain_style return config_chain_style
@@ -173,6 +178,10 @@ def make_request_chain_style(request: Dict) -> Text:
data = f'"{data}"' data = f'"{data}"'
request_chain_style += f".with_data({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: if "timeout" in request:
timeout = request["timeout"] timeout = request["timeout"]
request_chain_style += f".set_timeout({timeout})" request_chain_style += f".set_timeout({timeout})"
@@ -198,7 +207,7 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
elif teststep.get("testcase"): elif teststep.get("testcase"):
step_info = f'RunTestCase("{teststep["name"]}")' step_info = f'RunTestCase("{teststep["name"]}")'
else: else:
raise exceptions.TestCaseFormatError raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}")
if "variables" in teststep: if "variables" in teststep:
variables = teststep["variables"] variables = teststep["variables"]
@@ -211,11 +220,18 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
call_ref_testcase = f".call({testcase})" call_ref_testcase = f".call({testcase})"
step_info += call_ref_testcase step_info += call_ref_testcase
if "extract" in teststep: extract_info = teststep.get("extract")
step_info += ".extract()" if extract_info:
if isinstance(extract_info, Dict):
for extract_name, extract_path in teststep["extract"].items(): # request step
step_info += f'.with_jmespath("{extract_path}", "{extract_name}")' 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: if "validate" in teststep:
step_info += ".validate()" step_info += ".validate()"
@@ -298,6 +314,7 @@ def make_testcase(
) )
data = { data = {
"version": __version__,
"testcase_path": __ensure_cwd_relative(testcase_path), "testcase_path": __ensure_cwd_relative(testcase_path),
"class_name": f"TestCase{testcase_cls_name}", "class_name": f"TestCase{testcase_cls_name}",
"imports_list": imports_list, "imports_list": imports_list,
@@ -326,10 +343,10 @@ def make_testsuite(testsuite: Dict) -> NoReturn:
# validate testsuite format # validate testsuite format
load_testsuite(testsuite) load_testsuite(testsuite)
config = testsuite["config"] testsuite_config = testsuite["config"]
testsuite_path = config["path"] testsuite_path = testsuite_config["path"]
testsuite_variables = config.get("variables", {}) testsuite_variables = testsuite_config.get("variables", {})
if isinstance(testsuite_variables, Text): if isinstance(testsuite_variables, Text):
# get variables by function, e.g. ${get_variables()} # get variables by function, e.g. ${get_variables()}
project_meta = load_project_meta(testsuite_path) project_meta = load_project_meta(testsuite_path)
@@ -357,9 +374,12 @@ def make_testsuite(testsuite: Dict) -> NoReturn:
# override testcase name # override testcase name
testcase_dict["config"]["name"] = testcase["name"] testcase_dict["config"]["name"] = testcase["name"]
# override base_url # 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: if base_url:
testcase_dict["config"]["base_url"] = 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 # override variables
testcase_dict["config"].setdefault("variables", {}) testcase_dict["config"].setdefault("variables", {})
testcase_dict["config"]["variables"].update(testcase.get("variables", {})) testcase_dict["config"]["variables"].update(testcase.get("variables", {}))
+8 -3
View File
@@ -3,6 +3,8 @@ import builtins
import re import re
from typing import Any, Set, Text, Callable, List, Dict from typing import Any, Set, Text, Callable, List, Dict
from sentry_sdk import capture_exception
from httprunner import loader, utils, exceptions from httprunner import loader, utils, exceptions
from httprunner.schema import VariablesMapping, FunctionsMapping 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): for var_tuple in variable_regex_compile.findall(content):
vars_list.append(var_tuple[0] or var_tuple[1]) vars_list.append(var_tuple[0] or var_tuple[1])
return vars_list return vars_list
except TypeError: except TypeError as ex:
capture_exception(ex)
return [] return []
@@ -102,7 +105,8 @@ def regex_findall_functions(content: Text) -> List[Text]:
""" """
try: try:
return function_regex_compile.findall(content) return function_regex_compile.findall(content)
except TypeError: except TypeError as ex:
capture_exception(ex)
return [] return []
@@ -358,7 +362,8 @@ def parse_data(
# content in string format may contains variables and functions # content in string format may contains variables and functions
variables_mapping = variables_mapping or {} variables_mapping = variables_mapping or {}
functions_mapping = functions_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) return parse_string(raw_data, variables_mapping, functions_mapping)
elif isinstance(raw_data, (list, set, tuple)): elif isinstance(raw_data, (list, set, tuple)):
+13
View File
@@ -46,6 +46,7 @@ class HttpRunner(object):
__step_datas: List[StepData] = None __step_datas: List[StepData] = None
__session: HttpSession = None __session: HttpSession = None
__session_variables: VariablesMapping = {} __session_variables: VariablesMapping = {}
__export_variables: VariablesMapping = {}
# time # time
__start_at: float = 0 __start_at: float = 0
__duration: float = 0 __duration: float = 0
@@ -101,6 +102,7 @@ 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["verify"] = self.__config.verify
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {}) parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
# request # request
@@ -147,6 +149,8 @@ class HttpRunner(object):
except ValidationFailure: except ValidationFailure:
self.__session.data.success = False self.__session.data.success = False
log_req_resp_details() log_req_resp_details()
# log testcase duration before raise ValidationFailure
self.__duration = time.time() - self.__start_at
raise raise
finally: finally:
# save request & response meta data # save request & response meta data
@@ -248,6 +252,7 @@ class HttpRunner(object):
self.__step_datas: List[StepData] = [] self.__step_datas: List[StepData] = []
self.__session = self.__session or HttpSession() self.__session = self.__session or HttpSession()
self.__session_variables = {} self.__session_variables = {}
self.__export_variables = {}
# run teststeps # run teststeps
for step in self.__teststeps: for step in self.__teststeps:
@@ -269,6 +274,11 @@ class HttpRunner(object):
self.__session_variables.update(extract_mapping) self.__session_variables.update(extract_mapping)
self.__duration = time.time() - self.__start_at 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 return self
def run_path(self, path: Text) -> "HttpRunner": def run_path(self, path: Text) -> "HttpRunner":
@@ -293,6 +303,9 @@ class HttpRunner(object):
return self.__step_datas return self.__step_datas
def get_export_variables(self) -> Dict: def get_export_variables(self) -> Dict:
if self.__export_variables:
return self.__export_variables
export_vars_mapping = {} export_vars_mapping = {}
for var_name in self.__config.export: for var_name in self.__config.export:
if var_name not in self.__session_variables: if var_name not in self.__session_variables:
+2
View File
@@ -2,6 +2,7 @@ import os.path
import sys import sys
from loguru import logger from loguru import logger
from sentry_sdk import capture_message
def init_parser_scaffold(subparsers): def init_parser_scaffold(subparsers):
@@ -140,5 +141,6 @@ def sleep(n_secs):
def main_scaffold(args): def main_scaffold(args):
capture_message("startproject with scaffold")
create_scaffold(args.project_name) create_scaffold(args.project_name)
sys.exit(0) sys.exit(0)
+1 -1
View File
@@ -66,7 +66,7 @@ class TStep(BaseModel):
variables: VariablesMapping = {} variables: VariablesMapping = {}
setup_hooks: Hook = [] setup_hooks: Hook = []
teardown_hooks: Hook = [] teardown_hooks: Hook = []
extract: Dict[Text, Text] = {} extract: Union[Dict[Text, Text], List[Text]] = {}
validators: Validators = Field([], alias="validate") validators: Validators = Field([], alias="validate")
validate_script: List[Text] = [] validate_script: List[Text] = []
+61 -30
View File
@@ -16,6 +16,7 @@ class Config(object):
self.__variables = {} self.__variables = {}
self.__base_url = "" self.__base_url = ""
self.__verify = False self.__verify = False
self.__export = []
caller_frame = inspect.stack()[1] caller_frame = inspect.stack()[1]
self.__path = caller_frame.filename self.__path = caller_frame.filename
@@ -40,45 +41,52 @@ class Config(object):
self.__verify = verify self.__verify = verify
return self return self
def export(self, *export_var_name: Text) -> "Config":
self.__export.extend(export_var_name)
return self
def perform(self) -> TConfig: def perform(self) -> TConfig:
return TConfig( return TConfig(
name=self.__name, name=self.__name,
base_url=self.__base_url, base_url=self.__base_url,
verify=self.__verify, verify=self.__verify,
variables=self.__variables, variables=self.__variables,
export=list(set(self.__export)),
path=self.__path, path=self.__path,
) )
class StepValidation(object): class StepRequestValidation(object):
def __init__(self, step: TStep): def __init__(self, step: TStep):
self.__t_step = step 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]}) self.__t_step.validators.append({"equal": [jmes_path, expected_value]})
return self return self
def assert_not_equal( def assert_not_equal(
self, jmes_path: Text, expected_value: Any self, jmes_path: Text, expected_value: Any
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"not_equal": [jmes_path, expected_value]}) self.__t_step.validators.append({"not_equal": [jmes_path, expected_value]})
return self return self
def assert_greater_than( def assert_greater_than(
self, jmes_path: Text, expected_value: Union[int, float] self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"greater_than": [jmes_path, expected_value]}) self.__t_step.validators.append({"greater_than": [jmes_path, expected_value]})
return self return self
def assert_less_than( def assert_less_than(
self, jmes_path: Text, expected_value: Union[int, float] self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"less_than": [jmes_path, expected_value]}) self.__t_step.validators.append({"less_than": [jmes_path, expected_value]})
return self return self
def assert_greater_or_equals( def assert_greater_or_equals(
self, jmes_path: Text, expected_value: Union[int, float] self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append( self.__t_step.validators.append(
{"greater_or_equals": [jmes_path, expected_value]} {"greater_or_equals": [jmes_path, expected_value]}
) )
@@ -86,19 +94,19 @@ class StepValidation(object):
def assert_less_or_equals( def assert_less_or_equals(
self, jmes_path: Text, expected_value: Union[int, float] self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"less_or_equals": [jmes_path, expected_value]}) self.__t_step.validators.append({"less_or_equals": [jmes_path, expected_value]})
return self return self
def assert_length_equal( def assert_length_equal(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"length_equal": [jmes_path, expected_value]}) self.__t_step.validators.append({"length_equal": [jmes_path, expected_value]})
return self return self
def assert_length_greater_than( def assert_length_greater_than(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append( self.__t_step.validators.append(
{"length_greater_than": [jmes_path, expected_value]} {"length_greater_than": [jmes_path, expected_value]}
) )
@@ -106,7 +114,7 @@ class StepValidation(object):
def assert_length_less_than( def assert_length_less_than(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append( self.__t_step.validators.append(
{"length_less_than": [jmes_path, expected_value]} {"length_less_than": [jmes_path, expected_value]}
) )
@@ -114,7 +122,7 @@ class StepValidation(object):
def assert_length_greater_or_equals( def assert_length_greater_or_equals(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append( self.__t_step.validators.append(
{"length_greater_or_equals": [jmes_path, expected_value]} {"length_greater_or_equals": [jmes_path, expected_value]}
) )
@@ -122,7 +130,7 @@ class StepValidation(object):
def assert_length_less_or_equals( def assert_length_less_or_equals(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append( self.__t_step.validators.append(
{"length_less_or_equals": [jmes_path, expected_value]} {"length_less_or_equals": [jmes_path, expected_value]}
) )
@@ -130,41 +138,43 @@ class StepValidation(object):
def assert_string_equals( def assert_string_equals(
self, jmes_path: Text, expected_value: int self, jmes_path: Text, expected_value: int
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"string_equals": [jmes_path, expected_value]}) self.__t_step.validators.append({"string_equals": [jmes_path, expected_value]})
return self return self
def assert_startswith( def assert_startswith(
self, jmes_path: Text, expected_value: Text self, jmes_path: Text, expected_value: Text
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"startswith": [jmes_path, expected_value]}) self.__t_step.validators.append({"startswith": [jmes_path, expected_value]})
return self return self
def assert_endswith( def assert_endswith(
self, jmes_path: Text, expected_value: Text self, jmes_path: Text, expected_value: Text
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"endswith": [jmes_path, expected_value]}) self.__t_step.validators.append({"endswith": [jmes_path, expected_value]})
return self return self
def assert_regex_match( def assert_regex_match(
self, jmes_path: Text, expected_value: Text self, jmes_path: Text, expected_value: Text
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]}) self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]})
return self 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]}) self.__t_step.validators.append({"contains": [jmes_path, expected_value]})
return self return self
def assert_contained_by( def assert_contained_by(
self, jmes_path: Text, expected_value: Any self, jmes_path: Text, expected_value: Any
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"contained_by": [jmes_path, expected_value]}) self.__t_step.validators.append({"contained_by": [jmes_path, expected_value]})
return self return self
def assert_type_match( def assert_type_match(
self, jmes_path: Text, expected_value: Text self, jmes_path: Text, expected_value: Text
) -> "StepValidation": ) -> "StepRequestValidation":
self.__t_step.validators.append({"type_match": [jmes_path, expected_value]}) self.__t_step.validators.append({"type_match": [jmes_path, expected_value]})
return self return self
@@ -172,11 +182,11 @@ class StepValidation(object):
return self.__t_step return self.__t_step
class StepExtraction(object): class StepRequestExtraction(object):
def __init__(self, step: TStep): def __init__(self, step: TStep):
self.__t_step = step 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 self.__t_step.extract[var_name] = jmes_path
return self return self
@@ -188,8 +198,8 @@ class StepExtraction(object):
# # TODO: extract response json with jsonpath # # TODO: extract response json with jsonpath
# pass # pass
def validate(self) -> StepValidation: def validate(self) -> StepRequestValidation:
return StepValidation(self.__t_step) return StepRequestValidation(self.__t_step)
def perform(self) -> TStep: def perform(self) -> TStep:
return self.__t_step return self.__t_step
@@ -215,6 +225,10 @@ class RequestWithOptionalArgs(object):
self.__t_step.request.data = data self.__t_step.request.data = data
return self 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": def set_timeout(self, timeout: float) -> "RequestWithOptionalArgs":
self.__t_step.request.timeout = timeout self.__t_step.request.timeout = timeout
return self return self
@@ -234,11 +248,11 @@ class RequestWithOptionalArgs(object):
# def hooks(self): # def hooks(self):
# pass # pass
def extract(self) -> StepExtraction: def extract(self) -> StepRequestExtraction:
return StepExtraction(self.__t_step) return StepRequestExtraction(self.__t_step)
def validate(self) -> StepValidation: def validate(self) -> StepRequestValidation:
return StepValidation(self.__t_step) return StepRequestValidation(self.__t_step)
def perform(self) -> TStep: def perform(self) -> TStep:
return self.__t_step return self.__t_step
@@ -281,6 +295,19 @@ class RunRequest(object):
return RequestWithOptionalArgs(self.__t_step) 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): class RunTestCase(object):
def __init__(self, name: Text): def __init__(self, name: Text):
self.__t_step = TStep(name=name) self.__t_step = TStep(name=name)
@@ -289,9 +316,9 @@ class RunTestCase(object):
self.__t_step.variables.update(variables) self.__t_step.variables.update(variables)
return self return self
def call(self, testcase: Callable): def call(self, testcase: Callable) -> StepRefCase:
self.__t_step.testcase = testcase self.__t_step.testcase = testcase
return self return StepRefCase(self.__t_step)
def perform(self) -> TStep: def perform(self) -> TStep:
return self.__t_step return self.__t_step
@@ -301,7 +328,11 @@ class Step(object):
def __init__( def __init__(
self, self,
step: Union[ step: Union[
StepValidation, StepExtraction, RequestWithOptionalArgs, RunTestCase StepRequestValidation,
StepRequestExtraction,
RequestWithOptionalArgs,
RunTestCase,
StepRefCase,
], ],
): ):
self.__t_step = step.perform() self.__t_step = step.perform()
+11
View File
@@ -2,14 +2,25 @@ import collections
import json import json
import os.path import os.path
import platform import platform
import uuid
from typing import Dict, List, Any from typing import Dict, List, Any
import sentry_sdk
from loguru import logger from loguru import logger
from httprunner import __version__ from httprunner import __version__
from httprunner import exceptions 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): def set_os_environ(variables_mapping):
""" set variables mapping to os.environ """ set variables mapping to os.environ
""" """
Generated
+31 -1
View File
@@ -451,6 +451,32 @@ version = "0.9.1"
[package.dependencies] [package.dependencies]
requests = ">=2.0.1,<3.0.0" 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]] [[package]]
category = "main" category = "main"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
@@ -572,7 +598,7 @@ allure = ["allure-pytest"]
upload = ["requests-toolbelt", "filetype"] upload = ["requests-toolbelt", "filetype"]
[metadata] [metadata]
content-hash = "3b5147c8c95480574c9eaa8f035c536cf18535766f60f768d2e714b257511dae" content-hash = "581cacf33c8afe330e5b6a965d5e16f6266718249cbdfed0d080b7536c5c4590"
python-versions = "^3.6" python-versions = "^3.6"
[metadata.files] [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.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, {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 = [ six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+2 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "httprunner" name = "httprunner"
version = "3.0.7" version = "3.0.8"
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"
@@ -38,6 +38,7 @@ jmespath = "^0.9.5"
black = "^19.10b0" black = "^19.10b0"
pytest = "^5.4.2" pytest = "^5.4.2"
pytest-html = "^2.1.1" pytest-html = "^2.1.1"
sentry-sdk = "^0.14.4"
allure-pytest = {version = "^2.8.16", optional = true} allure-pytest = {version = "^2.8.16", optional = true}
requests-toolbelt = {version = "^0.9.1", optional = true} requests-toolbelt = {version = "^0.9.1", optional = true}
filetype = {version = "^1.0.7", optional = true} filetype = {version = "^1.0.7", optional = true}
+2 -2
View File
@@ -1,7 +1,7 @@
import os import os
import unittest import unittest
from httprunner import compat, exceptions from httprunner import compat
class TestCompat(unittest.TestCase): class TestCompat(unittest.TestCase):
@@ -19,7 +19,7 @@ class TestCompat(unittest.TestCase):
compat.convert_jmespath("body.data.buildings.0.building_id"), compat.convert_jmespath("body.data.buildings.0.building_id"),
"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") compat.convert_jmespath("2.buildings.0.building_id")
def test_convert_extractors(self): def test_convert_extractors(self):
+1 -1
View File
@@ -25,4 +25,4 @@ class TestHttpRunner(unittest.TestCase):
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 functions") self.assertEqual(result.step_datas[0].name, "request with functions")
self.assertEqual(len(result.step_datas), 1) self.assertEqual(len(result.step_datas), 2)