HttpRunner二次开发UI自动化框架

基于HttpRunner接口自动化开源框架二次开发,下阶段更新UI自动化平台前端页面

Ui自动化应具备哪些功能呢?

  • Po模式,全称PageObject(页面对象);UI自动化PO模式应把一个页面分为三层,对象库层、操作层、业务层。(这里只实现操作层)业务层可在debugtalk中拓展

  • 收集每个test运行日志

  • 页面报错截图

  • WebDriver、BasePage可全局调用

  • BrowserDriver配置

  • 继承HttpRunner所有特性

  • 先实现以上几个功能,有需求后面再继续扩展

重新封装runner. _run_test方法

   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
                    "page": {
                        "url": "http://baidu.com",
                        "step":[{"by":'',"locator":'',"fun_name":'',"locator_desc":'':,"kwargs":{}}]
                    },
                    "extract": {},              # optional
                    "validate": [],             # optional
                    "setup_hooks": [],          # optional
                    "teardown_hooks": []        # optional
                }

        Raises:
            exceptions.ParamsError
            exceptions.ValidationFailure
            exceptions.ExtractFailure

        """
        # check skip
        self._handle_skip_feature(test_dict)

        # prepare
        test_dict = utils.lower_test_dict_keys(test_dict)
        test_variables = test_dict.get("variables", {})
        self.session_context.init_test_variables(test_variables)

        # teststep name
        self.meta_datas = {"log": "", "err_shot": "", "at_time": 0}  # Collect test logs
        test_name = self.session_context.eval_content(test_dict.get("name", ""))
        _log = f"====== Start the test, the use case is {test_name} ======\n"
        logger.log_info(_log)
        self.meta_datas["log"] += _log

        # parse test page
        raw_page = test_dict.get('page', {})
        parsed_test_page = self.session_context.eval_content(raw_page)
        self.session_context.update_test_variables("page", parsed_test_page)
        self.session_context.update_test_variables("driver", self.driver)

        # prepend url with base_url unless it's already an absolute URL
        url = parsed_test_page.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:
            step = parsed_test_page.pop('step')
            parsed_test_page.setdefault("verify", self.verify)
            # group_name = parsed_test_request.pop("group", None)
        except KeyError:
            raise exceptions.ParamsError("URL or Step missed!")

        # page
        valid_bys = ["XPATH", "ID", "LINK_TEXT", "PARTIAL_LINK_TEXT", "NAME", "TAG_NAME", "CLASS_NAME", "CSS_SELECTOR"]
        index = 1  # Number of steps
        eo = BasePage(self.driver)
        logger.log_info('Open page,Url:%s' % parsed_url)
        self.driver.get(parsed_url)
        start_at = eo.get_time  # start_at
        for item in step:
            _log = '====== execution step, id:%s ====== \n' % index
            logger.log_info(_log)
            self.meta_datas["log"] += _log
            if item["by"].upper() not in valid_bys:
                err_msg = u"by locator strategies! => {}\n".format(item["by"])
                err_msg += "Available of supported locator strategies: {}".format("/".join(valid_bys))
                self.meta_datas["log"] += err_msg
                logger.log_error(err_msg)
                raise exceptions.ParamsError(err_msg)
            try:
                eo.sys_method__run(item["by"].upper(), item["locator"], item["fun_name"],
                                   item["locator_desc"], item["kwargs"])
                self.meta_datas[
                    "log"] += f"Step details,{item['fun_name']} the operation,operation elements are {item['locator']}\n"
            except (TimeoutException, NoSuchElementException, InvalidSelectorException) as err:
                err_msg = f'{item["locator"]}The element search failed, the current page name is being intercepted\n'
                self.meta_datas["log"] += err_msg
                logger.log_error(err_msg)
                self.meta_datas["err_shot"] = eo.screen_shot()  # Screenshot page
                raise err
            finally:
                index += 1

        # teardown hooks
        teardown_hooks = test_dict.get("teardown_hooks", [])
        if teardown_hooks:
            self.session_context.update_test_variables("driver", self.driver)
            self.do_hook_actions(teardown_hooks, "teardown")

        # extract
        extractors = test_dict.get("extract", {})
        self.session_context.update_session_variables(extractors)

        # validate
        validators = test_dict.get("validate") or test_dict.get("validators") or []
        validate_script = test_dict.get("validate_script", [])
        if validate_script:
            validators.append({
                "type": "python_script",
                "script": validate_script
            })

        validator = Validator(self.session_context, None)
        try:
            validator.validate(validators)
        except (exceptions.ParamsError,
                exceptions.ValidationFailure, exceptions.ExtractFailure):
            err_msg = "{} DETAILED PAGE & Validator {}\n".format("*" * 32, "*" * 32)
            err_msg += "url: {}\n".format(parsed_url)
            for k, v in parsed_test_page.items():
                v = utils.omit_long_data(v)
                err_msg += "{}: {}\n".format(k, repr(v))

            err_msg += "\n"
            logger.log_error(err_msg)
            self.meta_datas["log"] += err_msg
            raise

        finally:
            # get page and validate results
            end_mes = f"====== End of test, use case time {eo.get_time - start_at} s ======\n"
            logger.log_info(end_mes)
            self.meta_datas["log"] += end_mes
            self.meta_datas["at_time"] = eo.get_time - start_at
            self.meta_datas["validators"] = validator.validation_results

新增Object文件

class BasePage(object):
    """公共方法"""

    def __init__(self, driver: Chrome):
        self.driver = driver

    def wait_presence_elem(self, locator, locator_desc=None, timeout=5, frequency=0.2):
        """
        等待元素存在,显示等待
        :param locator: 元素位置
        :param locator_desc: 元素描述
        :param timeout: 默认等待时间
        :param frequency: 默认间隔时间
        :return:
        """
        # 开始时间
        t1 = self.get_time
        wait = WebDriverWait(self.driver, timeout, poll_frequency=frequency)
        e = wait.until(EC.presence_of_element_located(locator))
        t2 = self.get_time
        logger.log_info("{} The element waits for the end, the waiting time is {}".format(locator_desc, (t2 - t1)))
        return e

    def wait_visible_elem(self, locator, locator_desc=None, timeout=20, frequency=0.2, ):
        """等待元素出现(可见),显示等待"""
        # 开始时间
        t1 = self.get_time
        wait = WebDriverWait(self.driver, timeout, poll_frequency=frequency)
        e = wait.until(EC.visibility_of_element_located(locator))
        t2 = self.get_time
        logger.log_info("{} The element waits for the end, the waiting time is {}".format(locator_desc, t2 - t1))
        return e

    def find_element(self, locator):
        """
        :param locator: 元素定位。以元组的形式。(定位类型、值)
        :return: WebElement对象。
        """
        logger.log_info("Find element {} ".format(locator))
        elem = self.driver.find_element(*locator)
        return elem

    def screen_shot(self):
        """截图"""
        # shot_name = 时间戳字符串 + 后缀名 .png
        shot_name = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + '.png'
        shot_file = os.path.join(BASE_DIR, 'media/file/' + shot_name)
        try:
            self.driver.save_screenshot(shot_file)
            logger.log_info("The current page has been intercepted, the file path {}".format(shot_file))
        except:
            logger.log_error("Failed to take screenshot of WebPage!")
        return shot_name

    def wait_click_elem(self, locator, timeout=0.5, frequency=0.2):
        """等待元素可被点击"""
        wait = WebDriverWait(self.driver, timeout, poll_frequency=frequency)
        e = wait.until(EC.element_to_be_clickable(locator))
        return e

    def my_quit(self):
        """关闭浏览器"""
        self.driver.quit()
        logger.log_info('Step details, close the browser')

    def click(self, locator, locator_desc=None):
        """点击元素"""
        self.wait_presence_elem(locator, locator_desc).click()
        logger.log_info('Step details, By:%s, Click element:%s' % (locator[0], locator[1]))

    def send_key(self, locator, value, locator_desc=None):
        """输入内容"""
        self.wait_presence_elem(locator, locator_desc).send_keys(value)
        logger.log_info('Step details, By:%s, element:%s, input content:%s' % (locator[0], locator[1], value))
        self.my_sleep(0.5)

    def get_text(self, locator, locator_desc=None):
        """获取文本"""
        _text = self.wait_presence_elem(locator, locator_desc).text
        logger.log_info('Step details, By:%s, element:%s, Get text:%s' % (locator[0], locator[1], _text))
        self.my_sleep(0.5)
        return _text

    @staticmethod
    def my_sleep(times):
        """强制等待"""
        time.sleep(times)
        logger.log_info(f'Step details, pause for {times}second')
    
    ..... 该文件封装web常用操作,自行百度

    def sys_method__run(self, by, locator, fun_name, locator_desc=None, kwargs=None):

        _tu_locator = (getattr(By, by), locator)
        fun = getattr(self, fun_name, None)
        if locator:
            if not isinstance(kwargs, dict) or not kwargs:
                kwargs = dict()
            kwargs.update({"locator": _tu_locator})
            kwargs.update({"locator_desc": locator_desc})
        try:
            return fun(**kwargs)
        except TypeError as err:
            raise err

启动web浏览器文件

class Browser:

    def __init__(self, name):
        self.name = name

    def start_browser(self):
        """firefox"、"chrome"、"ie"、"phantomjs"""
        try:
            if self.name == "firefox" or self.name == "Firefox" or self.name == "ff":
                logger.log_info("start browser name :Firefox")
                driver = webdriver.Firefox()
                return driver
            elif self.name == "chrome" or self.name == "Chrome":
                logger.log_info("start browser name :Chrome")
                driver = webdriver.Chrome()
                return driver
            elif self.name == "ie" or self.name == "Ie":
                logger.log_info("start browser name :Ie")
                driver = webdriver.Ie()
                return driver
            elif self.name == "phantomjs" or self.name == "Phantomjs":
                logger.log_info("start browser name :phantomjs")
                driver = webdriver.PhantomJS()
                return driver
            else:
                logger.log_error("Not found this browser,You can use 'firefox', 'chrome', 'ie' or 'phantomjs'")
        except Exception as msg:
            logger.log_error("An exception occurs when starting the browser \n%s" % str(msg))

ok、本地启动测试一下


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容