diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d166e67c..d6a0b667 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,7 @@ **Added** - feat: add sentry sdk +- feat: extract session variable from referenced testcase step **Fixed** 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 8164a197..345887c2 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 @@ -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 = [ 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 93e7297d..ca0232b9 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 @@ -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") ), ] diff --git a/examples/postman_echo/request_methods/request_with_functions.yml b/examples/postman_echo/request_methods/request_with_functions.yml index 6fc68325..33952cab 100644 --- a/examples/postman_echo/request_methods/request_with_functions.yml +++ b/examples/postman_echo/request_methods/request_with_functions.yml @@ -4,6 +4,7 @@ config: foo1: session_bar1 base_url: "https://postman-echo.com" verify: False + export: ["session_foo2"] teststeps: - diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index cf8a91b3..99a103e9 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -10,6 +10,7 @@ class TestCaseRequestWithFunctions(HttpRunner): .variables(**{"foo1": "session_bar1"}) .base_url("https://postman-echo.com") .verify(False) + .export(*["session_foo2"]) ) teststeps = [ diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference.yml b/examples/postman_echo/request_methods/request_with_testcase_reference.yml index 3b2bfbdc..85138047 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference.yml +++ b/examples/postman_echo/request_methods/request_with_testcase_reference.yml @@ -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"] diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index b015e9c7..24a5d107 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -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") ), ] diff --git a/httprunner/compat.py b/httprunner/compat.py index b0b0e740..54addff2 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -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) diff --git a/httprunner/make.py b/httprunner/make.py index aaab8987..66fd9a1b 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -149,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 @@ -204,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"] @@ -217,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()" diff --git a/httprunner/schema.py b/httprunner/schema.py index 59ff17f6..be60bf41 100644 --- a/httprunner/schema.py +++ b/httprunner/schema.py @@ -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] = [] diff --git a/httprunner/testcase.py b/httprunner/testcase.py index daaedc7b..fc738851 100644 --- a/httprunner/testcase.py +++ b/httprunner/testcase.py @@ -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,12 +41,17 @@ 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, ) @@ -54,7 +60,9 @@ class StepRequestValidation(object): def __init__(self, step: TStep): self.__t_step = step - def assert_equal(self, jmes_path: Text, expected_value: Any) -> "StepRequestValidation": + def assert_equal( + self, jmes_path: Text, expected_value: Any + ) -> "StepRequestValidation": self.__t_step.validators.append({"equal": [jmes_path, expected_value]}) return self @@ -152,7 +160,9 @@ class StepRequestValidation(object): self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]}) return self - def assert_contains(self, jmes_path: Text, expected_value: Any) -> "StepRequestValidation": + def assert_contains( + self, jmes_path: Text, expected_value: Any + ) -> "StepRequestValidation": self.__t_step.validators.append({"contains": [jmes_path, expected_value]}) return self @@ -285,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) @@ -293,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 @@ -305,7 +328,11 @@ class Step(object): def __init__( self, step: Union[ - StepRequestValidation, StepRequestExtraction, RequestWithOptionalArgs, RunTestCase + StepRequestValidation, + StepRequestExtraction, + RequestWithOptionalArgs, + RunTestCase, + StepRefCase, ], ): self.__t_step = step.perform() diff --git a/tests/runner_test.py b/tests/runner_test.py index 39d9d978..f0dc4a87 100644 --- a/tests/runner_test.py +++ b/tests/runner_test.py @@ -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)