def _load_testcase(raw_testcase, project_mapping): """ load testcase/testsuite with api/testcase references Args: raw_testcase (list): raw testcase content loaded from JSON/YAML file: [ # config part { "config": { "name": "", "def": "suite_order()", "request": {} } }, # teststeps part { "test": {...} }, { "test": {...} } ] project_mapping (dict): project_mapping Returns: dict: loaded testcase content { "config": {}, "teststeps": [teststep11, teststep12] } """ loaded_testcase = {"config": {}, "teststeps": []} for item in raw_testcase: # TODO: add json schema validation if not isinstance(item, dict) or len(item) != 1: raise exceptions.FileFormatError( "Testcase format error: {}".format(item)) key, test_block = item.popitem() if not isinstance(test_block, dict): raise exceptions.FileFormatError( "Testcase format error: {}".format(item)) if key == "config": loaded_testcase["config"].update(test_block) elif key == "test": loaded_testcase["teststeps"].extend( _load_teststeps(test_block, project_mapping)) else: logger.log_warning( "unexpected block key: {}. block key should only be 'config' or 'test'." .format(key)) return loaded_testcase
def _check_format(file_path, content): """ check testcase format if valid """ if not content: # testcase file content is empty err_msg = u"Testcase file content is empty: {}".format(file_path) logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg) elif not isinstance(content, (list, dict)): # testcase file content does not match testcase format err_msg = u"Testcase file content format invalid: {}".format(file_path) logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg)
def load_dot_env_file(path): """ load .env file """ if not path: path = os.path.join(os.getcwd(), ".env") if not os.path.isfile(path): logger.log_debug(".env file not exist: {}".format(path)) return {} else: if not os.path.isfile(path): raise exceptions.FileNotFound( "env file not exist: {}".format(path)) logger.log_info("Loading environment variables from {}".format(path)) env_variables_mapping = {} with io.open(path, 'r', encoding='utf-8') as fp: for line in fp: if "=" in line: variable, value = line.split("=") elif ":" in line: variable, value = line.split(":") else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip()] = value.strip() return env_variables_mapping
def convert_jmespath(raw: Text) -> Text: # 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: raise exceptions.FileFormatError( f"Invalid jmespath: {raw}, jmespath should startswith headers/body/status_code/cookies" ) last_item = raw_list.pop() item = f"{last_item}[{item}]" raw_list.append(item) else: raw_list.append(item) return ".".join(raw_list)
def __extend_with_testcase_ref(raw_testinfo): """ extend with test_case reference """ testcase_path = raw_testinfo["test_case"] if testcase_path not in tests_def_mapping["testcases"]: # make compatible with Windows/Linux pwd = get_project_working_directory() testcase_path = os.path.join(pwd, *testcase_path.split("/")) loaded_testcase = load_file(testcase_path) if isinstance(loaded_testcase, list): # make compatible with version < 2.2.0 testcase_dict = load_testcase(loaded_testcase) elif isinstance(loaded_testcase, dict) and "teststeps" in loaded_testcase: # format version 2, implemented in 2.2.0 testcase_dict = load_testcase_v2(loaded_testcase) else: raise exceptions.FileFormatError( "Invalid format test_case: {}".format(testcase_path)) tests_def_mapping["testcases"][testcase_path] = testcase_dict else: testcase_dict = tests_def_mapping["testcases"][testcase_path] raw_testinfo["testcase_def"] = testcase_dict
def convert_extractors(extractors: Union[List, Dict]) -> Dict: """ convert extract list(v2) to dict(v3) Args: extractors: [{"varA": "content.varA"}, {"varB": "json.varB"}] Returns: {"varA": "body.varA", "varB": "body.varB"} """ v3_extractors: Dict = {} if isinstance(extractors, List): for extractor in extractors: for k, v in extractor.items(): v3_extractors[k] = v elif isinstance(extractors, Dict): v3_extractors = extractors else: raise exceptions.FileFormatError(f"Invalid extractor: {extractors}") for k, v in v3_extractors.items(): v3_extractors[k] = convert_jmespath(v) return v3_extractors
def load_testcase_file(testcase_file: Text) -> Tuple[Dict, TestCase]: """load testcase file and validate with pydantic model""" if not os.path.isfile(testcase_file): raise exceptions.FileNotFound( f"testcase file not exists: {testcase_file}") file_suffix = os.path.splitext(testcase_file)[1].lower() if file_suffix == ".json": testcase_content = _load_json_file(testcase_file) elif file_suffix in [".yaml", ".yml"]: testcase_content = _load_yaml_file(testcase_file) else: # '' or other suffix raise exceptions.FileFormatError( f"testcase file should be YAML/JSON format, invalid testcase file: {testcase_file}" ) try: # validate with pydantic TestCase model testcase_obj = TestCase.parse_obj(testcase_content) except ValidationError as ex: err_msg = f"Invalid testcase format: {testcase_file}" logger.error(f"{err_msg}\n{ex}") raise exceptions.TestCaseFormatError(err_msg) testcase_content["config"]["path"] = testcase_file testcase_obj.config.path = testcase_file return testcase_content, testcase_obj
def load_api_file(file_path): """ load api definition from file and store in overall_def_dict["api"] api file should be in format below: [ { "api": { "def": "api_login", "request": {}, "validate": [] } }, { "api": { "def": "api_logout", "request": {}, "validate": [] } } ] """ api_items = FileUtils.load_file(file_path) if not isinstance(api_items, list): raise exceptions.FileFormatError( "API format error: {}".format(file_path)) for api_item in api_items: if not isinstance(api_item, dict) or len(api_item) != 1: raise exceptions.FileFormatError( "API format error: {}".format(file_path)) key, api_dict = api_item.popitem() if key != "api" or not isinstance(api_dict, dict) or "def" not in api_dict: raise exceptions.FileFormatError( "API format error: {}".format(file_path)) api_def = api_dict.pop("def") function_meta = parse_function(api_def) func_name = function_meta["func_name"] if func_name in TestcaseLoader.overall_def_dict["api"]: logger.log_warning( "API definition duplicated: {}".format(func_name)) api_dict["function_meta"] = function_meta TestcaseLoader.overall_def_dict["api"][func_name] = api_dict
def _check_format(file_path, content): ''' check testcase format if valid ''' if not content: # empty file err_msg = f'Testcase file content is empty: {file_path}' logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg)
def _load_json_file(json_file: Text) -> Dict: """load json file and check file content format""" with open(json_file, mode="rb") as data_file: try: json_content = json.load(data_file) except json.JSONDecodeError as ex: err_msg = f"JSONDecodeError:\nfile: {json_file}\nerror: {ex}" raise exceptions.FileFormatError(err_msg) return json_content
def _check_format(file_path, content): ''' check testcase format if vaild ''' if not content: err_msg = f'Testcase file content is empty: {file_path}' print(err_msg) raise exceptions.FileFormatError(err_msg) elif not isinstance(content, (list, dict)): err_msg = f'testcase does content format invaild: {file_path}'
def load_yaml_file(yaml_file): ''' load yaml file and check file content format ''' with open(yaml_file, 'r', encoding='utf-8') as f: yaml_content = yaml.load(f) _check_format(yaml_file, yaml_content) if not isinstance(yaml_content, dict): err_msg = f'YAML file format error: {yaml_file}' raise exceptions.FileFormatError(err_msg) return yaml_content
def _load_json_file(json_file): """ load json file and check file content format """ with io.open(json_file, encoding='utf-8') as data_file: try: json_content = json.load(data_file) except json.JSONDecodeError: err_msg = f"JSONDecodeError: JSON file format error: {json_file}" logger.error(err_msg) raise exceptions.FileFormatError(err_msg) return json_content
def load_json_file(json_file): """ load json file and check file content format """ with io.open(json_file, encoding='utf-8') as data_file: try: json_content = json.load(data_file) except exceptions.JSONDecodeError: err_msg = u"JSONDecodeError: JSON file format error: {}".format(json_file) logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg) _check_format(json_file, json_content) return json_content
def load_yaml_file(yaml_file_path): ''' load yaml file and check file content format ''' with open(yaml_file_path, 'r', encoding='utf-8') as stream: yaml_content = yaml.load(stream) _check_format(yaml_file_path, yaml_content) if not isinstance(yaml_content, (list, dict)): err_msg = f'YAML file format error: {yaml_file_path}' logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg) return yaml_content
def load_json_file(json_file_path): ''' load json file and check file content format ''' with open(json_file_path, 'r', encoding='utf-8') as data_file: try: json_content = json.load(data_file) except exceptions.JSONDecodeError: err_msg = f'JSONDecodeError: JSON file format error: {json_file_path}' logger.log_error(err_msg) raise exceptions.FileFormatError(err_msg) _check_format(json_file_path, json_content) return json_content
def __make(tests_path: Text) -> List: test_files = [] if os.path.isdir(tests_path): files_list = load_folder_files(tests_path) test_files.extend(files_list) elif os.path.isfile(tests_path): test_files.append(tests_path) else: raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}") testcase_path_list = [] for test_file in test_files: try: test_content = load_test_file(test_file) test_content.setdefault("config", {})["path"] = test_file except (exceptions.FileNotFound, exceptions.FileFormatError) as ex: logger.warning(ex) continue # testcase if "teststeps" in test_content: try: testcase_file = make_testcase(test_content) except exceptions.TestCaseFormatError: continue testcase_path_list.append(testcase_file) # testsuite elif "testcases" in test_content: try: testcase_files = make_testsuite(test_content) except exceptions.TestSuiteFormatError: continue testcase_path_list.extend(testcase_files) # invalid format else: raise exceptions.FileFormatError( f"test file is neither testcase nor testsuite: {test_file}" ) if not testcase_path_list: logger.warning(f"No valid testcase generated on {tests_path}") return [] return testcase_path_list
def __make(tests_path: Text) -> NoReturn: """ make testcase(s) with testcase/testsuite/folder absolute path generated pytest file path will be cached in make_files_cache_set Args: tests_path: should be in absolute path """ test_files = [] if os.path.isdir(tests_path): files_list = load_folder_files(tests_path) test_files.extend(files_list) elif os.path.isfile(tests_path): test_files.append(tests_path) else: raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}") for test_file in test_files: try: test_content = load_test_file(test_file) except (exceptions.FileNotFound, exceptions.FileFormatError) as ex: logger.warning(ex) continue # api in v2 format, convert to v3 testcase if "request" in test_content: test_content = ensure_testcase_v3_api(test_content) test_content.setdefault("config", {})["path"] = test_file # testcase if "teststeps" in test_content: try: __make_testcase(test_content) except exceptions.TestCaseFormatError: continue # testsuite elif "testcases" in test_content: try: __make_testsuite(test_content) except exceptions.TestSuiteFormatError: continue # invalid format else: raise exceptions.FileFormatError( f"test file is neither testcase nor testsuite: {test_file}")
def load_dot_env_file(path): """ load .env file Args: path (str): .env file path. If path is None, it will find .env file in current working directory. Returns: dict: environment variables mapping { "UserName": "******", "Password": "******", "PROJECT_KEY": "ABCDEFGH" } Raises: exceptions.FileNotFound: If specified env file is not exist. exceptions.FileFormatError: If env file format is invalid. """ if not path: path = os.path.join(os.getcwd(), ".env") if not os.path.isfile(path): logger.log_debug(".env file not exist: {}".format(path)) return {} else: if not os.path.isfile(path): raise exceptions.FileNotFound( "env file not exist: {}".format(path)) logger.log_info("Loading environment variables from {}".format(path)) env_variables_mapping = {} with io.open(path, 'r', encoding='utf-8') as fp: for line in fp: if "=" in line: variable, value = line.split("=") elif ":" in line: variable, value = line.split(":") else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip()] = value.strip() project_mapping["env"] = env_variables_mapping return env_variables_mapping
def load_test_file(test_file: Text) -> Dict: """load testcase/testsuite file content""" if not os.path.isfile(test_file): raise exceptions.FileNotFound(f"test file not exists: {test_file}") file_suffix = os.path.splitext(test_file)[1].lower() if file_suffix == ".json": test_file_content = _load_json_file(test_file) elif file_suffix in [".yaml", ".yml"]: test_file_content = _load_yaml_file(test_file) else: # '' or other suffix raise exceptions.FileFormatError( f"testcase/testsuite file should be YAML/JSON format, invalid format file: {test_file}" ) return test_file_content
def load_dot_env_file(): """ load .env file, .env file should be located in project working directory by default. If dot_env_path is specified, it will be loaded instead. Returns: dict: environment variables mapping { "UserName": "******", "Password": "******", "PROJECT_KEY": "ABCDEFGH" } Raises: exceptions.FileFormatError: If env file format is invalid. """ path = dot_env_path or os.path.join(project_working_directory, ".env") if not os.path.isfile(path): if dot_env_path: logger.log_error(".env file not exist: {}".format(dot_env_path)) sys.exit(1) else: logger.log_debug( ".env file not exist in: {}".format(project_working_directory)) return {} logger.log_info("Loading environment variables from {}".format(path)) env_variables_mapping = {} with io.open(path, 'r', encoding='utf-8') as fp: for line in fp: # maxsplit=1 if "=" in line: variable, value = line.split("=", 1) elif ":" in line: variable, value = line.split(":", 1) else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip()] = value.strip() project_mapping["env"] = env_variables_mapping utils.set_os_environ(env_variables_mapping) return env_variables_mapping
def load_dot_env_file(dot_env_path: Text) -> Dict: """load .env file. Args: dot_env_path (str): .env file path Returns: dict: environment variables mapping { "UserName": "******", "Password": "******", "PROJECT_KEY": "ABCDEFGH" } Raises: exceptions.FileFormatError: If .env file format is invalid. """ if not os.path.isfile(dot_env_path): return {} logger.info(f"Loading environment variables from {dot_env_path}") env_variables_mapping = {} with open(dot_env_path, mode="rb") as fp: for line in fp: # maxsplit=1 line = line.strip() if not len(line) or line.startswith(b"#"): continue if b"=" in line: variable, value = line.split(b"=", 1) elif b":" in line: variable, value = line.split(b":", 1) else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip().decode( "utf-8")] = value.strip().decode("utf-8") utils.set_os_environ(env_variables_mapping) return env_variables_mapping
def load_dot_env_file(dot_env_path): """ load .env file. Args: dot_env_path (str): .env file path Returns: dict: environment variables mapping { "UserName": "******", "Password": "******", "PROJECT_KEY": "ABCDEFGH" } Raises: exceptions.FileFormatError: If .env file format is invalid. """ if not os.path.isfile(dot_env_path): return {} logger.log_info( "Loading environment variables from {}".format(dot_env_path)) env_variables_mapping = {} with io.open(dot_env_path, 'r', encoding='utf-8') as fp: for line in fp: # maxsplit=1 if "=" in line: variable, value = line.split("=", 1) elif ":" in line: variable, value = line.split(":", 1) elif line.startswith("#") or line.startswith("\n"): pass else: raise exceptions.FileFormatError(".env format error") env_variables_mapping[variable.strip()] = value.strip() utils.set_os_environ(env_variables_mapping) return env_variables_mapping
def load_env_file(): ''' load .env file, .env file should be located in project working directory. Returns: dict: enviroment variables mapping { 'username':'******', 'password':'******', 'PROJECT_KEY':'ABCDEFGH' } Raises: exceptions.FileFormatError: If env file format is invalid. ''' path = os.path.join(project_working_directory, '.env') if not os.path.isfile(path): logger.log_debug( f'.env file not exist in: {project_working_directory}') return {} logger.log_info(f'Loading enviroment variables from {path}') env_variables_mapping = {} with open(path, 'r', encoding='utf-8') as fp: for line in fp: if '=' in line: variable, value = line.split('=') elif ':' in line: variable, value = line.split(':') else: raise exceptions.FileFormatError('.env format error') env_variables_mapping[variable.strip()] = value.strip() project_mapping['env'] = env_variables_mapping utils.set_os_environ(env_variables_mapping) return env_variables_mapping
def __make(tests_path: Text) -> NoReturn: """ make testcase(s) with testcase/testsuite/folder absolute path generated pytest file path will be cached in pytest_files_made_cache_mapping Args: tests_path: should be in absolute path """ test_files = [] if os.path.isdir(tests_path): files_list = load_folder_files(tests_path) test_files.extend(files_list) elif os.path.isfile(tests_path): test_files.append(tests_path) else: raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}") for test_file in test_files: if test_file.lower().endswith("_test.py"): pytest_files_run_set.add(test_file) continue try: test_content = load_test_file(test_file) except (exceptions.FileNotFound, exceptions.FileFormatError) as ex: 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 and "name" in test_content: test_content = ensure_testcase_v3_api(test_content) 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 # testcase if "teststeps" in test_content: try: testcase_pytest_path = make_testcase(test_content) pytest_files_run_set.add(testcase_pytest_path) except exceptions.TestCaseFormatError: continue # testsuite elif "testcases" in test_content: try: make_testsuite(test_content) except exceptions.TestSuiteFormatError: continue # invalid format else: logger.warning( f"skip invalid testcase/testsuite file: {test_file}")
def load_test_file(path): """ load test file, file maybe test_case/testsuite/api Args: path (str): test file path Returns: dict: loaded test content # api { "path": path, "type": "api", "name": "", "request": {} } # test_case { "path": path, "type": "test_case", "config": {}, "teststeps": [] } # testsuite { "path": path, "type": "testsuite", "config": {}, "testcases": {} } """ raw_content = load_file(path) if isinstance(raw_content, dict): if "testcases" in raw_content: # file_type: testsuite loaded_content = load_testsuite(raw_content) loaded_content["path"] = path loaded_content["type"] = "testsuite" elif "teststeps" in raw_content: # file_type: test_case (format version 2) loaded_content = load_testcase_v2(raw_content) loaded_content["path"] = path loaded_content["type"] = "test_case" elif "request" in raw_content: # file_type: api JsonSchemaChecker.validate_api_format(raw_content) loaded_content = raw_content loaded_content["path"] = path loaded_content["type"] = "api" else: # invalid format raise exceptions.FileFormatError("Invalid test file format!") elif isinstance(raw_content, list) and len(raw_content) > 0: # file_type: test_case # make compatible with version < 2.2.0 loaded_content = load_testcase(raw_content) loaded_content["path"] = path loaded_content["type"] = "test_case" else: # invalid format raise exceptions.FileFormatError("Invalid test file format!") return loaded_content
def load_testsuite(raw_testsuite): """ load testsuite with test_case references. support two different formats. Args: raw_testsuite (dict): raw testsuite content loaded from JSON/YAML file: # version 1, compatible with version < 2.2.0 { "config": { "name": "xxx", "variables": {} } "testcases": { "testcase1": { "test_case": "/path/to/test_case", "variables": {...}, "parameters": {...} }, "testcase2": {} } } # version 2, implemented in 2.2.0 { "config": { "name": "xxx", "variables": {} } "testcases": [ { "name": "testcase1", "test_case": "/path/to/test_case", "variables": {...}, "parameters": {...} }, {} ] } Returns: dict: loaded testsuite content { "config": {}, "testcases": [testcase1, testcase2] } """ raw_testcases = raw_testsuite["testcases"] if isinstance(raw_testcases, dict): # format version 1, make compatible with version < 2.2.0 JsonSchemaChecker.validate_testsuite_v1_format(raw_testsuite) raw_testsuite["testcases"] = {} for name, raw_testcase in raw_testcases.items(): __extend_with_testcase_ref(raw_testcase) raw_testcase.setdefault("name", name) raw_testsuite["testcases"][name] = raw_testcase elif isinstance(raw_testcases, list): # format version 2, implemented in 2.2.0 JsonSchemaChecker.validate_testsuite_v2_format(raw_testsuite) raw_testsuite["testcases"] = {} for raw_testcase in raw_testcases: __extend_with_testcase_ref(raw_testcase) testcase_name = raw_testcase["name"] raw_testsuite["testcases"][testcase_name] = raw_testcase else: # invalid format raise exceptions.FileFormatError("Invalid testsuite format!") return raw_testsuite
def _load_test_file(file_path): """ load testcase file or testsuite file Args: file_path (str): absolute valid file path. file_path should be in the following format: [ { "config": { "name": "", "def": "suite_order()", "request": {} } }, { "test": { "name": "add product to cart", "api": "api_add_cart()", "validate": [] } }, { "test": { "name": "add product to cart", "suite": "create_and_check()", "validate": [] } }, { "test": { "name": "checkout cart", "request": {}, "validate": [] } } ] Returns: dict: testcase dict { "config": {}, "teststeps": [teststep11, teststep12] } """ testcase = {"config": {}, "teststeps": []} for item in load_file(file_path): # TODO: add json schema validation if not isinstance(item, dict) or len(item) != 1: raise exceptions.FileFormatError( "Testcase format error: {}".format(file_path)) key, test_block = item.popitem() if not isinstance(test_block, dict): raise exceptions.FileFormatError( "Testcase format error: {}".format(file_path)) if key == "config": testcase["config"].update(test_block) elif key == "test": def extend_api_definition(block): ref_call = block["api"] def_block = _get_block_by_name(ref_call, "def-api") _extend_block(block, def_block) # reference api if "api" in test_block: extend_api_definition(test_block) testcase["teststeps"].append(test_block) # reference testcase elif "suite" in test_block: # TODO: replace suite with testcase ref_call = test_block["suite"] block = _get_block_by_name(ref_call, "def-testcase") # TODO: bugfix lost block config variables for teststep in block["teststeps"]: if "api" in teststep: extend_api_definition(teststep) testcase["teststeps"].append(teststep) # define directly else: testcase["teststeps"].append(test_block) else: logger.log_warning( "unexpected block key: {}. block key should only be 'config' or 'test'." .format(key)) return testcase
def load_test_file(file_path): """ load testcase file or suite file @param file_path: absolute valid file path file_path should be in format below: [ { "config": { "name": "", "def": "suite_order()", "request": {} } }, { "test": { "name": "add product to cart", "api": "api_add_cart()", "validate": [] } }, { "test": { "name": "checkout cart", "request": {}, "validate": [] } } ] @return testset dict { "config": {}, "testcases": [testcase11, testcase12] } """ testset = { "config": { "path": file_path }, "testcases": [] # TODO: rename to tests } for item in FileUtils.load_file(file_path): if not isinstance(item, dict) or len(item) != 1: raise exceptions.FileFormatError( "Testcase format error: {}".format(file_path)) key, test_block = item.popitem() if not isinstance(test_block, dict): raise exceptions.FileFormatError( "Testcase format error: {}".format(file_path)) if key == "config": testset["config"].update(test_block) elif key == "test": if "api" in test_block: ref_call = test_block["api"] def_block = TestcaseLoader._get_block_by_name( ref_call, "api") TestcaseLoader._override_block(def_block, test_block) testset["testcases"].append(test_block) elif "suite" in test_block: ref_call = test_block["suite"] block = TestcaseLoader._get_block_by_name( ref_call, "suite") testset["testcases"].extend(block["testcases"]) else: testset["testcases"].append(test_block) else: logger.log_warning( "unexpected block key: {}. block key should only be 'config' or 'test'." .format(key)) return testset
def _load_test_file(file_path): ''' load testcase file or testsuite file Args: file_path (str): absolute valid file path. file should be in the following format: e.g. [ { "config":{ "name":"", "def":"suite_order()", "request":{} } }, { "test":{ "name":"add product to cart", "api":"api_add_cart()", "validate":[] } }, { "test":{ "name":"add product to cart", "suite":"create_and_check()", validate":[] } }, { "test":{ "name":"checkout cart", "request":{}, "validate":[] } } ] Returns: dict: testcase dict { "config":{}, "teststeps":[teststep1,teststep2] } ''' testcase = {"config": {}, "teststeps": []} for item in load_file(file_path): if not isinstance(item, dict) or len(item) != 1: raise exceptions.FileFormatError( f'Testcase format error: {file_path}') key, test_block = item.popitem() if not isinstance(test_block, dict): raise exceptions.FileFormatError( f'Testcase format error: {file_path}') if key == "config": testcase["config"].update(test_block) elif key == 'test': def extend_api_definition(block): ref_call = block["api"] def_block = _get_block_by_name(ref_call, 'def-api') _extend_block(block, def_block) if 'api' in test_block: extend_api_definition(test_block) testcase['teststeps'].append(test_block) elif 'suite' in test_block: ref_call = test_block['suite'] block = _get_block_by_name(ref_call, 'def-testcase') for teststep in block['teststeps']: if 'api' in teststep: extend_api_definition(teststep) testcase['teststeps'].append(teststep) else: testcase['teststeps'].append(test_block) else: logger.log_warning( f'unexpected block key: {key}. block key should only be "config" or "test".' ) return testcase