def render_html_report(summary, html_report_name=None, html_report_template=None): """ render html report with specified report name and template if html_report_name is not specified, use current datetime if html_report_template is not specified, use default report template """ if not html_report_template: html_report_template = os.path.join( os.path.abspath(os.path.dirname(__file__)), "templates", "default_report_template.html") logger.log_debug("No html report template specified, use default.") else: logger.log_info("render with html report template: {}".format( html_report_template)) logger.log_info("Start to render Html report ...") logger.log_debug("render data: {}".format(summary)) report_dir_path = os.path.join(os.getcwd(), "reports") start_datetime = summary["time"]["start_at"].strftime('%Y-%m-%d-%H-%M-%S') if html_report_name: summary["html_report_name"] = html_report_name report_dir_path = os.path.join(report_dir_path, html_report_name) html_report_name += "-{}.html".format(start_datetime) else: summary["html_report_name"] = "" html_report_name = "{}.html".format(start_datetime) if not os.path.isdir(report_dir_path): os.makedirs(report_dir_path) for record in summary.get("records"): meta_data = record['meta_data'] stringify_body(meta_data, 'request') stringify_body(meta_data, 'response') with io.open(html_report_template, "r", encoding='utf-8') as fp_r: template_content = fp_r.read() report_path = os.path.join(report_dir_path, html_report_name) with io.open(report_path, 'w', encoding='utf-8') as fp_w: rendered_content = Template(template_content).render(summary) fp_w.write(rendered_content) logger.log_info("Generated Html report: {}".format(report_path)) return report_path
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 load_dot_env_file(path): """ load .env file and set to os.environ """ 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 exception.FileNotFoundError( "env file not exist: {}".format(path)) logger.log_info("Loading environment variables from {}".format(path)) with io.open(path, 'r', encoding='utf-8') as fp: for line in fp: variable, value = line.split("=") variable = variable.strip() os.environ[variable] = value.strip() logger.log_debug("Loaded variable: {}".format(variable))
def print_output(outputs): if not outputs: return content = "\n================== Variables & Output ==================\n" content += '{:<6} | {:<16} : {:<}\n'.format("Type", "Variable", "Value") content += '{:<6} | {:<16} : {:<}\n'.format("-" * 6, "-" * 16, "-" * 27) def prepare_content(var_type, in_out): content = "" for variable, value in in_out.items(): if is_py2: if isinstance(variable, unicode): variable = variable.encode("utf-8") if isinstance(value, unicode): value = value.encode("utf-8") content += '{:<6} | {:<16} : {:<}\n'.format( var_type, variable, value) return content for output in outputs: _in = output["in"] _out = output["out"] if not _out: continue content += prepare_content("Var", _in) content += "\n" content += prepare_content("Out", _out) content += "-" * 56 + "\n" logger.log_debug(content)
def run_test(self, testcase_dict): """ run single testcase. @param (dict) testcase_dict { "name": "testcase description", "skip": "skip this test unconditionally", "times": 3, "requires": [], # optional, override "function_binds": {}, # optional, override "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" }, "body": '{"name": "user", "password": "******"}' }, "extract": [], # optional "validate": [], # optional "setup_hooks": [], # optional "teardown_hooks": [] # optional } @return True or raise exception during test """ # check skip self._handle_skip_feature(testcase_dict) # prepare parsed_request = self.init_config(testcase_dict, level="testcase") self.context.bind_testcase_variable("request", parsed_request) # setup hooks setup_hooks = testcase_dict.get("setup_hooks", []) setup_hooks.insert(0, "${setup_hook_prepare_kwargs($request)}") self.do_hook_actions(setup_hooks) try: url = parsed_request.pop('url') method = parsed_request.pop('method') group_name = parsed_request.pop("group", None) except KeyError: raise exception.ParamsError("URL or METHOD missed!") logger.log_info("{method} {url}".format(method=method, url=url)) logger.log_debug("request kwargs(raw): {kwargs}".format(kwargs=parsed_request)) # request resp = self.http_client_session.request( method, url, name=group_name, **parsed_request ) resp_obj = response.ResponseObject(resp) # teardown hooks teardown_hooks = testcase_dict.get("teardown_hooks", []) if teardown_hooks: self.context.bind_testcase_variable("response", resp_obj) self.do_hook_actions(teardown_hooks) # extract extractors = testcase_dict.get("extract", []) or testcase_dict.get("extractors", []) extracted_variables_mapping = resp_obj.extract_response(extractors) self.context.bind_extracted_variables(extracted_variables_mapping) # validate validators = testcase_dict.get("validate", []) or testcase_dict.get("validators", []) try: self.context.validate(validators, resp_obj) except (exception.ParamsError, exception.ResponseError, \ exception.ValidationError, exception.ParseResponseError): # log request err_req_msg = "request: \n" err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {})) for k, v in parsed_request.items(): err_req_msg += "{}: {}\n".format(k, v) logger.log_error(err_req_msg) # log response err_resp_msg = "response: \n" err_resp_msg += "status_code: {}\n".format(resp_obj.status_code) err_resp_msg += "headers: {}\n".format(resp_obj.headers) err_resp_msg += "content: {}\n".format(resp_obj.content) logger.log_error(err_resp_msg) raise
def do_hook_actions(self, actions): for action in actions: logger.log_debug("call hook: {}".format(action)) self.context.eval_content(action)
def request(self, method, url, name=None, **kwargs): """ Constructs and sends a :py:class:`requests.Request`. Returns :py:class:`requests.Response` object. :param method: method for the new :class:`Request` object. :param url: URL for the new :class:`Request` object. :param name: (optional) Placeholder, make compatible with Locust's HttpSession :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of ``'filename': file-like-objects`` for multipart encoding upload. :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How long to wait for the server to send data before giving up, as a float, or \ a (`connect timeout, read timeout <user/advanced.html#timeouts>`_) tuple. :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ # store detail data of request and response self.meta_data = {} # prepend url with hostname unless it's already an absolute URL url = self._build_url(url) # set up pre_request hook for attaching meta data to the request object self.meta_data["method"] = method kwargs.setdefault("timeout", 120) self.meta_data["request_time"] = time.time() response = self._send_request_safe_mode(method, url, **kwargs) # record the consumed time self.meta_data["response_time_ms"] = round( (time.time() - self.meta_data["request_time"]) * 1000, 2) self.meta_data["elapsed_ms"] = response.elapsed.microseconds / 1000.0 self.meta_data["url"] = (response.history and response.history[0] or response)\ .request.url self.meta_data["request_headers"] = response.request.headers self.meta_data["request_body"] = response.request.body self.meta_data["status_code"] = response.status_code self.meta_data["response_headers"] = response.headers try: self.meta_data["response_body"] = response.json() except ValueError: self.meta_data["response_body"] = response.content msg = "response details:\n" msg += "> status_code: {}\n".format(self.meta_data["status_code"]) msg += "> headers: {}\n".format(self.meta_data["response_headers"]) msg += "> body: {}".format(self.meta_data["response_body"]) logger.log_debug(msg) # get the length of the content, but if the argument stream is set to True, we take # the size from the content-length header, in order to not trigger fetching of the body if kwargs.get("stream", False): self.meta_data["content_size"] = int( self.meta_data["response_headers"].get("content-length") or 0) else: self.meta_data["content_size"] = len(response.content or "") try: response.raise_for_status() except RequestException as e: logger.log_error(u"{exception}".format(exception=str(e))) else: logger.log_info( """status_code: {}, response_time(ms): {} ms, response_length: {} bytes""" .format(self.meta_data["status_code"], self.meta_data["response_time_ms"], self.meta_data["content_size"])) return response