def test_start(self, param: Dict = None) -> "HttpRunner": """main entrance, discovered by pytest""" self.__init_tests__() self.__project_meta = self.__project_meta or load_project_meta( self.__config.path) self.__case_id = self.__case_id or str(uuid.uuid4()) self.__log_path = self.__log_path or os.path.join( self.__project_meta.RootDir, "logs", f"{self.__case_id}.run.log") log_handler = logger.add(self.__log_path, level="DEBUG") # parse config name config_variables = self.__config.variables if param: config_variables.update(param) config_variables.update(self.__session_variables) self.__config.name = parse_data(self.__config.name, config_variables, self.__project_meta.functions) if USE_ALLURE: # update allure report meta allure.dynamic.title(self.__config.name) allure.dynamic.description(f"TestCase ID: {self.__case_id}") logger.info( f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}" ) try: return self.run_testcase( TestCase(config=self.__config, teststeps=self.__teststeps)) finally: logger.remove(log_handler) logger.info(f"generate testcase log: {self.__log_path}")
def run_testcase(self, testcase: TestCase) -> "HttpRunner": """run specified testcase Examples: >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)]) >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) """ self.__config = testcase.config self.__teststeps = testcase.teststeps # prepare self.__project_meta = self.__project_meta or load_project_meta( self.__config.path) self.__parse_config(self.__config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session = self.__session or HttpSession() # save extracted variables of teststeps extracted_variables: VariablesMapping = {} # run teststeps for step in self.__teststeps: # override variables # step variables > extracted variables from previous steps step.variables = merge_variables(step.variables, extracted_variables) # step variables > testcase config variables step.variables = merge_variables(step.variables, self.__config.variables) step.variables = merge_variables(step.variables, self.__config.datasource) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions) # run step if USE_ALLURE: with allure.step(f"step: {step.name}"): extract_mapping = self.__run_step(step) else: extract_mapping = self.__run_step(step) # save extracted variables to session variables extracted_variables.update(extract_mapping) self.__session_variables.update(extracted_variables) self.__duration = time.time() - self.__start_at return self
def multipart_encoder(**kwargs): """ initialize MultipartEncoder with uploading fields. Returns: MultipartEncoder: initialized MultipartEncoder object """ def get_filetype(file_path): file_type = filetype.guess(file_path) if file_type: return file_type.mime else: return "text/html" ensure_upload_ready() fields_dict = {} for key, value in kwargs.items(): if os.path.isabs(value): # value is absolute file path _file_path = value is_exists_file = os.path.isfile(value) else: # value is not absolute file path, check if it is relative file path from rrtv_httprunner.loader import load_project_meta project_meta = load_project_meta("") _file_path = os.path.join(project_meta.RootDir, value) is_exists_file = os.path.isfile(_file_path) if is_exists_file: # value is file path to upload filename = os.path.basename(_file_path) mime_type = get_filetype(_file_path) # TODO: fix ResourceWarning for unclosed file file_handler = open(_file_path, "rb") fields_dict[key] = (filename, file_handler, mime_type) else: fields_dict[key] = value return MultipartEncoder(fields=fields_dict)
def __ensure_absolute(path: Text) -> Text: if path.startswith("./"): # Linux/Darwin, hrun ./test.yml path = path[len("./"):] elif path.startswith(".\\"): # Windows, hrun .\\test.yml path = path[len(".\\"):] path = ensure_path_sep(path) project_meta = load_project_meta(path) if os.path.isabs(path): absolute_path = path else: absolute_path = os.path.join(project_meta.RootDir, path) if not os.path.isfile(absolute_path): logger.error(f"Invalid testcase file path: {absolute_path}") sys.exit(1) return absolute_path
def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space Args: file_abs_path: absolute file path Returns: ensured valid absolute file path """ project_meta = load_project_meta(file_abs_path) raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path) file_suffix = file_suffix.lower() raw_file_relative_name = convert_relative_project_root_dir( raw_abs_file_name) if raw_file_relative_name == "": return file_abs_path path_names = [] for name in raw_file_relative_name.rstrip(os.sep).split(os.sep): if name[0] in string.digits: # ensure file name not startswith digit # 19 => T19, 2C => T2C name = f"T{name}" if name.startswith("."): # avoid ".csv" been converted to "_csv" pass else: # handle cases when directory name includes dot/hyphen/space name = name.replace(" ", "_").replace(".", "_").replace("-", "_") path_names.append(name) new_file_path = os.path.join(project_meta.RootDir, f"{os.sep.join(path_names)}{file_suffix}") return new_file_path
def test_parse_parameters_testcase(self): parameters = { "user_agent": ["iOS/10.1", "iOS/10.2"], "username-password": "******", "sum": "${calculate_two_nums(1, 2)}", } load_project_meta( os.path.join( os.path.dirname(os.path.dirname(__file__)), "examples", "postman_echo", "request_methods", ), ) parsed_params = parser.parse_parameters(parameters) self.assertEqual(len(parsed_params), 2 * 3 * 2) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.1", "sum": 3, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.1", "sum": 1, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.2", "sum": 3, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.2", "sum": 1, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.1", "sum": 3, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.1", "sum": 1, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.2", "sum": 3, }, parsed_params, ) self.assertIn( { "username": "******", "password": "******", "user_agent": "iOS/10.2", "sum": 1, }, parsed_params, )
def parse_parameters(parameters: Dict, ) -> List[Dict]: """ parse parameters and generate cartesian product. Args: parameters (Dict) parameters: parameter name and value mapping parameter value may be in three types: (1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"] (2) call built-in parameterize function, "${parameterize(account.csv)}" (3) call custom function in debugtalk.py, "${gen_app_version()}" Returns: list: cartesian product list Examples: >>> parameters = { "user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"], "username-password": "******", "app_version": "${gen_app_version()}", } >>> parse_parameters(parameters) """ parsed_parameters_list: List[List[Dict]] = [] # load project_meta functions project_meta = loader.load_project_meta(os.getcwd()) functions_mapping = project_meta.functions for parameter_name, parameter_content in parameters.items(): parameter_name_list = parameter_name.split("-") if isinstance(parameter_content, List): # (1) data list # e.g. {"app_version": ["2.8.5", "2.8.6"]} # => [{"app_version": "2.8.5", "app_version": "2.8.6"}] # e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]} # => [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}] parameter_content_list: List[Dict] = [] for parameter_item in parameter_content: if not isinstance(parameter_item, (list, tuple)): # "2.8.5" => ["2.8.5"] parameter_item = [parameter_item] # ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"} # ["username", "password"], ["user1", "111111"] => {"username": "******", "password": "******"} parameter_content_dict = dict(zip(parameter_name_list, parameter_item)) parameter_content_list.append(parameter_content_dict) elif isinstance(parameter_content, Text): # (2) & (3) parsed_parameter_content: List = parse_data( parameter_content, {}, functions_mapping ) if not isinstance(parsed_parameter_content, List): raise exceptions.ParamsError( f"parameters content should be in List type, got {parsed_parameter_content} for {parameter_content}" ) parameter_content_list: List[Dict] = [] for parameter_item in parsed_parameter_content: if isinstance(parameter_item, Dict): # get subset by parameter name # {"app_version": "${gen_app_version()}"} # gen_app_version() => [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] # {"username-password": "******"} # get_account() => [ # {"username": "******", "password": "******"}, # {"username": "******", "password": "******"} # ] parameter_dict: Dict = { key: parameter_item[key] for key in parameter_name_list } elif isinstance(parameter_item, (List, tuple)): if len(parameter_name_list) == len(parameter_item): # {"username-password": "******"} # get_account() => [("user1", "111111"), ("user2", "222222")] parameter_dict = dict(zip(parameter_name_list, parameter_item)) else: raise exceptions.ParamsError( f"parameter names length are not equal to value length.\n" f"parameter names: {parameter_name_list}\n" f"parameter values: {parameter_item}" ) elif len(parameter_name_list) == 1: # {"user_agent": "${get_user_agent()}"} # get_user_agent() => ["iOS/10.1", "iOS/10.2"] # parameter_dict will get: {"user_agent": "iOS/10.1", "user_agent": "iOS/10.2"} parameter_dict = {parameter_name_list[0]: parameter_item} else: raise exceptions.ParamsError( f"Invalid parameter names and values:\n" f"parameter names: {parameter_name_list}\n" f"parameter values: {parameter_item}" ) parameter_content_list.append(parameter_dict) else: raise exceptions.ParamsError( f"parameter content should be List or Text(variables or functions call), got {parameter_content}" ) parsed_parameters_list.append(parameter_content_list) return utils.gen_cartesian_product(*parsed_parameters_list)