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: if not isinstance(raw, Text): raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}") # content.xx/json.xx => body.xx if raw.startswith("content"): raw = f"body{raw[len('content'):]}" elif raw.startswith("json"): raw = f"body{raw[len('json'):]}" raw_list = [] for item in raw.split("."): if "-" in item: # add quotes for field with separator # e.g. headers.Content-Type => headers."Content-Type" item = item.strip('"') raw_list.append(f'"{item}"') elif item.isdigit(): # convert lst.0.name to lst[0].name if len(raw_list) == 0: logger.error(f"Invalid jmespath: {raw}") sys.exit(1) last_item = raw_list.pop() item = f"{last_item}[{item}]" raw_list.append(item) else: raw_list.append(item) return ".".join(raw_list)
def ensure_testcase_v3(test_content: Dict) -> Dict: logger.info("ensure compatibility with testcase format v2") v3_content = {"config": test_content["config"], "teststeps": []} if "teststeps" not in test_content: logger.error(f"Miss teststeps: {test_content}") sys.exit(1) if not isinstance(test_content["teststeps"], list): logger.error( f'teststeps should be list type, got {type(test_content["teststeps"])}: {test_content["teststeps"]}' ) sys.exit(1) for step in test_content["teststeps"]: teststep = {} if "request" in step: teststep["request"] = _sort_request_by_custom_order(step.pop("request")) elif "api" in step: teststep["testcase"] = step.pop("api") elif "testcase" in step: teststep["testcase"] = step.pop("testcase") else: raise exceptions.TestCaseFormatError(f"Invalid teststep: {step}") teststep.update(_ensure_step_attachment(step)) teststep = _sort_step_by_custom_order(teststep) v3_content["teststeps"].append(teststep) return v3_content
def _ensure_step_attachment(step: Dict) -> Dict: test_dict = { "name": step["name"], } if "variables" in step: test_dict["variables"] = step["variables"] if "setup_hooks" in step: test_dict["setup_hooks"] = step["setup_hooks"] if "teardown_hooks" in step: test_dict["teardown_hooks"] = step["teardown_hooks"] if "extract" in step: test_dict["extract"] = _convert_extractors(step["extract"]) if "export" in step: test_dict["export"] = step["export"] if "validate" in step: if not isinstance(step["validate"], List): raise exceptions.TestCaseFormatError( f'Invalid teststep validate: {step["validate"]}' ) test_dict["validate"] = _convert_validators(step["validate"]) if "validate_script" in step: test_dict["validate_script"] = step["validate_script"] return test_dict
def load_testcase(testcase: Dict) -> TestCase: try: # validate with pydantic TestCase model testcase_obj = TestCase.parse_obj(testcase) except ValidationError as ex: err_msg = f"TestCase ValidationError:\nerror: {ex}\ncontent: {testcase}" raise exceptions.TestCaseFormatError(err_msg) return testcase_obj
def make_testsuite(testsuite: Dict) -> NoReturn: """convert valid testsuite dict to pytest folder with testcases""" # validate testsuite format load_testsuite(testsuite) testsuite_config = testsuite["config"] testsuite_path = testsuite_config["path"] testsuite_variables = convert_variables( testsuite_config.get("variables", {}), testsuite_path) logger.info(f"start to make testsuite: {testsuite_path}") # create directory with testsuite file name, put its testcases under this directory testsuite_path = ensure_file_abs_path_valid(testsuite_path) testsuite_dir, file_suffix = os.path.splitext(testsuite_path) # demo_testsuite.yml => demo_testsuite_yml testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}" for testcase in testsuite["testcases"]: # get referenced testcase content testcase_file = testcase["testcase"] testcase_path = __ensure_absolute(testcase_file) testcase_dict = load_test_file(testcase_path) testcase_dict.setdefault("config", {}) testcase_dict["config"]["path"] = testcase_path # override testcase name testcase_dict["config"]["name"] = testcase["name"] # override 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 # testsuite testcase variables > testsuite config variables testcase_variables = convert_variables(testcase.get("variables", {}), testcase_path) testcase_variables = merge_variables(testcase_variables, testsuite_variables) # testsuite testcase variables > testcase config variables testcase_dict["config"]["variables"] = convert_variables( testcase_dict["config"].get("variables", {}), testcase_path) testcase_dict["config"]["variables"].update(testcase_variables) # override weight if "weight" in testcase: testcase_dict["config"]["weight"] = testcase["weight"] # override order if "order" in testcase: try: if not isinstance(testcase.get('order'), int) and testcase.get( 'order') not in ('first', 'second', 'last', 'second_to_last'): raise exceptions.TestCaseFormatError( 'order is must be an number, or choise in "first", "second", "last" and "second_to_last"' ) testcase_dict["config"]["order"] = testcase["order"] except exceptions.TestCaseFormatError as ex: logger.warning(f"Invalid order value: : {ex}") break # make testcase testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir) pytest_files_run_set.add(testcase_pytest_path)
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: """convert valid testcase dict to pytest file path""" # ensure compatibility with testcase format v2 testcase = ensure_testcase_v3(testcase) # validate testcase format load_testcase(testcase) testcase_abs_path = __ensure_absolute(testcase["config"]["path"]) logger.info(f"start to make testcase: {testcase_abs_path}") testcase_python_abs_path, testcase_cls_name = convert_testcase_path( testcase_abs_path) if dir_path: testcase_python_abs_path = os.path.join( dir_path, os.path.basename(testcase_python_abs_path)) global pytest_files_made_cache_mapping if testcase_python_abs_path in pytest_files_made_cache_mapping: return testcase_python_abs_path config = testcase["config"] config["path"] = convert_relative_project_root_dir( testcase_python_abs_path) config["variables"] = convert_variables(config.get("variables", {}), testcase_abs_path) # prepare reference testcase imports_list = [] teststeps = testcase["teststeps"] for teststep in teststeps: if not teststep.get("testcase"): continue # make ref testcase pytest file ref_testcase_path = __ensure_absolute(teststep["testcase"]) test_content = load_test_file(ref_testcase_path) if not isinstance(test_content, Dict): raise exceptions.TestCaseFormatError( f"Invalid teststep: {teststep}") # api in v2 format, convert to v3 testcase if "request" in test_content and "name" in test_content: test_content = ensure_testcase_v3_api(test_content) test_content.setdefault("config", {})["path"] = ref_testcase_path ref_testcase_python_abs_path = make_testcase(test_content) # override testcase export ref_testcase_export: List = test_content["config"].get("export", []) if ref_testcase_export: step_export: List = teststep.setdefault("export", []) step_export.extend(ref_testcase_export) teststep["export"] = list(set(step_export)) # prepare ref testcase class name ref_testcase_cls_name = pytest_files_made_cache_mapping[ ref_testcase_python_abs_path] teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase ref_testcase_python_relative_path = convert_relative_project_root_dir( ref_testcase_python_abs_path) ref_module_name, _ = os.path.splitext( ref_testcase_python_relative_path) ref_module_name = ref_module_name.replace(os.sep, ".") import_expr = f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" if import_expr not in imports_list: imports_list.append(import_expr) testcase_path = convert_relative_project_root_dir(testcase_abs_path) # current file compared to ProjectRootDir diff_levels = len(testcase_path.split(os.sep)) data = { "version": __version__, "testcase_path": testcase_path, "diff_levels": diff_levels, "class_name": f"TestCase{testcase_cls_name}", "imports_list": imports_list, "config_chain_style": make_config_chain_style(config), "parameters": config.get("parameters"), "order": config.get("order"), "teststeps_chain_style": [make_teststep_chain_style(step) for step in teststeps], } content = __TEMPLATE__.render(data) # ensure new file's directory exists dir_path = os.path.dirname(testcase_python_abs_path) if not os.path.exists(dir_path): os.makedirs(dir_path) with open(testcase_python_abs_path, "w", encoding="utf-8") as f: f.write(content) pytest_files_made_cache_mapping[ testcase_python_abs_path] = testcase_cls_name __ensure_testcase_module(testcase_python_abs_path) logger.info(f"generated testcase: {testcase_python_abs_path}") return testcase_python_abs_path
def make_teststep_chain_style(teststep: Dict) -> Text: if teststep.get("request"): step_info = f'RunRequest("{teststep["name"]}")' elif teststep.get("testcase"): step_info = f'RunTestCase("{teststep["name"]}")' else: raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}") if "variables" in teststep: variables = teststep["variables"] step_info += f".with_variables(**{variables})" if "setup_hooks" in teststep: setup_hooks = teststep["setup_hooks"] for hook in setup_hooks: if isinstance(hook, Text): step_info += f'.setup_hook("{hook}")' elif isinstance(hook, Dict) and len(hook) == 1: assign_var_name, hook_content = list(hook.items())[0] step_info += f'.setup_hook("{hook}", "{assign_var_name}")' else: raise exceptions.TestCaseFormatError( f"Invalid setup hook: {hook}") if teststep.get("request"): step_info += make_request_chain_style(teststep["request"]) elif teststep.get("testcase"): testcase = teststep["testcase"] call_ref_testcase = f".call({testcase})" step_info += call_ref_testcase if "teardown_hooks" in teststep: teardown_hooks = teststep["teardown_hooks"] for hook in teardown_hooks: if isinstance(hook, Text): step_info += f'.teardown_hook("{hook}")' elif isinstance(hook, Dict) and len(hook) == 1: assign_var_name, hook_content = list(hook.items())[0] step_info += f'.teardown_hook("{hook}", "{assign_var_name}")' else: raise exceptions.TestCaseFormatError( f"Invalid teardown hook: {hook}") if "extract" in teststep: # request step step_info += ".extract()" for extract_name, extract_path in teststep["extract"].items(): step_info += f""".with_jmespath('{extract_path}', '{extract_name}')""" if "export" in teststep: # reference testcase step export: List[Text] = teststep["export"] step_info += f".export(*{export})" if "validate" in teststep: step_info += ".validate()" for v in teststep["validate"]: validator = uniform_validator(v) assert_method = validator["assert"] check = validator["check"] if '"' in check: # e.g. body."user-agent" => 'body."user-agent"' check = f"'{check}'" else: check = f'"{check}"' expect = validator["expect"] if isinstance(expect, Text): expect = f'"{expect}"' message = validator["message"] if message: step_info += f".assert_{assert_method}({check}, {expect}, '{message}')" else: step_info += f".assert_{assert_method}({check}, {expect})" return f"Step({step_info})"