def extract_response(self, extractors): """ extract value from requests.Response and store in OrderedDict. Args: extractors (list): [ {"resp_status_code": "status_code"}, {"resp_headers_content_type": "headers.content-type"}, {"resp_content": "content"}, {"resp_content_person_first_name": "content.person.name.first_name"} ] Returns: OrderDict: variable binds ordered dict """ if not extractors: return {} logger.log_debug("start to extract from response object.") extracted_variables_mapping = OrderedDict() extract_binds_order_dict = utils.ensure_mapping_format(extractors) for key, field in extract_binds_order_dict.items(): extracted_variables_mapping[key] = self.extract_field(field) return extracted_variables_mapping
def log_print(req_resp_dict, r_type): msg = "\n================== {} details ==================\n".format( r_type) for key, value in req_resp_dict[r_type].items(): if isinstance(value, dict): msg += "{:<16} : {}\n".format( key, json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False)) else: # url decoder msg += "{:<16} : {}\n".format( key, unquote(repr(value), 'utf-8')) logger.log_debug(msg)
def _send_request_safe_mode(self, method, url, **kwargs): """ Send a HTTP request, and catch any exception that might occur due to connection problems. Safe mode has been removed from requests 1.x. """ try: msg = "processed request:\n" msg += "> {method} {url}\n".format(method=method, url=url) msg += "> kwargs: {kwargs}".format(kwargs=kwargs) logger.log_debug(msg) return requests.Session.request(self, method, url, **kwargs) except (MissingSchema, InvalidSchema, InvalidURL): raise except RequestException as ex: resp = ApiResponse() resp.error = ex resp.status_code = 0 # with this status_code, content returns None resp.request = Request(method, url).prepare() return resp
def render_html_report(summary, report_template=None, report_dir=None): """ render html report with specified report name and template Args: report_template (str): specify html report template path report_dir (str): specify html report save directory """ if not report_template: report_template = os.path.join( os.path.abspath(os.path.dirname(__file__)), "templates", "report_template.html") logger.log_debug("No html report template specified, use default.") else: logger.log_info( "render with html report template: {}".format(report_template)) logger.log_info("Start to render Html report ...") report_dir = report_dir or os.path.join(os.getcwd(), "reports") if not os.path.isdir(report_dir): os.makedirs(report_dir) start_at_timestamp = int(summary["time"]["start_at"]) summary["time"]["start_datetime"] = datetime.fromtimestamp( start_at_timestamp).strftime('%Y-%m-%d %H:%M:%S') report_path = os.path.join( report_dir, "{}.html".format(summary["time"]["start_datetime"])) with io.open(report_template, "r", encoding='utf-8') as fp_r: template_content = fp_r.read() with io.open(report_path, 'w', encoding='utf-8') as fp_w: rendered_content = Template(template_content, extensions=["jinja2.ext.loopcontrols" ]).render(summary) fp_w.write(rendered_content) logger.log_info("Generated Html report: {}".format(report_path)) return report_path
def do_hook_actions(self, actions, hook_type): """ call hook actions. Args: actions (list): each action in actions list maybe in two format. format1 (dict): assignment, the value returned by hook function will be assigned to variable. {"var": "${func()}"} format2 (str): only call hook functions. ${func()} hook_type (enum): setup/teardown """ logger.log_debug("call {} hook actions.".format(hook_type)) for action in actions: if isinstance(action, dict) and len(action) == 1: # format 1 # {"var": "${func()}"} var_name, hook_content = list(action.items())[0] hook_content_eval = self.session_context.eval_content( hook_content) logger.log_debug("assignment with hook: {} = {} => {}".format( var_name, hook_content, hook_content_eval)) self.session_context.update_test_variables( var_name, hook_content_eval) else: # format 2 logger.log_debug("call hook function: {}".format(action)) # TODO: check hook function if valid self.session_context.eval_content(action)
def extract_field(self, field): """ extract value from requests.Response. """ if not isinstance(field, basestring): err_msg = u"Invalid extractor! => {}\n".format(field) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) msg = "extract: {}".format(field) if text_extractor_regexp_compile.match(field): value = self._extract_field_with_regex(field) else: value = self._extract_field_with_delimiter(field) if is_py2 and isinstance(value, unicode): value = value.encode("utf-8") msg += "\t=> {}".format(value) logger.log_debug(msg) return value
def unset_os_environ(variables_mapping): """ set variables mapping to os.environ """ for variable in variables_mapping: os.environ.pop(variable) logger.log_debug("Unset OS environment variable: {}".format(variable))
def set_os_environ(variables_mapping): """ set variables mapping to os.environ """ for variable in variables_mapping: os.environ[variable] = variables_mapping[variable] logger.log_debug("Set OS environment variable: {}".format(variable))
def _run_test(self, test_dict): """ run single teststep. Args: test_dict (dict): teststep info { "name": "teststep description", "skip": "skip this test unconditionally", "times": 3, "variables": [], # optional, override "request": { "url": "http://127.0.0.1:5000/api/users/1000", "method": "POST", "headers": { "Content-Type": "application/json", "authorization": "$authorization", "random": "$random" }, "json": {"name": "user", "password": "******"} }, "extract": {}, # optional "validate": [], # optional "setup_hooks": [], # optional "teardown_hooks": [] # optional } Raises: exceptions.ParamsError exceptions.ValidationFailure exceptions.ExtractFailure """ global tmp_extracted_variables # clear meta data first to ensure independence for each test self.__clear_test_data() # check skip self._handle_skip_feature(test_dict) # prepare test_dict = utils.lower_test_dict_keys(test_dict) test_variables = test_dict.get("variables", {}) # override variables use former extracted_variables test_variables.update(tmp_extracted_variables) self.session_context.init_test_variables(test_variables) # teststep name test_name = self.session_context.eval_content(test_dict.get( "name", "")) # parse test request raw_request = test_dict.get('request', {}) parsed_test_request = self.session_context.eval_content(raw_request) self.session_context.update_test_variables("request", parsed_test_request) # prepend url with base_url unless it's already an absolute URL url = parsed_test_request.pop('url') base_url = self.session_context.eval_content( test_dict.get("base_url", "")) parsed_url = utils.build_url(base_url, url) # setup hooks setup_hooks = test_dict.get("setup_hooks", []) if setup_hooks: self.do_hook_actions(setup_hooks, "setup") try: method = parsed_test_request.pop('method') parsed_test_request.setdefault("verify", self.verify) group_name = parsed_test_request.pop("group", None) except KeyError: raise exceptions.ParamsError("URL or METHOD missed!") # TODO: move method validation to json schema valid_methods = [ "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" ] if method.upper() not in valid_methods: err_msg = u"Invalid HTTP method! => {}\n".format(method) err_msg += "Available HTTP methods: {}".format( "/".join(valid_methods)) logger.log_error(err_msg) raise exceptions.ParamsError(err_msg) logger.log_info("{method} {url}".format(method=method, url=parsed_url)) logger.log_debug( "request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request)) # request resp = self.http_client_session.request(method, parsed_url, name=(group_name or test_name), **parsed_test_request) resp_obj = response.ResponseObject(resp) # extract extractors = test_dict.get("extract", {}) if extractors: extracted_variables_mapping = resp_obj.extract_response(extractors) tmp_extracted_variables.update(extracted_variables_mapping) self.session_context.update_session_variables( extracted_variables_mapping) self.session_context.update_session_variables(tmp_extracted_variables) # validate validators = test_dict.get("validate") or test_dict.get( "validators") or [] try: self.session_context.validate(validators, resp_obj) except (exceptions.ParamsError, exceptions.ValidationFailure, exceptions.ExtractFailure): err_msg = "{} DETAILED REQUEST & RESPONSE {}\n".format( "*" * 32, "*" * 32) # log request err_msg += "====== request details ======\n" err_msg += "url: {}\n".format(parsed_url) err_msg += "method: {}\n".format(method) err_msg += "headers: {}\n".format( parsed_test_request.pop("headers", {})) for k, v in parsed_test_request.items(): v = utils.omit_long_data(v) err_msg += "{}: {}\n".format(k, repr(v)) err_msg += "\n" # log response err_msg += "====== response details ======\n" err_msg += "status_code: {}\n".format(resp_obj.status_code) err_msg += "headers: {}\n".format(resp_obj.headers) err_msg += "body: {}\n".format(repr(resp_obj.text)) logger.log_error(err_msg) raise finally: self.validation_results = self.session_context.validation_results # teardown hooks teardown_hooks = test_dict.get("teardown_hooks", []) if teardown_hooks: self.session_context.update_test_variables( "response", resp_obj) self.do_hook_actions(teardown_hooks, "teardown")
def log_print(req_resp_dict, r_type): msg = "\n================== {} details ==================\n".format( r_type) for key, value in req_resp_dict[r_type].items(): msg += "{:<16} : {}\n".format(key, repr(value)) logger.log_debug(msg)
def validate(self, validators, resp_obj): """ make validation with comparators """ self.validation_results = [] if not validators: return logger.log_debug("start to validate.") validate_pass = True failures = [] for validator in validators: # validator should be LazyFunction object if not isinstance(validator, parser.LazyFunction): raise exceptions.ValidationFailure( "validator should be parsed first: {}".format(validators)) # evaluate validator args with context variable mapping. validator_args = validator.get_args() check_item, expect_item = validator_args check_value = self.__eval_validator_check(check_item, resp_obj) expect_value = self.__eval_validator_expect(expect_item) validator.update_args([check_value, expect_value]) comparator = validator.func_name validator_dict = { "comparator": comparator, "check": check_item, "check_value": check_value, "expect": expect_item, "expect_value": expect_value } validate_msg = "\nvalidate: {} {} {}({})".format( check_item, comparator, expect_value, type(expect_value).__name__) try: validator.to_value(self.test_variables_mapping) validator_dict["check_result"] = "pass" validate_msg += "\t==> pass" logger.log_debug(validate_msg) except (AssertionError, TypeError): validate_pass = False validator_dict["check_result"] = "fail" validate_msg += "\t==> fail" validate_msg += "\n{}({}) {} {}({})".format( check_value, type(check_value).__name__, comparator, expect_value, type(expect_value).__name__) logger.log_error(validate_msg) failures.append(validate_msg) self.validation_results.append(validator_dict) # restore validator args, in case of running multiple times validator.update_args(validator_args) if not validate_pass: failures_string = "\n".join([failure for failure in failures]) raise exceptions.ValidationFailure(failures_string)