def __read_file(name): try: with open(file=name, mode="r", encoding='utf-8') as f: content = f.read() # solve problem caused by BOM head if content.startswith(u'\ufeff'): content = content.encode('utf-8')[3:].decode('utf-8') return content except IOError: logger.error("Failed to open file: {}".format(name)) sys.exit(-1)
def source_to_case(self, source, target="ParrotProject", include=None, exclude=None, validate_include=None, validate_exclude=None, auto_extract=False): """ :param source: source file or direcotry :param target: target directory for case output :param include: list, not matched url would be ignored in recording :param exclude: list, matched url would be ignored in recording :param validate_include: list, not matched response would be ignored in validating :param validate_exclude: list, matched response would be ignored in validating :param auto_extract: bool, for automatic identification of interface dependencies :return suite dict """ source = format(source).strip() if not (source and os.path.exists(source)): logger.error( "Source file or directory does not exist: {}".format(source)) sys.exit(-1) if source.endswith("/") or source.endswith("\\"): suite_name = get_file_name(get_file_path(source)) else: suite_name = get_file_name(source) if os.path.isdir(source): files = get_dir_files(source) else: files = [ source, ] suite_dict = copy.deepcopy(self.suite_tpl) suite_dict['config']['name'] = suite_name logger.info( "Start to parse cases from source files: {}".format(source)) for _file in files: if _file.lower().endswith('.har'): one_case = self.har_to_case(_file, target, include, exclude, validate_include, validate_exclude, auto_extract, suite_name) # elif _file.lower().endswith('.trace'): # self.charles_trace_to_case() # elif _file.lower().endswith('.txt'): # self.fiddler_txt_to_case() else: logger.warning( "Unsupported file extension: {}, ignore".format(_file)) continue # add case into suite suite_dict['test_cases'].append(one_case) logger.info("Parse finished.") self.__generate_case(suite_dict, target) return suite_dict
def har_to_case(self, source, target="ParrotProject", include=None, exclude=None, validate_include=None, validate_exclude=None, auto_extract=False, suite_name=None): """parse source har file and generate test cases :param source: source file :param target: target directory for case output :param include: list, not matched url would be ignored in recording :param exclude: list, matched url would be ignored in recording :param validate_include: list, not matched response would be ignored in validating :param validate_exclude: list, matched response would be ignored in validating :param auto_extract: bool, for automatic identification of interface dependencies :param suite_name: specified suite, new a suite as default :return suite dict """ if not (source and os.path.exists(source)): logger.error("Source file does not exist: {}".format(source)) return False if not source.lower().endswith('.har'): logger.error("The source is not a har file: {}".format(source)) return False logger.info("Start to parse source file: {}".format(source)) content = self.__read_file(source) try: har_dict = json.loads(content)['log']['entries'] except TypeError: logger.error("HAR file content error: {}".format(source)) except KeyError: logger.error("HAR file content error: {}".format(source)) return False case_dict = copy.deepcopy(self.case_tpl) case_dict['config']['name'] = get_file_name(file=source) for entry_dict in har_dict: step_dict = copy.deepcopy(self.step_tpl) self.__har_times(entry=entry_dict, step_dict=step_dict) if not self.__har_request(entry=entry_dict, step_dict=step_dict, include=include, exclude=exclude, auto_extract=auto_extract): continue if not self.__har_response(entry=entry_dict, step_dict=step_dict, include=validate_include, exclude=validate_exclude, auto_extract=auto_extract): continue logger.debug("test_step: {}".format( json.dumps(step_dict, ensure_ascii=False))) # add step into case case_dict['test_steps'].append(step_dict) if suite_name: return case_dict else: suite_dict = copy.deepcopy(self.suite_tpl) # add case into suite suite_dict['test_cases'].append(case_dict) suite_dict['config']['name'] = get_file_name(file=source) logger.info("Parse finished.") self.__generate_case(suite_dict, target) return suite_dict
def run_cases(self, suite_or_case, environment=None, interval='ms', reset_after_case=False, fail_stop=False, retry_times=0, retry_interval=100, output='.'): """ :param suite_or_case: file or directory of test suites / cases / steps :param environment: environment flag defined in test data, 'None' - only load 'global' data :param interval: interval time(ms) between each step, use the recorded interval as default :param reset_after_case: reset runtime environment after each case or not, 'no' as default :param fail_stop: stop or not when a test step failed on validation, False as default :param retry_times: max retry times when a test step failed on validation, 0 as default :param retry_interval: retry interval(ms) when a test step failed on validation, 100 as default :param output: output path for report, '.' as default :return: """ try: interval = float(interval) except ValueError: interval = 'ms' try: retry_times = int(retry_times) except ValueError: retry_times = 0 try: retry_interval = int(retry_interval) except ValueError: retry_interval = 100 # parse specified cases into dict items = self.parser.load_test_case(suite_or_case=suite_or_case, environment=environment) self.report['title'] = suite_or_case self.report['detail'] = items self.report['time']['start'] = now_ms() if not items: logger.error("Parsed {}, but get nothing.".format(suite_or_case)) return -1 for _sid, _suite in enumerate(items): self.report['summary']['suite']['total'] += 1 _suite['_report_'] = { 'id': _sid, 'name': _suite['config']['name'], 'status': True, 'cases': { 'total': 0, 'pass': 0, 'fail': 0 } } logger.info("Run test suite: {}".format(json.dumps(_suite, ensure_ascii=False))) # do hook actions before a suite logger.info(" - Do setup hook actions of the suite: {}".format(_suite['setup_hooks'])) self.do_hook_actions(_suite['setup_hooks']) for _cid, _case in enumerate(_suite['test_cases']): self.report['summary']['case']['total'] += 1 _suite['_report_']['cases']['total'] += 1 _case['_report_'] = { 'id': _cid, 'name': _case['config']['name'], 'status': True, 'steps': { 'total': 0, 'pass': 0, 'fail': 0 } } logger.info("Run test case: {}".format(json.dumps(_case, ensure_ascii=False))) # do hook actions before a case logger.info(" - Do setup hook actions of the case: {}".format(_case['setup_hooks'])) self.do_hook_actions(_case['setup_hooks']) for _tid, _step in enumerate(_case['test_steps']): self.report['summary']['step']['total'] += 1 _case['_report_']['steps']['total'] += 1 _step['_report_'] = { 'id': _tid, 'name': _step['config']['name'], 'status': True } logger.info("Run test step: {}".format(json.dumps(_step, ensure_ascii=False))) # do hook actions before a request logger.info(" - Do setup hook actions of the step: {}".format(_step['setup_hooks'])) self.do_hook_actions(_step['setup_hooks']) # handle variables, priority: suite > case > step self.__set_variables(_step['config']['variables']) self.__set_variables(_case['config']['variables']) self.__set_variables(_suite['config']['variables']) logger.info(" - Config variables of the step: {}".format(json.dumps(self.variables, ensure_ascii=False))) # handle request interval if not isinstance(interval, (int, float)): if 'time.start' in _step['request']: # use the recorded interval _span = now_timestamp_ms() - int(_step['request']['time.start']) if not self.req_span: self.req_span = _span _sleep = self.req_span - _span if self.req_span > _span else MINOR_INTERVAL_MS # higher than MAX, treat it as request of another batch if _sleep > MAX_INTERVAL_MS: _sleep = MINOR_INTERVAL_MS self.req_span = _span # reset span else: # no recorded interval, use default _sleep = MINOR_INTERVAL_MS else: # use specified interval _sleep = interval if _sleep != MINOR_INTERVAL_MS: logger.info(" - Break time, sleep for {} ms.".format(_sleep)) time.sleep(_sleep/1000.0) try_flag = True while try_flag: # run this request response = self.run_one_request(_step['request']) _step['_report_']['request'] = response['request'] _step['_report_']['response'] = response['response'] _step['_report_']['time'] = response['time'] response['response']['time'] = response['time'] # extract specified variables if 'extract' in _step['response'] and _step['response']['extract']: logger.info(" - Extract variables: {}".format(_step['response']['extract'])) self.__extract_variable(extract=_step['response']['extract'], response=response['response']) logger.debug(" - Variables after extract: {}".format(json.dumps(self.variables, ensure_ascii=False))) # do response validation _validate = self.do_validation(response=response['response'], rules=_step['validations']) _step['_report_']['validation'] = _validate if not _validate['status']: # failed logger.info(" - Test step validation failed") if fail_stop: try_flag = False break elif retry_times: logger.info("Sleep {} ms and Run this test step again..".format(retry_interval)) retry_times -= 1 time.sleep(retry_interval*1.0/1000) else: break else: break if _step['_report_']['validation']['status']: # step pass self.report['summary']['step']['pass'] += 1 _case['_report_']['steps']['pass'] += 1 else: self.report['summary']['step']['fail'] += 1 _case['_report_']['steps']['fail'] += 1 _suite['_report_']['status'] = _case['_report_']['status'] = _step['_report_']['status'] = False if not try_flag: # need to stop _suite['_report_']['cases']['fail'] += 1 self.report['summary']['case']['fail'] += 1 self.report['summary']['case']['pass'] = self.report['summary']['case']['total'] - \ self.report['summary']['case']['fail'] self.report['summary']['suite']['fail'] += 1 self.report['summary']['suite']['pass'] = self.report['summary']['suite']['total'] - \ self.report['summary']['suite']['fail'] self.report['time']['end'] = now_ms() logger.info("Stop according to your --fail-stop argument") return self.generate_report(output=output) # do hook actions after a request logger.info(" - Do teardown hook actions of the step: {}".format(_step['teardown_hooks'])) self.do_hook_actions(_step['teardown_hooks']) # do hook actions after a case logger.info(" - Do teardown hook actions of the case: {}".format(_case['teardown_hooks'])) self.do_hook_actions(_case['teardown_hooks']) if reset_after_case: # reset runtime environment after each case logger.info("Reset runtime environment after the case") self.__reset_env() if _case['_report_']['status']: # case pass _suite['_report_']['cases']['pass'] += 1 self.report['summary']['case']['pass'] += 1 else: _suite['_report_']['cases']['fail'] += 1 self.report['summary']['case']['fail'] += 1 _suite['_report_']['status'] = False # do hook actions after a suite logger.info(" - Do teardown hook actions of the suite: {}".format(_suite['teardown_hooks'])) self.do_hook_actions(_suite['teardown_hooks']) # reset runtime environment after each suite logger.info("Reset runtime environment after the suite") self.__reset_env() if _suite['_report_']['status']: # suite pass self.report['summary']['suite']['pass'] += 1 else: self.report['summary']['suite']['fail'] += 1 self.report['time']['end'] = now_ms() # generate report self.generate_report(output=output)