diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py index acb70cd0..da34ceca 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py @@ -7,7 +7,7 @@ from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseRequestWithFunctions(HttpRunner): config = ( Config("request with functions") - .variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"}) + .variables(**{"var1": "testsuite_val1", "foo1": "session_bar1"}) .base_url("https://postman-echo.com") .verify(False) .export(*["session_foo2"]) diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py index 294df37f..af45b5ba 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py @@ -16,7 +16,7 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( class TestCaseRequestWithTestcaseReference(HttpRunner): config = ( Config("request with referenced testcase") - .variables(**{"foo1": "session_bar1", "var2": "testsuite_val2"}) + .variables(**{"var2": "testsuite_val2", "foo1": "session_bar1"}) .base_url("https://postman-echo.com") .verify(False) ) diff --git a/httprunner/compat.py b/httprunner/compat.py index e683eaf5..35b996f9 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -3,15 +3,49 @@ This module handles compatibility issues between testcase format v2 and v3. """ import os import sys -from typing import List, Dict, Text, Union +from typing import List, Dict, Text, Union, Any from loguru import logger from httprunner import exceptions from httprunner.loader import load_project_meta +from httprunner.parser import parse_data from httprunner.utils import sort_dict_by_custom_order, ensure_file_path_valid +def convert_variables( + raw_variables: Union[Dict, List, Text], test_path: Text +) -> Dict[Text, Any]: + + if isinstance(raw_variables, Dict): + return raw_variables + + if isinstance(raw_variables, List): + # [{"var1": 1}, {"var2": 2}] + variables: Dict[Text, Any] = {} + for var_item in raw_variables: + if not isinstance(var_item, Dict) or len(var_item) != 1: + raise exceptions.TestCaseFormatError( + f"Invalid variables format: {raw_variables}" + ) + + variables.update(var_item) + + return variables + + elif isinstance(raw_variables, Text): + # get variables by function, e.g. ${get_variables()} + project_meta = load_project_meta(test_path) + variables = parse_data(raw_variables, {}, project_meta.functions) + + return variables + + else: + raise exceptions.TestCaseFormatError( + f"Invalid variables format: {raw_variables}" + ) + + def convert_jmespath(raw: Text) -> Text: # content.xx/json.xx => body.xx if raw.startswith("content"): diff --git a/httprunner/ext/har2case/utils.py b/httprunner/ext/har2case/utils.py index cc051246..ed200995 100644 --- a/httprunner/ext/har2case/utils.py +++ b/httprunner/ext/har2case/utils.py @@ -6,7 +6,6 @@ from urllib.parse import unquote import yaml from loguru import logger -from sentry_sdk import capture_exception def load_har_log_entries(file_path): @@ -33,9 +32,11 @@ def load_har_log_entries(file_path): try: content_json = json.loads(f.read()) return content_json["log"]["entries"] - except (KeyError, TypeError, JSONDecodeError) as ex: - capture_exception(ex) - logger.error("HAR file content error: {}".format(file_path)) + except (TypeError, JSONDecodeError) as ex: + logger.error(f"failed to load HAR file {file_path}: {ex}") + sys.exit(1) + except KeyError: + logger.error(f"log entries not found in HAR file: {content_json}") sys.exit(1) @@ -53,7 +54,7 @@ def x_www_form_urlencoded(post_data): """ if isinstance(post_data, dict): return "&".join( - [u"{}={}".format(key, value) for key, value in post_data.items()] + ["{}={}".format(key, value) for key, value in post_data.items()] ) else: return post_data diff --git a/httprunner/make.py b/httprunner/make.py index 5ddc22b6..8dcdbb5c 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -1,5 +1,6 @@ import os import subprocess +import sys from shutil import copyfile from typing import Text, List, Tuple, Dict, Set, NoReturn @@ -8,7 +9,11 @@ from loguru import logger from sentry_sdk import capture_exception 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, + convert_variables, +) from httprunner.loader import ( load_folder_files, load_test_file, @@ -284,15 +289,9 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: config = testcase["config"] config["path"] = __ensure_cwd_relative(testcase_python_path) - - # parse config variables - config.setdefault("variables", {}) - if isinstance(config["variables"], Text): - # get variables by function, e.g. ${get_variables()} - project_meta = load_project_meta(testcase_abs_path) - config["variables"] = parse_data( - config["variables"], {}, project_meta.functions - ) + config["variables"] = convert_variables( + config.get("variables", {}), testcase_abs_path + ) # prepare reference testcase imports_list = [] @@ -356,14 +355,9 @@ def make_testsuite(testsuite: Dict) -> NoReturn: testsuite_config = testsuite["config"] testsuite_path = testsuite_config["path"] - - 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) - testsuite_variables = parse_data( - testsuite_variables, {}, project_meta.functions - ) + testsuite_variables = convert_variables( + testsuite_config.get("variables", {}), testsuite_path + ) logger.info(f"start to make testsuite: {testsuite_path}") @@ -391,9 +385,11 @@ def make_testsuite(testsuite: Dict) -> NoReturn: 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", {})) - testcase_dict["config"]["variables"].update(testsuite_variables) + testcase_variables = convert_variables( + testcase.get("variables", {}), testcase_path + ) + testcase_variables.update(testsuite_variables) + testcase_dict["config"]["variables"] = testcase_variables # make testcase testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir) @@ -428,12 +424,19 @@ def __make(tests_path: Text) -> NoReturn: logger.warning(ex) continue + if not isinstance(test_content, Dict): + raise exceptions.FileFormatError( + f"test content not in dict format: {test_content}" + ) + # api in v2 format, convert to v3 testcase - if "request" in test_content: + if "request" in test_content and "name" in test_content: test_content = ensure_testcase_v3_api(test_content) - if not (isinstance(test_content, Dict) and "config" in test_content): - raise exceptions.FileFormatError("Invalid testcase/testsuite v2/v3 format!") + if "config" not in test_content: + raise exceptions.FileFormatError( + f"miss config part in testcase/testsuite: {test_content}" + ) test_content.setdefault("config", {})["path"] = test_file @@ -465,7 +468,12 @@ def main_make(tests_paths: List[Text]) -> List[Text]: if not os.path.isabs(tests_path): tests_path = os.path.join(os.getcwd(), tests_path) - __make(tests_path) + try: + __make(tests_path) + except exceptions.MyBaseError as ex: + logger.error(ex) + sys.exit(1) + __ensure_project_meta_files(tests_path) # format pytest files diff --git a/tests/compat_test.py b/tests/compat_test.py index 89707db7..5e80e6d6 100644 --- a/tests/compat_test.py +++ b/tests/compat_test.py @@ -1,10 +1,34 @@ import os import unittest -from httprunner import compat +from httprunner import compat, exceptions +from httprunner.compat import convert_variables class TestCompat(unittest.TestCase): + def test_convert_variables(self): + raw_variables = [{"var1": 1}, {"var2": "val2"}] + self.assertEqual( + convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + {"var1": 1, "var2": "val2"}, + ) + raw_variables = {"var1": 1, "var2": "val2"} + self.assertEqual( + convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + {"var1": 1, "var2": "val2"}, + ) + raw_variables = "${get_variables()}" + self.assertEqual( + convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + {"foo1": "session_bar1"}, + ) + + with self.assertRaises(exceptions.TestCaseFormatError): + raw_variables = [{"var1": 1}, {"var2": "val2", "var3": 3}] + convert_variables(raw_variables, "tests/data/a-b.c/1.yml") + with self.assertRaises(exceptions.TestCaseFormatError): + convert_variables(None, "tests/data/a-b.c/1.yml") + def test_convert_jmespath(self): self.assertEqual(compat.convert_jmespath("content.abc"), "body.abc")