示例#1
0
def convert_variables(raw_variables: Union[Dict, List, Text],
                      test_path: Text) -> Dict[Text, Any]:

    if isinstance(raw_variables, Dict):
        return raw_variables

    if isinstance(raw_variables, List):
        # [{"var1": 1}, {"var2": 2}]
        variables: Dict[Text, Any] = {}
        for var_item in raw_variables:
            if not isinstance(var_item, Dict) or len(var_item) != 1:
                raise exceptions.TestCaseFormatError(
                    f"Invalid variables format: {raw_variables}")

            variables.update(var_item)

        return variables

    elif isinstance(raw_variables, Text):
        # get variables by function, e.g. ${get_variables()}
        project_meta = load_project_meta(test_path)
        variables = parse_data(raw_variables, {}, project_meta.functions)

        return variables

    else:
        raise exceptions.TestCaseFormatError(
            f"Invalid variables format: {raw_variables}")
示例#2
0
    def test_start(self) -> "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
        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}")
示例#3
0
def __ensure_absolute(path: Text) -> Text:
    logger.info(f"[__ensure_absolute] in path={path}")
    if path.startswith("./"):
        # Linux/Darwin, hrun ./test.yml
        path = path[len("./"):]
    elif path.startswith(".\\"):
        # Windows, hrun .\\test.yml
        path = path[len(".\\"):]

    logger.debug(f"[__ensure_absolute] befor ensure_path_sep, path={path}")
    path = ensure_path_sep(path)
    project_meta = load_project_meta(path)
    logger.debug(f"[__ensure_absolute] path={path}")
    logger.debug(f"[__ensure_absolute] project_meta={project_meta}")
    logger.debug(f"[__ensure_absolute] type project_meta={type(project_meta)}")

    if os.path.isabs(path):
        absolute_path = path
    else:
        absolute_path = os.path.join(project_meta.RootDir, path)

    logger.debug(f"[__ensure_absolute] absolute_path={absolute_path}")
    if not os.path.isfile(absolute_path):
        logger.error(f"Invalid testcase file path: {absolute_path}")
        move_pytest_files_to_target(project_meta.RootDir)
        sys.exit(1)

    return absolute_path
示例#4
0
    def run(self, testcase: TestCase):
        """run testcase"""
        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()
        self.__session_variables = {}

        # run teststeps
        for step in self.teststeps:
            # update with config variables
            step.variables.update(self.config.variables)
            # update with session variables extracted from pre step
            step.variables.update(self.__session_variables)
            # parse variables
            step.variables = parse_variables_mapping(
                step.variables, self.__project_meta.functions)
            # run step
            with allure.step(f"step: {step.name}"):
                extract_mapping = self.__run_step(step)
            # save extracted variables to session variables
            self.__session_variables.update(extract_mapping)

        self.__duration = time.time() - self.__start_at
        return self
示例#5
0
文件: make.py 项目: tcw0nw/httprunner
def __ensure_absolute(path: Text) -> Text:
    project_meta = load_project_meta(path)

    if os.path.isabs(path):
        absolute_path = path
    else:
        absolute_path = os.path.join(project_meta.PWD, path)

    return absolute_path
示例#6
0
def make_testsuite(testsuite: Dict) -> List[Text]:
    """convert valid testsuite dict to pytest folder with testcases"""
    try:
        # validate testcase format
        load_testsuite(testsuite)
    except exceptions.TestSuiteFormatError as ex:
        logger.error(f"TestSuiteFormatError: {ex}")
        raise

    config = testsuite["config"]
    testsuite_path = config["path"]
    project_meta = load_project_meta(testsuite_path)
    project_working_directory = project_meta.PWD

    testsuite_variables = config.get("variables", {})
    if isinstance(testsuite_variables, Text):
        # get variables by function, e.g. ${get_variables()}
        testsuite_variables = parse_data(
            testsuite_variables, {}, project_meta.functions
        )

    logger.info(f"start to make testsuite: {testsuite_path}")

    # create directory with testsuite file name, put its testcases under this directory
    testsuite_dir = testsuite_path.replace(".", "_")
    os.makedirs(testsuite_dir, exist_ok=True)

    testcase_files = []

    for testcase in testsuite["testcases"]:
        # get referenced testcase content
        testcase_file = testcase["testcase"]
        testcase_path = os.path.join(project_working_directory, testcase_file)
        testcase_dict = load_test_file(testcase_path)
        testcase_dict.setdefault("config", {})
        testcase_dict["config"]["path"] = os.path.join(
            testsuite_dir, os.path.basename(testcase_path)
        )

        # override testcase name
        testcase_dict["config"]["name"] = testcase["name"]
        # override base_url
        base_url = testsuite["config"].get("base_url") or testcase.get("base_url")
        if base_url:
            testcase_dict["config"]["base_url"] = base_url
        # override variables
        testcase_dict["config"].setdefault("variables", {})
        testcase_dict["config"]["variables"].update(testcase.get("variables", {}))
        testcase_dict["config"]["variables"].update(testsuite_variables)

        # make testcase
        testcase_path = make_testcase(testcase_dict)
        testcase_files.append(testcase_path)

    return testcase_files
示例#7
0
def __ensure_absolute(path: Text) -> Text:
    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):
        raise exceptions.ParamsError(
            f"Invalid testcase file path: {absolute_path}")

    return absolute_path
示例#8
0
    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()
        self.__session_variables = {}

        # run teststeps
        for step in self.__teststeps:
            # override variables
            # session variables (extracted from pre step) > step variables
            step.variables.update(self.__session_variables)
            # step variables > testcase config variables
            step.variables = override_config_variables(
                step.variables, self.__config.variables
            )

            # 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
            self.__session_variables.update(extract_mapping)

        self.__duration = time.time() - self.__start_at
        return self
示例#9
0
def convert_variables(raw_variables: Union[Dict, Text],
                      test_path: Text) -> Dict[Text, Any]:

    if isinstance(raw_variables, Dict):
        return raw_variables

    elif isinstance(raw_variables, Text):
        # get variables by function, e.g. ${get_variables()}
        project_meta = load_project_meta(test_path)
        variables = parse_data(raw_variables, {}, project_meta.functions)

        return variables

    else:
        raise exceptions.TestCaseFormatError(
            f"Invalid variables format: {raw_variables}")
示例#10
0
文件: make.py 项目: tcw0nw/httprunner
def __make_testsuite(testsuite: Dict) -> NoReturn:
    """convert valid testsuite dict to pytest folder with testcases"""
    # validate testsuite format
    load_testsuite(testsuite)

    config = testsuite["config"]
    testsuite_path = config["path"]

    testsuite_variables = config.get("variables", {})
    if isinstance(testsuite_variables, Text):
        # get variables by function, e.g. ${get_variables()}
        project_meta = load_project_meta(testsuite_path)
        testsuite_variables = parse_data(testsuite_variables, {},
                                         project_meta.functions)

    logger.info(f"start to make testsuite: {testsuite_path}")

    # create directory with testsuite file name, put its testcases under this directory
    testsuite_dir = os.path.join(
        os.path.dirname(testsuite_path),
        os.path.basename(testsuite_path).replace(".", "_"),
    )
    os.makedirs(testsuite_dir, exist_ok=True)

    for testcase in testsuite["testcases"]:
        # get referenced testcase content
        testcase_file = testcase["testcase"]
        testcase_path = __ensure_absolute(testcase_file)
        testcase_dict = load_test_file(testcase_path)
        testcase_dict.setdefault("config", {})
        testcase_dict["config"]["path"] = testcase_path

        # override testcase name
        testcase_dict["config"]["name"] = testcase["name"]
        # override base_url
        base_url = testsuite["config"].get("base_url") or testcase.get(
            "base_url")
        if base_url:
            testcase_dict["config"]["base_url"] = base_url
        # override variables
        testcase_dict["config"].setdefault("variables", {})
        testcase_dict["config"]["variables"].update(
            testcase.get("variables", {}))
        testcase_dict["config"]["variables"].update(testsuite_variables)

        # make testcase
        __make_testcase(testcase_dict, testsuite_dir)
示例#11
0
    def __init(self):
        init_logger()
        self.__config = self.config.struct()
        self.__session_variables = {}
        self.__start_at = 0
        self.__duration = 0

        self.__project_meta = self.__project_meta or load_project_meta(
            self.__config.path)
        self.case_id = self.case_id or str(uuid.uuid4())
        self.root_dir = self.root_dir or self.__project_meta.RootDir
        self.__log_path = os.path.join(self.root_dir, "logs",
                                       f"{self.case_id}.run.log")

        self.__step_results.clear()
        self.session = self.session or HttpSession()
        self.parser = self.parser or Parser(self.__project_meta.functions)
示例#12
0
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 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)
示例#13
0
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
示例#14
0
def make_testcase(testcase: Dict) -> Union[str, None]:
    """convert valid testcase dict to pytest file path"""
    try:
        # validate testcase format
        load_testcase(testcase)
    except exceptions.TestCaseFormatError as ex:
        logger.error(f"TestCaseFormatError: {ex}")
        raise

    testcase_path = testcase["config"]["path"]
    logger.info(f"start to make testcase: {testcase_path}")

    template = jinja2.Template(__TMPL__)

    testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path)

    config = testcase["config"]

    config.setdefault("variables", {})
    if isinstance(config["variables"], Text):
        # get variables by function, e.g. ${get_variables()}
        project_meta = load_project_meta(testcase_path)
        config["variables"] = parse_data(
            config["variables"], {}, project_meta.functions
        )

    config["path"] = testcase_python_path
    data = {
        "testcase_path": testcase_path,
        "class_name": f"TestCase{name_in_title_case}",
        "config": config,
        "teststeps": testcase["teststeps"],
    }
    content = template.render(data)

    with open(testcase_python_path, "w") as f:
        f.write(content)

    logger.info(f"generated testcase: {testcase_python_path}")
    return testcase_python_path
示例#15
0
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
示例#16
0
    def run(self, testcase: TestCase):
        """main entrance"""
        self.config = testcase.config
        self.teststeps = testcase.teststeps
        self.config.variables.update(self.__session_variables)

        if self.config.path:
            self.__project_meta = load_project_meta(self.config.path)
        elif not self.__project_meta:
            self.__project_meta = ProjectMeta()

        def parse_config(config: TConfig):
            config.variables = parse_variables_mapping(
                config.variables, self.__project_meta.functions)
            config.name = parse_data(config.name, config.variables,
                                     self.__project_meta.functions)
            config.base_url = parse_data(config.base_url, config.variables,
                                         self.__project_meta.functions)

        parse_config(self.config)
        self.__start_at = time.time()
        self.__step_datas: List[StepData] = []
        self.__session_variables = {}
        for step in self.teststeps:
            # update with config variables
            step.variables.update(self.config.variables)
            # update with session variables extracted from pre step
            step.variables.update(self.__session_variables)
            # parse variables
            step.variables = parse_variables_mapping(
                step.variables, self.__project_meta.functions)
            # run step
            extract_mapping = self.__run_step(step)
            # save extracted variables to session variables
            self.__session_variables.update(extract_mapping)

        self.__duration = time.time() - self.__start_at
        return self
示例#17
0
def __ensure_project_meta_files(tests_path: Text) -> NoReturn:
    """ ensure project meta files exist in generated pytest folder files
        include debugtalk.py and .env
    """
    project_meta = load_project_meta(tests_path)

    # handle cases when generated pytest directory are different from original yaml/json testcases
    debugtalk_path = project_meta.debugtalk_path
    if debugtalk_path:
        debugtalk_new_path = ensure_file_path_valid(debugtalk_path)
        if debugtalk_new_path != debugtalk_path:
            logger.info(f"copy debugtalk.py to {debugtalk_new_path}")
            copyfile(debugtalk_path, debugtalk_new_path)

        global pytest_files_made_cache_mapping
        pytest_files_made_cache_mapping[debugtalk_new_path] = ""

    dot_csv_path = project_meta.dot_env_path
    if dot_csv_path:
        dot_csv_new_path = ensure_file_path_valid(dot_csv_path)
        if dot_csv_new_path != dot_csv_path:
            logger.info(f"copy .env to {dot_csv_new_path}")
            copyfile(dot_csv_path, dot_csv_new_path)
示例#18
0
    def __init__(self, testdata):
        '''
        根据用户在django系统操作的选中数据使用httprunner发送请求数据、进行前置后置处理,根据断言校验响应报文等
        :param testdata: 单条测试用例数据
        '''

        Saver.caseno = testdata['caseno']
        # 请求的基本配置
        self.config = (Config(testdata['caseno'] + '-' +
                              testdata['casename']).variables(**{}).base_url(
                                  testdata['baseurl']).verify(False))

        # 使用对应项目的debugtalk文件
        self.with_project_meta(
            load_project_meta(filedir + '/projectdata/' + testdata['project'] +
                              '/'))

        self.teststeps = []

        # 根据输入值构建请求信息
        running = RunRequest(testdata['casename'])\
        .with_variables() \
        .setup_hook('${prerequest($request)}') \
        .setup_hook(testdata['setupfunc']) \
        .__getattribute__(testdata['method'].lower())(testdata['url']) \
        .with_json(testdata['data'])\
        .with_data(testdata['formdata'])\
        .with_params(**testdata['params'])\
        .with_headers(**testdata['headers']) \
        .teardown_hook('${afterresponse($response)}') \
        .teardown_hook(testdata['teardownfunc']) \
        .validate() \

        # 根据校验参数输入值构建断言
        for ast in testdata['asserts']:
            running = running.__getattribute__(ast[0].lower())(ast[1], ast[2])
        self.teststeps.append(Step(running))
示例#19
0
def parse_parameters(parameters,
                     variables_mapping=None,
                     functions_mapping=None):
    """ parse parameters and generate cartesian product.

    Args:
        parameters (list) parameters: parameter name and value in list
            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()}"

        variables_mapping (dict): variables mapping loaded from testcase config
        functions_mapping (dict): functions mapping loaded from debugtalk.py

    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)

    """
    variables_mapping = variables_mapping or {}
    functions_mapping = functions_mapping or {}
    parsed_parameters_list = []

    # load project_meta functions
    from httprunner.loader import load_project_meta
    project_meta = load_project_meta("")
    functions_mapping.update(project_meta.functions)

    parameters = utils.ensure_mapping_format(parameters)
    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 = []
            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)
        else:
            # (2) & (3)
            parsed_variables_mapping = parse_variables_mapping(
                variables_mapping, functions_mapping)
            parsed_parameter_content = parse_data(parameter_content,
                                                  parsed_variables_mapping,
                                                  functions_mapping)
            if not isinstance(parsed_parameter_content, list):
                raise exceptions.ParamsError(
                    f"{parsed_parameter_content} parameters syntax error!")

            parameter_content_list = []
            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 = {
                        key: parameter_item[key]
                        for key in parameter_name_list
                    }
    #             elif isinstance(parameter_item, (list, tuple)):
    #                 # {"username-password": "******"}
    #                 # get_account() => [("user1", "111111"), ("user2", "222222")]
    #                 parameter_dict = dict(zip(parameter_name_list, parameter_item))
                elif len(parameter_name_list) == 1:
                    # {"user_agent": "${get_user_agent()}"}
                    # get_user_agent() => ["iOS/10.1", "iOS/10.2"]
                    parameter_dict = {parameter_name_list[0]: parameter_item}

                parameter_content_list.append(parameter_dict)

        parsed_parameters_list.append(parameter_content_list)

    return utils.gen_cartesian_product(*parsed_parameters_list)
示例#20
0
    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)

            # parse variables
            step.variables = parse_variables_mapping(
                step.variables, self.__project_meta.functions)

            while True:
                # 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)

                if step.retry_whens:
                    variables_mapping = step.variables
                    variables_mapping.update(extract_mapping)

                    try:
                        response = step.variables.get(
                            'response') or ResponseObject(requests.Response())
                        if isinstance(response, ResponseObject):
                            response.validate(step.retry_whens,
                                              variables_mapping,
                                              self.__project_meta.functions)
                        else:
                            break
                    except ValidationFailure:
                        break
                else:
                    break

            # 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
示例#21
0
文件: make.py 项目: tcw0nw/httprunner
def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
    """convert valid testcase dict to pytest file path"""
    # ensure compatibility with testcase format v2
    testcase = ensure_testcase_v3(testcase)

    # validate testcase format
    load_testcase(testcase)

    testcase_path = __ensure_absolute(testcase["config"]["path"])
    logger.info(f"start to make testcase: {testcase_path}")

    testcase_python_path, testcase_cls_name = convert_testcase_path(
        testcase_path)
    if dir_path:
        testcase_python_path = os.path.join(
            dir_path, os.path.basename(testcase_python_path))

    global make_files_cache_set
    if testcase_python_path in make_files_cache_set:
        return

    config = testcase["config"]
    config["path"] = __ensure_cwd_relative(testcase_python_path)

    # parse config variables
    config.setdefault("variables", {})
    if isinstance(config["variables"], Text):
        # get variables by function, e.g. ${get_variables()}
        project_meta = load_project_meta(testcase_path)
        config["variables"] = parse_data(config["variables"], {},
                                         project_meta.functions)

    # prepare reference testcase
    imports_list = []
    teststeps = testcase["teststeps"]
    for teststep in teststeps:
        if not teststep.get("testcase"):
            continue

        # make ref testcase pytest file
        ref_testcase_path = __ensure_absolute(teststep["testcase"])
        __make(ref_testcase_path)

        # prepare ref testcase class name
        ref_testcase_python_path, ref_testcase_cls_name = convert_testcase_path(
            ref_testcase_path)
        teststep["testcase"] = f"CLS_LB({ref_testcase_cls_name})CLS_RB"

        # prepare import ref testcase
        ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) +
                                                            1:]
        ref_module_name, _ = os.path.splitext(ref_testcase_python_path)
        ref_module_name = ref_module_name.replace(os.sep, ".")
        imports_list.append(
            f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}"
        )

    data = {
        "testcase_path": __ensure_cwd_relative(testcase_path),
        "class_name": f"TestCase{testcase_cls_name}",
        "config": config,
        "teststeps": teststeps,
        "imports_list": imports_list,
    }
    content = __TEMPLATE__.render(data)
    content = content.replace("'CLS_LB(", "").replace(")CLS_RB'", "")

    with open(testcase_python_path, "w", encoding="utf-8") as f:
        f.write(content)

    __ensure_testcase_module(testcase_python_path)

    logger.info(f"generated testcase: {testcase_python_path}")
    make_files_cache_set.add(__ensure_cwd_relative(testcase_python_path))
示例#22
0
    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)

            # parse variables
            step.variables = parse_variables_mapping(
                step.variables, self.__project_meta.functions)

            step.skipif = parse_data(step.skipif, step.variables,
                                     self.__project_meta.functions)

            # 跳过满足条件的步骤
            logger.debug(f"[跳过步骤] skipif={step.skipif}")
            if step.skipif == '':
                continue

            if step.skipif and eval(step.skipif):
                logger.debug(
                    f"[满足条件,跳过步骤] skipif={step.skipif} | step_name={step.name}"
                )
                continue

            # 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)

            # 每运行一个步骤就重新加载公共变量
            logger.debug(f"step.variables={step.variables}")
            step.variables.clear()

            # 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
示例#23
0
def generate_conftest_for_summary(args: List):

    for arg in args:
        if os.path.exists(arg):
            test_path = arg
            # FIXME: several test paths maybe specified
            break
    else:
        logger.error(f"No valid test path specified! \nargs: {args}")
        sys.exit(1)

    project_meta = load_project_meta(test_path)
    project_root_dir = ensure_file_path_valid(project_meta.RootDir)
    conftest_path = os.path.join(project_root_dir, "conftest.py")
    conftest_content = '''# NOTICE: Generated By HttpRunner.
import json
import os
import time

import pytest
from loguru import logger

from httprunner.utils import get_platform, ExtendJSONEncoder


@pytest.fixture(scope="session", autouse=True)
def session_fixture(request):
    """setup and teardown each task"""
    logger.info(f"start running testcases ...")

    start_at = time.time()

    yield

    logger.info(f"task finished, generate task summary for --save-tests")

    summary = {
        "success": True,
        "stat": {
            "testcases": {"total": 0, "success": 0, "fail": 0},
            "teststeps": {"total": 0, "failures": 0, "successes": 0},
        },
        "time": {"start_at": start_at, "duration": time.time() - start_at},
        "platform": get_platform(),
        "details": [],
    }

    for item in request.node.items:
        testcase_summary = item.instance.get_summary()
        summary["success"] &= testcase_summary.success

        summary["stat"]["testcases"]["total"] += 1
        summary["stat"]["teststeps"]["total"] += len(testcase_summary.step_datas)
        if testcase_summary.success:
            summary["stat"]["testcases"]["success"] += 1
            summary["stat"]["teststeps"]["successes"] += len(
                testcase_summary.step_datas
            )
        else:
            summary["stat"]["testcases"]["fail"] += 1
            summary["stat"]["teststeps"]["successes"] += (
                len(testcase_summary.step_datas) - 1
            )
            summary["stat"]["teststeps"]["failures"] += 1

        testcase_summary_json = testcase_summary.dict()
        testcase_summary_json["records"] = testcase_summary_json.pop("step_datas")
        summary["details"].append(testcase_summary_json)

    summary_path = "{{SUMMARY_PATH_PLACEHOLDER}}"
    summary_dir = os.path.dirname(summary_path)
    os.makedirs(summary_dir, exist_ok=True)

    with open(summary_path, "w", encoding="utf-8") as f:
        json.dump(summary, f, indent=4, ensure_ascii=False, cls=ExtendJSONEncoder)

    logger.info(f"generated task summary: {summary_path}")

'''

    test_path = os.path.abspath(test_path)
    logs_dir_path = os.path.join(project_root_dir, "logs")
    test_path_relative_path = test_path[len(project_root_dir) + 1:]

    if os.path.isdir(test_path):
        file_foder_path = os.path.join(logs_dir_path, test_path_relative_path)
        dump_file_name = "all.summary.json"
    else:
        file_relative_folder_path, test_file = os.path.split(
            test_path_relative_path)
        file_foder_path = os.path.join(logs_dir_path,
                                       file_relative_folder_path)
        test_file_name, _ = os.path.splitext(test_file)
        dump_file_name = f"{test_file_name}.summary.json"

    summary_path = os.path.join(file_foder_path, dump_file_name)
    conftest_content = conftest_content.replace("{{SUMMARY_PATH_PLACEHOLDER}}",
                                                summary_path)

    dir_path = os.path.dirname(conftest_path)
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)

    with open(conftest_path, "w", encoding="utf-8") as f:
        f.write(conftest_content)

    logger.info("generated conftest.py to generate summary.json")
示例#24
0
    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,
        )
示例#25
0
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)