7

Selenium4+Python3系列(十二) - 测试框架的设计与开发 - 久曲健

 1 year ago
source link: https://www.cnblogs.com/longronglang/p/16972208.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Selenium4+Python3系列(十二) - 测试框架的设计与开发 - 久曲健 - 博客园

自己从未没想过能使用python来做自动化测试框架的设计、开发。

可能有人会好奇说,六哥,你怎么也用python写测试框架了?

python你也没有实际工作经验,可能就是自己自学的。

听完,那一刻,我真的特别证明自己,我也行!

整个框架的实现,大约也就1.5天,关于框架的开发并不是很难,主要难在测试报告增加失败自动截图功能echart的饼子图统计功能,两者的整合花了近半天的时间吧。

4b8a501c67f44e928502fefabe498938~tplv-k3u1fbpfcp-watermark.image?

e93853f425754636ab3c49fe9ecdd378~tplv-k3u1fbpfcp-watermark.image?

1、核心思想

延续使用Page ObjectPage Factory思想,使页面、数据、元素、脚本进行分离,此处演示仅仅为了讲解框架搭建思路,并非为我在公司写的那套框架,主要使用selenium4+python3+pytest,这里只贴核心代码,仅供学习交流使用。

目录结构

f268ac6e67f343f7a7af66bbc494cfb0~tplv-k3u1fbpfcp-watermark.image?

2、日志封装

主要用于方便定位用例脚本执行步骤,示例代码如下:

python
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/7 19:36
@Auth : 软件测试君
@File :LogUtils.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
import time
import os
import logging

currrent_path = os.path.dirname(__file__)
log_path = os.path.join(currrent_path, '../logs')


class LogUtils:

    def __init__(self, log_path=log_path):
        """
        通过python自带的logging模块进行封装
        """
        self.logfile_path = log_path
        # 创建日志对象logger
        self.logger = logging.getLogger(__name__)
        # 设置日志级别
        self.logger.setLevel(level=logging.INFO)
        # 设置日志的格式
        formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
        """在log文件中输出日志"""
        # 日志文件名称显示一天的日志
        self.log_name_path = os.path.join(self.logfile_path, "log_%s" % time.strftime('%Y_%m_%d')+".log")
        # 创建文件处理程序并实现追加
        self.file_log = logging.FileHandler(self.log_name_path, 'a', encoding='utf-8')
        # 设置日志文件里的格式
        self.file_log.setFormatter(formatter)
        # 设置日志文件里的级别
        self.file_log.setLevel(logging.INFO)
        # 把日志信息输出到文件中
        self.logger.addHandler(self.file_log)
        # 关闭文件
        self.file_log.close()

        """在控制台输出日志"""
        # 日志在控制台
        self.console = logging.StreamHandler()
        # 设置日志级别
        self.console.setLevel(logging.INFO)
        # 设置日志格式
        self.console.setFormatter(formatter)
        # 把日志信息输出到控制台
        self.logger.addHandler(self.console)
        # 关闭控制台日志
        self.console.close()

    def get_log(self):
        return self.logger


logger = LogUtils().get_log()

if __name__ == '__main__':
    logger.info('123')
    logger.error('error')

3、基础页面

用于存放,控件及API的常用操作,示例代码如下:

python
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/7 19:58
@Auth : 软件测试君
@File :BasePage.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
import time

from selenium.common import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait as WD

from util.LogUtils import LogUtils
from util.ParseConFile import ParseConFile

logger = LogUtils().get_log()


class BasePage(object):
    """控件及API的常用操作"""

    cf = ParseConFile()

    def __init__(self, driver, timeout=30):
        self.byDic = {
            'id': By.ID,
            'name': By.NAME,
            'class_name': By.CLASS_NAME,
            'xpath': By.XPATH,
            'link_text': By.LINK_TEXT,
            'css': By.CSS_SELECTOR
        }
        self.driver = driver
        self.outTime = timeout

    def find_element(self, by, locator):
        """
        通过id, name, xpath, css,class....,查找元素
        """
        try:
            logger.info("通过 " + by + " 定位")
            element = WD(self.driver, self.outTime).until(lambda x: x.find_element(self.byDic.get(by), locator))
        except TimeoutException as e:
            logger.error('请确认元素定位方式,' + e)
        else:
            return element

    def find_elements(self, by, locator):
        """
        通过id, name, xpath, css,class....,查找一组元素
        """
        try:
            logger.info("通过 " + by + " 定位")
            elements = WD(self.driver, self.outTime).until(lambda x: x.find_elements(self.byDic.get(by), locator))
        except TimeoutException as e:
            logger.error('请确认元素定位方式,' + e)
        else:
            return elements

    def get_text(self, by, locator):
        """
        获取元素文本/属性信息
        """
        logger.info("获取元素文本成功!")
        return self.find_element(by, locator).text

    def open_url(self, url):
        """打开浏览器"""
        logger.info("打开项目首页:" + url)
        self.driver.get(url)

    def quit_browser(self):
        self.driver.quit()

    def send_keys(self, by, locator, keys=''):
        """输入操作"""
        logger.info("输入:" + keys)
        self.find_element(by, locator).clear
        self.sleep(1)
        self.find_element(by, locator).send_keys(keys)

    def click(self, by, locator):
        """点击操作"""
        logger.info("点击按钮:" + locator)
        self.find_element(by, locator).click()

    @staticmethod
    def sleep(num=0):
        """强制等待"""
        logger.info("程序等待:" + str(num) + " 秒")
        time.sleep(num)

4、登陆页面

主要用于存放控件及元素操作,示例代码如下:

python
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/7 20:27
@Auth : 软件测试君
@File :LoginPage.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
from Page.BasePage import BasePage
from util.LogUtils import LogUtils
from util.ParseConFile import ParseConFile

logger = LogUtils().get_log()


class LoginPage(BasePage):
    """
    存放控件及元素操作
    """
    # 配置文件读取元素
    do_conf = ParseConFile()
    # 用户名输入框
    username = do_conf.get_locator('LoginPage_Elements', 'username')
    # 密码输入框
    password = do_conf.get_locator('LoginPage_Elements', 'password')
    # 登录按钮
    loginBtn = do_conf.get_locator('LoginPage_Elements', 'loginBtn')
    # 登录失败的提示信息
    error_msg = do_conf.get_locator('LoginPage_Elements', 'errorMsg')

    def login(self, username, password):
        """登录流程"""
        self.open()
        self.send_username(username)
        self.send_password(password)
        self.click_login_btn()
        msg = self.get_errorMsg()
        return msg

    def open(self):
        self.open_url('http://localhost:8080/login')

    def quit(self):
        self.quit_browser()

    def send_username(self, username):
        self.send_keys(*LoginPage.username, username)

    def send_password(self, password):
        self.send_keys(*LoginPage.password, password)

    def click_login_btn(self):
        self.click(*LoginPage.loginBtn)

    def get_errorMsg(self):
        return self.get_text(*LoginPage.error_msg)


if __name__ == "__main__":
    pass

5、业务操作

主要用于记录用例步骤,示例代码如下:

highlighter- Bash
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/7 20:27
@Auth : 软件测试君
@File :LoginPage.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
from Page.BasePage import BasePage
from util.LogUtils import LogUtils
from util.ParseConFile import ParseConFile

logger = LogUtils().get_log()


class LoginPage(BasePage):
    """
    存放控件及元素操作
    """
    # 配置文件读取元素
    do_conf = ParseConFile()
    # 用户名输入框
    username = do_conf.get_locator('LoginPage_Elements', 'username')
    # 密码输入框
    password = do_conf.get_locator('LoginPage_Elements', 'password')
    # 登录按钮
    loginBtn = do_conf.get_locator('LoginPage_Elements', 'loginBtn')
    # 登录失败的提示信息
    error_msg = do_conf.get_locator('LoginPage_Elements', 'errorMsg')

    def login(self, username, password):
        """登录流程"""
        self.open()
        self.send_username(username)
        self.send_password(password)
        self.click_login_btn()
        msg = self.get_errorMsg()
        return msg

    def open(self):
        self.open_url('http://localhost:8080/login')

    def quit(self):
        self.quit_browser()

    def send_username(self, username):
        self.send_keys(*LoginPage.username, username)

    def send_password(self, password):
        self.send_keys(*LoginPage.password, password)

    def click_login_btn(self):
        self.click(*LoginPage.loginBtn)

    def get_errorMsg(self):
        return self.get_text(*LoginPage.error_msg)


if __name__ == "__main__":
    pass

6、测试报告之失败带截图

这块确实很坑,看了很多网上的教程,笔者不才,整了一下午才弄出失败带截图,主要是对conftest.py的设计编写,示例代码如下:

highlighter- Bash
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/10 18:13
@Auth : 软件测试君
@File :conftest.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
import pytest
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager

driver = None


@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item):
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = report.nodeid.replace("::", "_") + ".png"
            screen_img = _capture_screenshot()
            if file_name:
                html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
                       'οnclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra


@pytest.fixture(scope='session')
def browser():
    global driver
    if driver is None:
        driver = webdriver.Chrome(ChromeDriverManager().install())
        driver.maximize_window()
    yield driver
    driver.quit()
    return driver


def _capture_screenshot():
    """截图"""
    return driver.get_screenshot_as_base64()

7、执行脚本

主要用于调用测试用例脚本,示例代码如下:

python
# -*- coding: utf-8 -*-
"""
@Time : 2022/12/10 18:04
@Auth : 软件测试君
@File :RunTestCase.py
@IDE :PyCharm
@Motto:ABC(Always Be Coding)
"""
import sys

import pytest

from config.conf import ROOT_DIR, HTML_NAME


def main():
    if ROOT_DIR not in sys.path:
        sys.path.append(ROOT_DIR)
    # 执行用例
    args = ['--html=' + './report/' + HTML_NAME]
    pytest.main(args)


if __name__ == '__main__':
    main()

8、测试效果

用例执行效果:

8ea17fb8111b4a588b2b350bc7e75674~tplv-k3u1fbpfcp-watermark.image?

测试报告:

b015422b43914940ac84f8be7fb6e490~tplv-k3u1fbpfcp-watermark.image?

其实写框架并不难,掌握核心思路,实现起来就会变得容易很多,与语言无关哦(因为我是Java党)。

关于API及很多细节部分,没做详细处理和封装,这里笔者仅仅是提供思路,感兴趣的同学,可自行去尝试进行进一步扩展,如想要源代码的同学可以文末留言或者加我好友领取哦。

__EOF__


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK